Hi DP-ler,
wie versprochen fangen wir mit den
Exceptions an. Exceptions sind Delphis Weg, um Fehler "abzufangen" und dem Anwender einer Anwendung darzustellen. Mit Hilfe der Konstrukte
try...except...end und
try...finally...end kann der Programmier im Falle einer
Exception (eines Fehlers) diesen abfangen bzw. notwendige "Aufräumarbeiten" werden durchgeführt, bevor der Fehler angezeigt wird.
Dieser Abschnitt des Kurses bildet die Grundlage für den nächsten. Wir werden als erstes eine Komponente beginnen, welche es uns ermöglicht die Standardfehlermeldung von Delphi zu umgehen und eigene Wege zu beschreiten.
try...except...end
Es gibt in der Programmierung immer wieder Bereiche, in welchen man davon ausgehen muss, dass es füher oder später zu Problemen im Code kommen wird. Die wohl üblichste Fehlerquelle ist eine falsche Eingabe durch den Benutzer.
Fangen wir doch mal mit einem sehr einfachen Beispiel an. Der Nutzer soll eine Zahl eingeben, welche später für Rechenoperationen genutzt wird. Wenn der Nutzer aber etwas anderes (oder nichts) eingibt, dann liegt kein gültiger Wert für die Rechenoperation vor.
Um das Beispiel ein wenig zu verdeutlichen, werden wir die folgende Prozedur etwas genauer betrachten.
Code:
function DivideIt_01(Zahl: String): String;
begin
Result := FloatToStr(1000 / StrToFloat(Zahl));
end;
Zugegeben, die Funktion ist sehr einfach. Aber anhand dieser Funktion lässt sich das ganze sehr leicht darstellen. Übergeben wird der Parameter
Zahl als String.
Zahl wird in eine Fließkommazahl (
StrToFloat) umgewandelt. Dieser Wert wird dann genutzt als Teiler für den Wert 1000 genutzt.
Es gibt jetzt zwei typische, potentielle Fehlerquellen.
- Der Wert des Parameters Zahl ist keine Fließkommazahl
- Der Parameter Zahl ist '0' - dann erfolgt eine Division durch Null.
Die gezeigt Funktion würde den Fehler nicht erkennen. Dadurch würde der an die aufrufende Funktion weitergeleitet werden. Folgende Funktion zeigt die simpelste Möglichkeit diesen Fehler mit Hilfe eines try...except...end Blockes abzufangen.
Code:
function DivideIt_02(Zahl: String): String;
begin
try
Result := FloatToStr(1000 / StrToFloat(Zahl));
except
Result := 'Der übergebene Wert ist leider ungültig';
end;
end;
Das Programm versucht die Anweisung
Result := FloatToStr(1000 / StrToFloat(Zahl)); auszuführen. Sollte dabei ein Problem auftreten, wird der Code innerhalb des
except Blockes ausgeführt. In diesem Fall geben wir einen "freundlichen" Hinweis zurück.
Hinweis: Der EXCEPT Block wird
nur im Falle einer aufgetretenen
Exception ausgeführt.
try...finally...end
Oft genug geschieht es in der Programmierung, dass wir einige Resourcen (Speicher) des Computers in Anspruch nehmen müssen. Anschließend sollten wir diese Resourcen stets wieder freigeben. Wieder ein denkbar einfaches Beispiel
Code:
function CountLines(FileName: String): Integer;
var
Strings: TStringList;
begin
// ressourcen reservieren
Strings := TStringList.Create;
// datei in die string liste laden
Strings.LoadFromFile(FileName);
// zeilenanzahl zurückliefern
Result := Strings.Count;
// string liste wieder zerstören
Strings.Free;
end;
Dieses Beipsiel liefert uns die Anzahl der Zeilen in einer (Text-)Datei zurück. In diesem Beispiel nutze ich eine Stringliste. Als erstes muss diese initialisiert werden. Anschliessend kann die Datei geladen werden, um die Anzahl der Zeilen zu ermitteln. Am Ende wird die Stringliste wieder freigegeben.
Was passiert jetzt, wenn die Datei nicht existiert oder der Nutzer nicht das Recht hat diese zu öffnen? Es wird eine
Exception (ein Fehler) in der Zeile
Strings.LoadFromFile(FileName); erzeugt und die Verarbeitung abgebrochen. Die Zeile zum freigeben der Stringliste
Strings.Free; wird nie ausgeführt, dass heißt, es ist ein kleines Speicherloch entstanden...
Da kommt der try...finally...end Block recht gelegen. Dieser Block wird immer ausgeführt. Damit können wir als Programmierer garantieren, dass alle Ressourcen wieder freigegeben werden. In unserem Beispiel sähe das wie folgt aus.
Code:
function CountLines(FileName: String): Integer;
var
Strings: TStringList;
begin
// ressourcen reservieren
Strings := TStringList.Create;
try
// datei in die string liste laden
Strings.LoadFromFile(FileName);
// zeilenanzahl zurückliefern
Result := Strings.Count;
finally
// string liste wieder zerstören
Strings.Free;
end;
end;
Auch wenn ein Fehler beim Laden der Datei auftritt, ist garantiert, dass die Stringliste zerstört wird, unabhängig davon, ob alle vorhergehenden Schritte ausgeführt worden sind, oder nicht.
Hinweis: Der FINALLY Block wird
immer ausgeführt. Auch wenn mitten im Code Befehle zum Abbrechen der Prozedur oder einer Schleife (z.B. Exit, Break) auftauchen.
Exceptions erzeugen
Um das Erzeugen eigener Exceptions zu demonstrieren, kehren wir zu unserem ersten Beispiel zurück.
Code:
function DivideIt_03(Zahl: String): String;
begin
if Zahl = '' then
raise
Exception.Create('Ein leerer Parameter ist unbrauchbar!');
Result := FloatToStr(1000 / StrToFloat(Zahl));
end;
Das Erzeugen einer
Exception ist denkbar einfach. Erstellt wird eine
Exception durch die Einbindung des reservierten Wortes
raise. Als Parameter wird ein
Exception Object (bzw. ein davon abgeleitetes Objekt) erstellt. Im obigen Beispiel testen wir, ob ein leerer String übergeben wird; in diesem Fall erstellen eine
Exception mit einer entsprechenden Fehlermeldung.
Eigene Exception-Typen deklarieren
Um im späteren Verlauf eines Programmes aufgetretene Fehler leichter zu erkennen, gibt Delphi uns die Möglichkeit eigene Fehlerklassen zu erstellen.
Code:
type
EEmptyParam = class(
Exception)
public
constructor Create;
end;
{ EEmptyString }
constructor EEmptyParam.Create;
begin
inherited Create('Ein leerer Parameter ist unbrauchbar!');
end;
In diesem Beispiel erstellen wir eine Fehlerklasse für leere (unbrauchbare) Parameter. Wenn wir diese Klasse als Fehlerklasse in unser letztes Beispiel integrieren, dann sieht die Funktion wie folgt aus.
Code:
function DivideIt_04(Zahl: String): String;
begin
if Zahl = '' then
raise EEmptyParam.Create;
Result := FloatToStr(1000 / StrToFloat(Zahl));
end;
Für den Anwender des Programmes ergeben sich daraus keine Änderungen, aber sehr wohl für den Programmierer, wie wir im folgenden Abschnitt (Excpetions erkennen) sehen werden.
Exceptions erkennen
Nachdem wir die Exceptions verstanden, erstellt und abgefangen haben, müssen wir diese für korrektes und sinnvolles Handling nur noch wiedererkennen. Dazu gibt es in einem try...except...end Block die Möglichkeit eine
Exception-Typ-Überprüfung vorzunehmen.
Code:
function DivideIt_05(Zahl: String): String;
begin
try
if Zahl = '' then
raise EEmptyParam.Create;
Result := FloatToStr(1000 / StrToFloat(Zahl));
except
// leere parameter erkennung
on E: EEmptyParam do Result := 'Leerer Parameter';
// division durch 0 erkannt
on E: EZeroDivide do Result := 'Division durch Null (0)';
// übergebener parameter ist keine zahl
on E: EConvertError do Result := 'Keine Zahl (NaN)';
// was gibt es sonst noch ???
on E:
Exception do Result := '??? ' + E.Message;
end;
end;
Durch die Überprüfung
on E: Exceptiontyp können wir den Typ eines Fehlers ermitteln und entsprechend reagieren. Die folgende Liste nennt die häufigsten Fehlertypen
- EIntError
- ERangeError
- EIntOverflow
- EMathError
- EInvalidOp
- EZeroDivide
- EOverflow
- EInvalidPointer
- EInvalidCast
- EConvertError
- EAccessViolation
- EStackOverflow
- EWin32Error
- ESafecallException
Das Beispiel...
zum downloaden
Der nächste Schritt
Im nächsten Teil dieses Tutorials fangen wir mit der Erstellung einer Komponente an. Diese Komponente ermöglicht es uns das Aussehen von Fehlermeldungen zu beeinflussen oder gar komplett Kontext-abhängige Fehlerbehandlungen durchzuführen.
Mal sehn was kommt...
Tippfehler gefixt! FuckRacism