Fehler abfangen
Was ist ein Laufzeitfehler?
Unter Object-Pascal können Laufzeitfehler (auch Exceptions genannt) auftreten, wenn eine Ausnahmesituation auftritt.Dies kann passieren, wenn beispielsweise versucht wird, eine Zahl durch 0 zu teilen oder wenn auf eine ungültige Speicheradresse zugegriffen wird.Wenn so ein Laufzeitfehler auftritt, wird die Ausführung des Codes abgebrochen und eine Fehlermeldung angezeigt.Im Falle einer Konsolen-Anwendung wird das Programm ohne Hinweis auf einen Fehler abgebrochen und sofort beendet. Bei einer Formularanwendung wird die Fehlermeldung angezeigt und das Programm läuft weiter, die aktuelle Ausführung des Codes wird aber trotzdem abgebrochen.
Es kann immer mal sein, dass durch verschiedene Umstände ein Fehler entsteht. Dies können System-Fehler sein (z.B. dass nicht mehr genügend Arbeitsspeicher zum Reservieren eines Objektes vorhanden ist) oder Fehler, die durch den Benutzer hervorgerufen wurden (z.B. dass der Benutzer beim Öffnen eines Bildes ein beschädigtes angegeben hat).In beiden Fällen ist das Ergebnis nicht schön: die Programmausführung wird abgebrochen, sodass eventuell andere (unvorhersehbare) Fehler folgen:
Button1.Enabled := False; //Der Button wird deaktiviert Image1.LoadFromFile('C:\test.bmp'); //Es soll ein Bild geladen werden Button1.Enalbed := True; //Der Button wird wieder aktiviert
In diesem Beispiel wird zuerst Button1 deaktiviert und dann ein Bild in Image1 geladen. Am Ende wird der Button wieder aktiviert.Wenn die Datei C:\test.bmp aber fehlt oder beschädigt ist, bricht der Code bei „Image1.LoadFromFile(‚C:\test.bmp‘);“ ab – Button1 wird nie wieder aktiviert und kann nicht mehr betätigt werden.Es sollte trotzdem darauf geachtet werden, Laufzeitfehler zu vermeiden.In dem Beispiel oben sollte also vorher noch geprüft werden, ob die Bild-Datei überhaupt existiert. Oder falls eine Benutzereingabe eingelesen wird, sollte diese erst validiert, d.h. auf falsche Eingaben untersucht werden, anstatt direkt darauf zuzugreifen. Hier ein Beispiel:
if (length(edit1.Text) >= 1) then //Zuerst wird geprüft, ob die Eingabe länger oder gleich 1 Zeichen ist begin edit1.Text[1] := 'A'; //Wenn sichergestellt ist, dass das erste Zeichen vorhanden ist, kann darauf zugegriffen werden end else ShowMessage('Die Eingabe muss mindestens 1 Zeichen lang sein!'); //Wenn die Eingabe falsch ist, soll eine Meldung ausgegeben werden
In diesem Beispiel wird vorher geprüft, ob das erste Zeichen existiert.Existiert es, wird es neu zugewiesen, andernfalls wird dem Benutzer eine Nachricht angezeigt, dass seine Eingabe falsch war.
Das try…finally…end Konstrukt
Object-Pascal bietet darum Möglichkeiten, einen bestimmten Code-Block in jedem Fall auszuführen (auch wenn ein solcher Laufzeitfehler aufgetreten ist), um dort Finalisierungsaufgaben durchzuführen, wie beispielsweise das Freigeben von vorher reservierten Objekten, das Aktivieren von Steuerelementen oder das ordnungsgemäße Schließen von Datenbanken.Dieser Block wird mit try…finally…end umschlossen:
Database.Lock; //Zuerst muss der Zugriff für andere Programme auf diese Datenbank gesperrt werden try Database.Check; //Hier wird die Datenbank auf Daten-Fehler überprüft finally Database.Unlock; //Egal was passiert ist: in jedem Fall soll die Datenbank für andere Zugriffe wieder entsperrt werden end;
Zuerst wird die Datenbank gesperrt.In dem Abschnitt von try bis finally wird die Datenbank auf Daten-Fehler durchsucht. Unabhängig davon, ob dort ein Fehler auftritt, muss die Datenbank für den Zugriff wieder entsperrt werden: dies geschieht im Abschnitt von end bis finally.Wenn in der Funktion Database.Check ohne dieses try…finally…end Konstrukt ein Laufzeitfehler entstehen würde, könnte nicht mehr mit der Datenbank gearbeitet werden, da sie jeden Zugriff gesperrt hat: Das gesamte Programm würde abstürzen und somit nicht mehr funktionieren. Mit diesem finally-Block wird sichergestellt, das wieder ein Zugriff auf die Datenbank möglich ist, eine Fehlermeldung wird aber trotzdem angezeigt und der Code nach dem „end;“ des try..finally…end Blocks wird auch übersprungen.Selbstverständlich kann dieses Konstrukt auch verschachtelt werden.Für Objekte gilt dasselbe:
obj := TObj.Create; try obj.DoSomething; finally obj.Free; end;
Zuerst wird das Objekt erstellt – aber Achtung: dies darf nicht im try-Block geschehen! Tritt nämlich beim Erstellen des Objekts ein Fehler auf, wird das Objekt nicht erstellt und darf somit auch nicht freigegeben werden, was ansonsten im finally-Block geschehen würde. Da sich dieser Befehl aber außerhalb des try-Blocks befindet, wird der finally-Block bei einem Laufzeitfehler im Konstruktor nie ausgeführt.Dann wird im try-Block eine Methode des Objekts aufgerufen und am Ende wird es wieder freigegeben. Auch wenn die aufgerufene Methode fehlschlägt, wird das Objekt freigegeben.Im Allgemeinen ist es guter Programmierstil, nach einer Instanzierung von einem Objekt immer ein try…finally…end Konstrukt einzusetzen – egal wie fehlersicher die nachfolgenden Aufrufe sind.
Das try…except…end Konstrukt
Manchmal möchte man einen Laufzeitfehler vor dem Benutzer unterdrücken, ihn auswerten oder ihn einfach anders darstellen.Dazu wird wie bei try…finally…end der kritische Code nach dem try eingefügt. Tritt ein Fehler auf, wird der Code von except bis end ausgeführt, andernfalls wird dieser übersprungen:
Button1.Enabled := False; try Image1.LoadFromFile(OpenDialog.Filename); //Es wird versucht, das vom Benutzer ausgewählte Bild zu laden except ShowMessage('Ups, da ist wohl was schief gelaufen!'); //Wenn das fehlschlägt, wird eine Nachricht angezeigt end; Button1.Enabled := True;
Hierbei wird zuerst der Button1 deaktiviert, dann versucht, das vom Benutzer ausgewählte Bild zu laden und dann der Button1 wieder aktiviert.Schlägt der Versuch, das Bild zu laden fehl (beispielsweise weil der Benutzer eine beschädigte Datei ausgewählt hat), wird eine Nachricht angezeigt.In jedem Fall wird Button1 wieder aktiviert, da der Fehler im except Block abgefangen wurde und somit nicht mehr existiert.Der Code danach wird wie gewohnt weiter ausgeführt.In dem except Block kann außerdem auf bestimmte Fehler reagiert werden – auch können bestimmte Fehlereigenschaften ausgelesen werden:
try DoSomething; //Tue irgendetwas except on EMathError do ShowMessage('Ein mathematischer Fehler ist aufgetreten!'); //Falls es ein Fehler mathematischer Natur ist (z.B. wenn versucht wurde, durch 0 zu teilen) on E: EAccessViolation do ShowMessage('Folgende Accessviolation ist aufgetreten: "' + E.Message + '"'); //Wenn es eine Accessviolation ist, soll auch noch die Nachricht des Fehlers angezeigt werden else ShowMessage('Sonstiger Fehler'); //Falls es ein anderer Fehler ist end;
Durch das on-Konstrukt kann der Fehler angegeben werden, der behandelt werden soll. Über einen Bezeichner und einem Doppelpunkt vor der Angabe des Lautzeitfehlers kann über den Bezeichner auf bestimmte Eigenschaften des Fehlers, z.B. auf die Nachricht oder den Klassennamen zugegriffen werden.Mit dem else werden alle anderen Laufzeitfehler behandelt, die nicht im on-Konstrukt aufgeführt wurden.Dabei sollte beachtet werden, dass die Laufzeitfehler in einer Hierarchie angeordnet sind.Zum Beispiel sind die Laufzeitfehler EDivByZero und ERangeError Unterklassen von EIntError.Wenn jetzt der Fehler EIntError abgefangen wird, werden auch alle Fehler, die unter ihm stehen (wie beispielsweise EDivByZero und ERangeError), abgefangen.Es muss daher die Reihenfolge beachtet werden, in der die Fehler abgefangen werden! Hier ein Beispiel:
try X := 0; X := 5 div X; Caption := IntToStr(X); except on EIntError do ShowMessage('Ein Zahl-Fehler ist aufgetreten!'); on EDivByZero do ShowMessage('Es wurde versucht, durch 0 zu teilen!'); end;
Hier wird nie die Meldung angezeigt, dass versucht wurde, durch 0 zu teilen, obwohl der Laufzeitfehler EDivByZero ist. Dies kommt, weil EDivByZero eine Unterklasse von EIntError ist: Wird der Laufzeitfehler EIntError behandelt, werden damit auch alle seine Unterklassen behandelt.