| HOWTO Work with Exceptions | |
|
zurück |
Autor : Sancho Fock Wenn man Fehler behandeln will, muss man sich zunächst mal im Klaren darüber sein, was ein Fehler überhaupt ist. Dann kann man auch entscheiden wie man damit umgehen will. Was ist ein Fehler?Grundsätzlich ist ein Fehler, eine Abweichung eines Ergebnisses von der Erwartung. Anders gesagt: reagiert ein Softwaresystem unerwartet oder ermittelt es Ergebnisse, die nicht im Rahmen der zu erwartenden Parameter sind, handelt es sich um einen Fehler. Ein Fehler kann nur im Kontext einer einzelnen Verarbeitung gesehen werden. Ein Fehler liegt immer dann vor, wenn ermittelte Ergebnisse einer Verarbeitung unerwartet unplausibel sind, oder wenn die Voraussetzungen für die Verarbeitung Unerwarteterweise nicht gegeben sind. Der Schlüssel ist also das Unerwartete. Fehlerbehandlung bedeutet somit: Das Unerwartete erwarten. top of page Vom Fehler zur SystemreaktionDaraus ergibt sich, dass ein Verarbeitungsergebnis in einem Verarbeitungsschritt ein Fehler sein kann, während die aufrufende Verarbeitung mit diesem „Fehler“ rechnet und ihn als legale Antwort betrachtet. Beispiel: try Qry.Fields[0].AsInteger := StrToInt(Edit1.Text); Except On
e : EConversionError do Qry.Fields[0].AsInteger := 0; End; Enthält das Property Text der EditBox keinen gültigen Integer Wert, so führt das zu einem Fehler in der Funktion StrToInt. Die Voraussetzungen für die Verarbeitung von StrToInt sind –unerwartet- nicht gegeben, da sich der Eingabewert nicht in eine Ganzzahl wandeln läst. Hier liegt ein Fehler vor, deshalb wirft StrToInt eine Exception. Die Aufrufende Verarbeitung hat jedoch damit gerechnet, dass sich der Eingabewert möglicherweise nicht in eine Ganzzahl wandeln lässt. Daher fängt sie die Exception ab und betrachtet diese nur noch als Reaktion des Systems. Die Verarbeitung reagiert entsprechend, und legt fest, dass der Zielwert in diesem Fall immer 0 sein soll. Damit bleibt das System in einem definierten Zustand. Wichtig ist also den Kontext der aktuellen Verarbeitung zu sehen: Für StrToInt liegt ein Fehler vor, für den Aufrufer nicht. top of page Was tun wenn der Fehler da ist?Liegt ein Fehler im obigen Sinne vor, den die aktuelle Verarbeitung nicht beheben kann, muss die aktuelle Verarbeitung diesen Fehler weitergeben, also entweder eine vorhandene Exception weiterwerfen (lassen) oder eine neue oder eigne Exception erzeugen und werfen. Zudem muss die Verarbeitung sicherstellen, dass alle von Ihr angeforderten Ressourcen und globalen Verarbeitungen (z.B. Transaktionen) zurückgesetzt werden. Kann die Verarbeitung eine Fehlersituation beheben, oder sinnvoll darauf reagieren, ohne dass dies einen Aufrufer stören würde, so ist der Fehler abzufangen und die Eskalation damit zu stoppen. Der Fehler ist dann kein Fehler mehr. Dies sollte für alle auftretenden Fehler möglich sein. Wird ein Fehler nirgends behandelt, wird die
Standardfehlerbehandlung ausgelöst. Da sich diese bei Delphi standardmäßig auf
das zeigen einer Message Box beschränkt sollte man hier unbedingt mehr tun und
einen globalen Handler, Tracer und Logger implementieren. Grundsätzlich gilt: Existiert ein Fehler im obigen Sinne soll auch eine Exception existieren. top of page Die ExceptionExceptions sind eine Hilfe, um eine strukturierte Fehlerbehandlung zu implementieren. Sie bieten insbesondere die folgenden Möglichkeiten: - Da eine Exception eine Klasse ist, können Fehlerklassen gebildet werden. Durch die Vererbung wird es möglich Fehler auf diese Art sehr fein zu gliedern. - Da Exceptions Klassen sind, kann Polymorphie zur Behandlung der Fehler eingesetzt werden. Das heißt, dass man auf einen Fehler reagieren kann, auch wenn man speziell diesen Fehler zur Implementationszeit gar nicht kennt. Es reicht auf die Fehlerklasse zu reagieren, von der diese spezielle Exception abgeleitet ist. - Das System bietet eine automatische Eskalation des Fehlers an (wird ein Fehler nicht behandelt, wird er automatisch an den Aufrufer weitergegeben. Bei Fehlercodes ist das beispielsweise umgekehrt). - Das System bietet einen automatischen Abbruchmechanismus (Durch alle Stack-Frames hindurch), wenn eine Exception geworfen wird. top of page VCL ExceptionsDelphi bietet bereits eine ganze Reihe von Exceptions, die von den Delphi Komponenten und Klassen auch fleißig geworfen werden. Treten in der eigenen Anwendungen unbehandelte Exceptions auf, so ist dies fasst immer auf einen Programmierfehler innerhalb der Applikation zurückführen. Sprich: Es sollen alle evtl. auftretenden Exceptions an der geeigneten Stelle abgefangen und behandelt werden. top of page Eigene ExceptionImmer dann, wenn in einem Verarbeitungsschritt ein Fehler auftritt, den man zwar vorhergesehen hat, den man jedoch nicht behandeln kann, sollte dafür ein eigener Exception Typ erstellt und geworfen werden. Dieser neue Typ soll in der vorhandenen Exception Hierarchie an eine geeignete Stelle eingehangen werden. Beispiele:
// Beispiel 1: Wenn die Funktion ein Formular anzeigen soll, function ShowWindowModal(aForm : TForm) : TModalResult; Begin if not assigned(aForm) then raise EObjectNotAssignedException.create(‘Das
Formular, das angezeigt werden sollte ist nicht erzeugt worden‘); Result
:= aForm.ShowModal; end;
// Beispiel 2: Die Funktion rechnet zwar damit, dass es beim function getUserBirthday(Name : String) : TDateTime; var thisUser : TUser; Begin try thisUser
:=(myStringList.Objects[myStringList.IndexOf(Name)]
as Tuser); result := thisUser.Birthdate; Except on e : EstringListError do raise EuserNotFoundEx.Create(‘Der User ‘+Name+‘ ist Unbekannt‘); on e : EAccessViolation
do raise EuserListCorrupt.create(‘Beim
Zugriff auf die Userliste kam es zu einer Schutzverletzung‘); end; end; top of page Behandlung von ExceptionsWie bereits erwähnt, werden Exceptions automatisch eskaliert. Das bedeutet also sie funktionieren auch, wenn man keine eigene Implementation für die Exceptions vorgenommen hat. Da Exceptions jedoch eine Verarbeitung auch abbrechen, muss evtl. sichergestellt werden, dass z.B. Ressourcen oder Transaktionen geschützt bleiben. Sind mögliche Fehler von Aktionen bekannt, sind diese so früh wie möglich zu behandeln. Ist eine Behandlung nicht möglich kann darauf verzichtet werden, es sei denn es erscheint sinnvoll den Fehlerkontext zu ändern. top of page Try ... finallyDas try...finally Konstrukt dient ausschließlich dazu, Ressourcen oder Verarbeitungsvorgänge zu schützen. Tritt im try Block eine Exception auf (oder es wird exit oder Break aufgerufen) wird die Verarbeitung an der auslösenden Stelle abgebrochen, der finally Block wird ausgeführt und wenn eine Exception den Abbruch verursacht hat, wird die Exception (weiter-)geworfen. Beispiele für die korrekte Verwendung von try...finally function getUserName(FileName : String;
Index : Integer) : String; Var Sl : TStringList; Begin Sl := TStringList.create; Try Sl.LoadFromFile(FileName); Result := sl.strings[Index]; Finally FreeAndNil(Sl); End; End; Procedure GlobalBlockedIncrement; Begin While
bGloablIsInProcess do Sleep(1000); bGloablIsInProcess
:= True; Try iGlobalCounter
:= iGlobalCounter + 1; myGlobalObject.process(iGlobalCounter); finally bGlobalIsInProcess
:= false; end; end; top of page Try ... exceptDas Try...except Konstrukt dient dazu, aufgetretene Exceptions zu erkennen, und sie zu behandeln. Wird eine Exception mit Try...Except gefangen, wird sie nicht weiter eskaliert. Wird in einem Except Block nicht auf mindestens einen Typen explizit eingegangen, gilt er für alle aufgetretenen Exception Klassen. Wird mit „on“ auf mindestens eine Exception Klasse eingegangen, gelten alle anderen Exception Klassen als unbehandelt und werden weitergeworfen. Da das Ziel der Fehlerbehandlung ist, dass keine Fehler verloren gehen, darf Except nur dann eingesetzt werden, wenn eine oder mehrere Fehlerklassen explizit behandelt werden. Ansonsten ist ggf. try...finally einzusetzen. Zu vermeiden und ggf. zu beseitigen sind daher Konstrukte wie die folgenden: // Try ...
Except nono‘s Try DoSomething; Except //
hier wird jede Exception verschluckt. Der End; Try DoSomething; Except On
e : exception do; End; //
auch hier wird jede Exception verschluckt. Der Try DoSomething; Except On
e : EStringlistError do Rollback; Else ShowMessage(‘uups‘); End; End;
// Hier wird zwar die EStringlistError Exception Try DoSomething; Except On e : Exception do ShowMessage(e.className+‘ aufgetreten‘); End;
// Hier werden alle Exceptions verschluckt, der Anwender Sollte es dennoch einmal nötig sein, an einer bestimmten Stelle alle Exceptions oder bestimmte Exception Klassen zu ignorieren, so ist der entsprechende Code mit einem eingehenden Kommentar zu versehen, der erklärt, warum die Exception hier ignoriert und verschluckt werden soll. Beispiele für die korrekte Verwendung von try...Except:
// begonnene Transaktion abbrechen und den Aufrufer mit einer Db.beginTransaction; Try QryInsert.ExecuteSQL; QryUpdateRelations.ExecuteSQL; QryDeleteOldRel.ExecuteSQL; Db.commit; Except On
e : EdatabaseException do Begin Db.rollback; Raise
EAppendCustomerNotPossibleEx.create(e); End Else Begin Db.rollback; raise; End End; // Eine Aktion wiederholen, wenn sie fehlgeschlagen ist: Function AddNewCustomer :
Integer; Var Succ : boolean; NewID, failures : Integer; Begin Failures := 0; Succ := false; Qry1.sql := ‘select max(ID) from customer‘; Repeat Qry1.open; NewID := qry1.fields[0].AsInteger; Qry1.close; With
qry2 do Begin Sql := 'Insert into customer (ID)
values ('+IntToStr(NewID)+‘)‘; try ExecuteSQL; Succ := true; Except On
e : EDatabaseError do Inc(failures); End; end; Until
Succ or
(Failures > 10); If
not Succ then Raise
EInsertingCustomerFailedEx.create; Result := NewID; End; // Den Fehlerkontext anpassen function getUserBirthday(Name : string) : TDateTime; var thisUser : TUser; Begin try thisUser
:=(myStringList.Objects[myStringList.IndexOf(Name)]
as Tuser); result := thisUser.Birthdate; Except on e : EstringListError do raise EuserNotFoundEx.Create('Der User '+Name+‘ ist Unbekannt‘); on e : EAccessViolation
do raise EuserListCorrupt.create('Beim Zugriff auf die Userliste kam es zu einer
Schutzverletzung‘); end; end; // aus einer Auftretenden Exception einen Verarbeitungszustand try Qry.Fields[0].AsInteger := StrToInt(Edit1.Text); Except On
e : EConversionError do Qry.Fields[0].AsInteger := 0; End; top of page zurück |