Home Computer Pascal Kurs Site Map counter

Jetzt lerne ich Pascal... Teil 6

In diesem Teil geht es um einen sauberen Programmierstill, um möglichst fehlerfreies Programmieren. Das alleine wäre schon ein eigener Kurs, aber ich will mich hier darauf beschränken die Sprachmittel von Turbo Pascal auszunutzen. Wenn sie das tun sind sie schon weiter als viele C Programmierer die ich kenne.

Typendeklaration

Bei Anfängern zu kurz kommt oft die Deklaration eigener Typen. Natürlich werden Typen deklariert die man dringend braucht wie Records oder Arrays. Aber wenn man eine Routine hat die Menüpunkte verarbeitet, wie deklarieren Sie eine Variable die einen Buchstaben entgegennimmt?

Es gibt hier zwei Möglichkeiten:

Var taste: char;

oder

Type menutyp = 'A'..'Z';
Var taste: Menutyp;

Beides mal wird der Compiler eine Char Variable einsetzen. Im zweiten Fall haben sie aber durch den Compiler Switch {$R+} die Möglichkeit das Range Checking einzuschalten: Der Compiler prüft ob bei solchen Typen eine Zuweisung in dem deklarierten Bereich ist. Eine Zuweisung wie

taste:=' ';

löst schon beim Compilieren einen Fehler aus, beim Testen zur Laufzeit gibt es ähnliche Fehlermeldungen. Diese dienen natürlich nicht um sie zu ärgern, denn wenn Sie in einem CASE den Wert abfragen und es taucht ein Wert auf den sie nie vorgesehen haben, so haben sie ein Problem. Ist ihr Programm getestet, so können Sie das Prüfen abschalten und der Code ist genauso effizient wie der erste Code mit einer Char Variablen.

Dies gilt auch für Aufzählungstypen, Arrays und Mengentypen. Es empfiehlt sich Beispielsweise den Index eines Arrays separat zu deklarieren:

Type
feldindex = 1..80;
arraytyp = Array [Feldindex] of String[40];

Konstantendeklaration

Noch stiefmütterlicher behandeln Anfänger die Konstantendeklaration - "Wozu Konstanten deklarieren, wenn ich den Wert direkt einsetzen kann?" Nun weil wenn man eine Konstante mehrmals im Quelltext hat, die Gefahr von Schreibfehlern gebannt ist, weil wenn man Änderungen an den Konstanten hat diese nur einmal machen muss und weil Namen oft besser merkbar sind Zahlen.

Der wichtigste Grund sind aber Programänderungen. Angenommen Sie haben ein Array kreiert und Sortieren dieses mit Bubble Sort so steht irgendwo vorne:

Feld: Array [1..100] of String;

und irgendwo im Programm:

procedure Bubblesort;

Var i,j: integer;
temp: String;

begin
for i:=1 to 99 do
for j:=i+1 to 100 do
if feld[i]>feld[j] then
begin
temp:=feld[i]; feld[i]:=feld[j]; feld[j]:=temp;
end;
end;

Nun ändert sich die Dimension auf 200. Nun als schlauer Programmierer ersetzen Sie nun überall im Quelltext über die Suchen/Ersetzenfunktion 100 durch 200. Doch was ist mit der Zeile mit der 99? Richtig die bleibt so und schon sortiert ihre Routine nur die Hälfte des Arrays durch. Richtig geschrieben sähe das Programmstück so aus:

const
feld_u 1;
feld_o 100;

type
feld_bound feld_u..feld_o;
feldtyp : Array [feld_bound] of String;

Var
Feld : feldtyp;

und irgendwo im Programm :

procedure Bubblesort;

var i,j : feld_bound;
temp : String;

begin
for
i:=feld_u to feld_o-1 do
for
j:=i+1 to feld_o do
if
feld[i]>feld[j] then
begin
temp:=feld[i]; feld[i]:=feld[j]; feld[j]:=temp;
end;
end;

Das ist zugegeben etwas extrem, aber sehr wartungsfreundlich. Zudem nutzt es auch den range Check für Array und i,j. In der Praxis ist es so, das man meistens als Programmierer Arrays bei 1 oder 0 anfangen lässt. Man kann sich in diesem Fall die untere Grenze sparen. Doch eine Konstante für die Obere Grenze macht immer Sinn!

Funktionsargumente

Der Typ von Funktionsargumenten haben Sie in Teil 3 kennengelernt. An dieser Stelle einige erläuternde Worte.

Wertparameter

Der häufigste Typ ist ein Wertparameter, d.h. es wird nur eine Variable deklariert ohne das Schlüsselwort CONST oder VAR. In diesem Fall erhält die Funktion eine Kopie des aktuellen Parameters. Man kann in der Funktion diesen verändern ohne das diese Änderungen auf den Originalwert "durchschlagen", z.B. kann eine Funktion die Leerzeichen aus einem String löscht, und diesen zurückgibt den Übergabewert dazu nutzen, ohne eine lokale Variable anzulegen.

Variablenparameter

Diese Parameter werden mit dem Wort VAR eingeleitet. Hier erhalten Sie keine Kopie sondern den originalen Parameter. Technisch gesehen arbeiten Sie mit Zeigern und alle Programmierer die von C kommen, werden Sie um die einfache Handhabung in Pascal beneiden. Der normale Anwendungsbereich sind Variablen die eine Funktion verändert. Wenn Sie z.B. in ihrem Programm immer eine Zeile wie diese haben:

Zeile:=Leerzeichen_weg(Zeile);

Wenn Sie immer den Übergabeparameter als Funktionsergebnis benutzen ist es besser zu schrieben:

Leerzeichen_weg(Zeile)

wobei Leerzeichen_weg definiert ist als

procedure Leerzeichen_weg(VAR Zeile: String)

Der Vorteil: Bei der Funktion haben Sie zweimal den String kopiert: Einmal bei der Übergabe und einmal bei der Zuweisung. Dies fällt bei der Prozedur weg. Aus diesem Sachverhalt ist auch klar, das es etwas bringt komplexe Typen (alles was intern nicht als Ganzzahl oder Fliesskommazahl gespeichert wird, also Strings, Arrays, Records und Dateien) als VAR Parameter zu übergeben, wenn die Funktion es nicht verändert. Das ist z.B. bei Arrays of der Fall, wenn Funktionen diese nutzen, um Werte zu extrahieren oder zu berechnen.

Konstante Parameter

Mann hat bei Borland das erkannt und ab Turbo Pascal 7.0 das alte Schlüsselwort Const neu eingeführt. Es hat zwei Funktionen. Zum einen gibt es Sicherheit, wenn man weiß, das eine Funktion einen Wert nicht verändern darf, z.B. eine Funktion die ein Array auswerter sollte weder das Array noch die Anzahl der Elemente ändern. Wenn man diese als Const deklariert so meldet der Compiler eine Zuweisung an diese Werte.

Der zweite ist, das man so Strukturen wie Arrays, Strings oder Records als Variablenparameter übergeben kann, also den gleichen kompakten Code wie bei VAR erzeugt, jedoch die zusätzliche Absicherung durch die Prüfung hat.

Es ist daher beim Schreiben des Funktionskopfes genau zu überlegen, welche Art der Parameter ist und das richtige Schlüsselwort zu wählen.

Exceptions

Wenn sie selbst schon etwas programmiert haben so werden Sie öfters eine unliebsame Beendigung mit der Meldung "Run Time Error 2xx at adress xxxx:xxxx" erlebt haben. Es handelt sich dabei um Laufzeitfehler, die der Compiler nicht entdecken kann, weil sie erst entstehen wenn das Programm läuft. Beliebte Laufzeitfehler sind: Wie kann man dem vorbeugen? Unter Turbo Pascal gibt es nur die Möglichkeit jede Codstelle zu überarbeiten. z.B.:

if x=0 then erg:=0 else erg:=y/x;

Hier wird vor der Division geprüft ob der Teiler 0 ist und wenn ja als Ergebnis 0 gesetzt. Als zusätzliche Absicherung kann man eine Procedure deklarieren, die Turbo Pascal vor dem ende des Programms auf jeden Fall aufruft.

Es ist eine normale Prozedur ohne Parameter aber mit dem Schlüsselwort far deklariert, da sie von jedem Programmteil aus erreichbar sein muss:

procedure Error; far;

begin
if
offene_datei
then close(datei);
end;

Diese Prozedur schließt z.B. offene Dateien vor dem Beenden.
Delphi hat neu das Exception Handling eingeführt, das man auch in den DOS Versionen Free Pascal und Virtual Pascal findet Grundlage ist folgender Block:

try
anweisungen;
except
Anweisungen im Fehlerfall;
end;

Zwischen Try und exept werden Anweisungen eingeschlossen die Einen Laufzeitfehler verursachen können, z.B. einen Divisionsfehler oder einen I/O Fehler. Tritt dieser Fehler auf, so wird der except Teil angesprungen und die dort befindlichen Anweisungen ausgeführt. Der Fehler ist damit gegessen und verursacht keinen Programmabbruch.

Oftmals hat man keine Anweisungen für den Except Fall, muss aber in jedem Fall Anweisungen durchführen, egal ob ein Fehler auftritt oder nicht. Sehr häufig ist dies z.B. das schließen offener Dateien. Das muss immer passieren. In diesem Fall gibt es eine Variante des try Statements

try
anweisungen;
finally
Anweisungen in jedem Fall;
end;

Egal wann bei try die Bearbeitung durch einen Fehler abgebrochen wurde oder es keinen gab, die Anweisungen bei finally werden auf jeden Fall ausgeführt. So was braucht man wenn man dynamisch Speicher allokiert und diesen im Finally Teil freigeben will, oder wenn man eine Datei bearbeitet und diese in jedem Falle geschlossen werden soll. Wenn es mehrere Fehlermöglichkeiten gibt so kann man diese noch eingrenzen:

try
...
Except
on EZeroDivide do HandleZeroDivide;
on EOverflow do HandleOverflow;
on EMathError do HandleMathError;
else donothing;
end;

Anstatt normaler Anweisungen kann man im Except Teil eine Liste von on Fehler do folgendes aufführen. Die einzelnen Fehlertypen sind definiert, wie zu überlegen ist ein Ezerodivide Fehler ein Laufzeitfehler der durch eine Division durch 0 entsteht. Man kann so auf den Fehler gezielt agieren. Try..except Blöcke können auch geschachtelt werden, für eine lokale Sonderbehandlung. Wichtig ist die Reihenfolge: EMathError ist ein allgemeiner Fehler und EOverflow ein spezieller EMathError. Der Compiler nimmt den ersten auf den der Fehler passt. Würde man die Reihenfolge umkehren, so würde nur der HandleMathError ausgeführt, weil EZeroDivide und EOverflow besondere EMathError sind (abgeleitete Klassen für die Leser, die schon Vererbung beherschen).

Man kann auch Fehlertypen selbst definieren und über raise einen Fehler auslösen. Dies kann wichtig sein wenn eine aufgerufene Funktion einen Fehler feststellt, und dies über raise an die aufrufende Funktion meldet, wodurch deren exception Handling aktiviert wird. Das wird man z.B. nutzen wenn man eigene Objekte mit einem Array hat und versucht wird auf einen ungültigen Index zuzugreifen.

Variablennamen

Es hat sich eingebürgert einfache Variablen für Schleifen etc. mit i,j,k.... zu bezeichnen. Alle Variablen, die aber irgendeine Funktion haben, sollen sollten auch einen aussagekräftigen Namen haben. So können sie auch nach längerer Zeit erkennen, was ihr Code gerade macht. Als Ersatz für ein Leerzeichen ist der Unterstrich (_) sehr gut geeignet. In anderen Sprachen ist es oftmals auch so, das es sich eingebürgert hat die ersten Buchstaben für eine Typangabe zu nehmen z.B. int_textlaenge. Man weiß hier sofort ohne in die Deklaration zu schauen das es eine Variable vom Typ Integer ist.

Ich halte davon bei Pascal nichts, da man aus dem Kontext erkennen kann was die Variable macht, anders als z.B. in C. In Sprachen die casesensitiv sind (Unterscheidung von Groß- und Kleinschreibung bei Variablennamen) haben sich oft weitere Regeln für Groß und Kleinschreibung eingebürgert. Man kann diese auf Pascal übertragen, hat aber auch mehr Schreibarbeit ohne echten Mehrnutzen. Ich selbst benutze gerne den Unterstrich um zusammengesetzte Worte zu trennen:
Kosten_pro_Monat oder Bahn_Berechnung wären solche Bezeichner. Ich halte dies für lesbarer als die Lösung die in C/C++/Java gängig ist: kostenProMonat und BahnBerechnung. Doch ich muss zugeben, ich stehe damit relativ alleine da. Wie immer Sie es machen, sorgen Sie dafür das Sie erkennen können was in der Variablen steckt, was die funktion macht - anhand ihres Namens. Anfänger fallen in der Pascal Newsgroup (de.comp.lang.pascal) durch kurze, nicht aussagekräftige Variablennamen auf.

Doppelter Code

Sehr beliebt - auch beim Autor - ist bei neuen Problemen die auf alten basieren, das Copy and Paste Prinzip: Man kopiert den alten Code an die neue Stelle modifiert ihn und fertig. Das ganze hat aber ein paar Nachteile: Deswegen ist es sinnvoller in einem solchen Fall inne zu halten und zu überlegen, was die Unterschiede zwischen neuer Funktion und alter sind, eine Funktion oder Prozedur zu schrieben in welcher der Code steckt. Hat man mehrere davon so lohnt es sich eine Unit draus zu machen.

Noch ein paar Ratschläge

Versuchen Sie ihr Problem in mehrere kleine Funktionen zu zerlegen. Eine Funktion sollte nicht zu lang sein (Faustregel: Auf dem Bildschirm sollte sie komplett Platz haben) und nicht zu viele Parameter entgegennehmen. Ist das der Fall so zerteilen sie weiter. Meistens kommt man so von selbst zu oftmals wiederverwendbaren Funktionen.

Vermeiden sie globale Daten: Diese sind gefährlich. Jeder kann sie ändern. Nutzen Sie stattdessen die Vorteile von Pascal. Pascal bietet ihnen anders als Java oder C/C++ zwei Vorteile an:

Komm schon!

Defensives Programmieren

Da Sie mit Pascal eine sehr lesbare Programmiersprache haben, erleichtert Ihnen schon einiges. Andere Sprachen sind von Natur aus schwer zu lesen (COBOL, C, C++ als prominente Beispiel) und bei manchen machen sogar die Erfinder vor wie man schlechte Programme schreibt (C). Damit ist neben den etwas langschwafeligen Vorarbeiten, die ich weiter oben getan habe schon etwas getan um ihre Programme sicher und leichter wartbar zu machen. Doch es gehört mehr dazu und das hat nicht einmal etwas unbedingt mit Programmieren zu tun. Man sollte sich überlegen:

Dies bezeichnet man als defensives Programmieren: Sein Programm gegen alle Eventualitäten absichern. Oftmals merkt man beim Nachdenken über diese Fragen, dass man komplette Prüfungen vergessen hat. Der meiner Meinung nach wichtigste Punkt ist der letzte. Sehr oft hat man eine Funktion geschrieben die ganz nützlich ist. Diese war einmal in einen Kontext eingebettet der ein paar Vorarbeiten gemacht hat, nun ist sie in einem anderen Programm nützlich, man übernimmt sie, aber die Vorarbeiten fehlen!

Daher mein Rat: Wenn sie etwas mehrfach verwenden können: lagern sie es in eine Unit aus, packen Sie alles dazu was sie an zusätzlichen Funktionen brauchen. Nutzen Sie auch das in Pascal einmalige Konzept, dass eine Funktion oder Prozedur wiederum einzelne Funktionen als "Unterprogramme" haben kann. Diese können Nebenarbeiten erledigen mit eigenen Variablen und sehen nur das was innerhalb der Funktion bekannt ist - Keine Seiteneffekte wie in C oder C++.´


Zum Thema Computer ist auch von mir ein Buch erschienen. "Computergeschichte(n)" beinhaltet, das was der Titel aussagt: einzelne Episoden aus der Frühzeit des PC. Es sind Episoden aus den Lebensäufen von Ed Roberts, Bill Gates, Steve Jobs, Stephen Wozniak, Gary Kildall, Adam Osborne, Jack Tramiel und Chuck Peddle und wie sie den PC schufen.

Das Buch wird abgerundet durch eine kurze Erklärung der Computertechnik vor dem PC, sowie einer Zusammenfassung was danach geschah, als die Claims abgesteckt waren. Ich habe versucht ein Buch zu schreiben, dass sie dahingehend von anderen Büchern abhebt, dass es nicht nur Geschichte erzählt sondern auch erklärt warum bestimmte Produkte erfolgreich waren, also auf die Technik eingeht.

Die 2014 erschienene zweite Auflage wurde aktualisiert und leicht erweitert. Die umfangreichste Änderung ist ein 60 Seiten starkes Kapitel über Seymour Cray und die von ihm entworfenen Supercomputer. Bedingt durch Preissenkungen bei Neuauflagen ist es mit 19,90 Euro trotz gestiegenem Umfang um 5 Euro billiger als die erste Auflage. Es ist auch als e-Book für 10,99 Euro erschienen.

Mehr über das Buch auf dieser eigenen Seite.

Sitemap Kontakt Neues Hier werben Bücher vom Autor Buchempfehlungen Top 99