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 Systemreaktion

Daraus 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.
Librarys wie die JCL bieten Möglichkeiten diese Aufgabe zu vereinfachen.
Die erweiterten Fehlerbehandlungsfunktionalitäten kann man aber auch fertig mit Produkten wie madExcept erhalten.

 

Grundsätzlich gilt: Existiert ein Fehler im obigen Sinne soll auch eine Exception existieren.

 


top of page

Die Exception

Exceptions 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 Exceptions

Delphi 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 Exception

Immer 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,
// der Zeiger darauf jedoch nicht assigned ist, ist die
// Funktion hilflos, deshalb muss sie eine Exception werfen

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
// Zugriff auf die Fehlerlist zu einem Problem kommt, kann
// das Problem jedoch nicht beseitigen, deshalb erzeugt sie
// eine neue Exception, die den Fehler für den Aufrufer
// klarer macht.
// Wenn z.B. eine Schutzverletzung auftritt, kann der
// Aufrufer von getUserBirthday nicht wissen, was für ein
// Problem vorgelegen hat, gibt getUserBirthday jedoch eine
// Exception weiter, die den Fehlerfall erklärt kann der
// Aufrufer evtl. gezielt darauf eingehen.

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 Exceptions

Wie 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 ... finally

Das 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 ... except

Das 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      
       // Aufrufer bekommt kein Feedback darüber, dass        // ein Fehler aufgetreten ist

End;

 

Try

  DoSomething;

Except

  On e : exception do;

End; // auch hier wird jede Exception verschluckt. Der
     // Aufrufer bekommt kein Feedback darüber, dass
     // ein Fehler aufgetreten ist

 

Try

  DoSomething;

Except

  On e : EStringlistError do

    Rollback;

  Else

    ShowMessage(uups);

  End;

End; // Hier wird zwar die EStringlistError Exception
     // behandelt, aber alle anderen Exceptions werden
     // verschluckt, der Anwender bekommt lediglich eine
     // (für Ihn) nicht aussagekräftige Fehlermeldung.
     // Der Aufrufer jedoch bekommt kein Feedback darüber,
     // dass eine Fehler aufgetreten ist. Somit ist kein
     // geplanter Ablauf garantiert.

 

Try

  DoSomething;

Except

  On e : Exception do

    ShowMessage(e.className+‘ aufgetreten‘);

End; // Hier werden alle Exceptions verschluckt, der Anwender
     // bekommt lediglich eine nicht aussagekräftige
     // Fehlermeldung. Der Aufrufer jedoch bekommt kein
     // Feedback darüber dass eine Fehler aufgetreten ist.

 

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
// Exception informieren, wenn bekannt mit einem
// explizierteren Exception Typ

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
// ableiten

try

  Qry.Fields[0].AsInteger := StrToInt(Edit1.Text);

Except

  On e : EConversionError do

    Qry.Fields[0].AsInteger := 0;

End;

 


top of page

zurück