Fehlermeldungen in Delphi

Nutzen von Exceptions

Wir haben nun kennen gelernt, wie man Exceptions vermeidet. Jetzt stellt sich die Frage, für was es überhaupt Exceptions gibt, wenn man sie eh vermeiden sollte. Das Konzept der Exceptions existiert in vielen Programmiersprachen und fußt auf der objektorientierten Programmierung(OOP). Bevor es Exceptions gab, hat man im Wesentlichen mit zwei Systemen zur Fehlerbehandlung gearbeitet: Fehlercodes und Fehlerreportfunktionen. In der WinAPI, also der Funktionssammlung von Windows, welche nun zunehmend vom .NET-Framework(welches Exceptions benutzt) ersetzt werden soll, kommt man noch mit beiden Typen in Kontakt.

Fehlercodes

Wird eine Funktion ausgeführt, gibt sie einen Fehlercode(meist einen Integer) zurück, der angibt, ob die Funktion erfolgreich ausgeführt werden konnte(meist ist dann der Fehlercode 0) oder nicht. In letzterem Fall spezifiziert dann der Fehlercode die Art des Fehlers. Speziell dafür definierte Fehlerkonstanten machen dann den Code lesbarer. Das ganze hat allerdings einen Nachteil: Werden Funktionen von Funktionen benutzt, muss der Fehlercode immer „von Hand“ angepasst bzw. durchgereicht werden. Und da, fast jede Funktion ihre eigenen Fehlercodes hat, kann das zu einem riesigen Fehlercodebrei werden, was der Übersicht nicht gerade förderlich ist…

Fehlerreportingfunktionen

Eine Art „Übergangslösung“, wenn man das denn eine „Lösung“ nennen darf, sind die Funktionen vom Typ „RaiseLastOSError“. Tritt ein Fehler in einer Funktion auf, die dieses Konzept verfolgt, so merkt sich Windows diesen Fehler intern. Weiter passiert erstmal nichts. Die Funktion liefert dann nur noch zurück, ob sie erfolgreich ausgeführt wurde, oder nicht(z.B. über einen Rückgabewert vom Typ Boolean). In letzterem Fall kann dann der Programmierer an einer geeigneten Stelle RaiseLastOSError(bzw. früher: RaiseLastWin32Error) aufrufen und dem Anwender wird die Fehlermeldung präsentiert.

Exceptions

Mit der OOP kam dann schließlich ein Umdenken in der Programmierung. Wurden bisher Funktionssammlungen programmiert und diese genutzt(prozedurale Programmierung), so werden nun voneinander unabhängige Programmbausteine(so genannte Objekte; Beispiele: Buttons, Memos…) entwickelt. Diese sind meist unterschiedlichen Schichten zugeordnet (eine untere Schicht z.B. kümmert sich nur um die Daten und ausschließlich die oberste Schicht kommuniziert mit dem User). Je nach Komplexität und Art der Anwendung kann es mehr oder weniger dieser Schichten geben. Problematisch wird es nun, wenn ein Fehler in der untersten Schicht auftritt. Da nur die oberste Schicht mit dem Benutzer kommunizieren darf und aber gleichzeitig die mittleren Schichten im Fehlerfall wissen müssen, was sie zu tun haben (kontrollierter Abbruch einer Aktion), muss die Information, dass ein Fehler(und natürlich auch welcher) ganz unten aufgetaucht ist durch alle Schichten irgendwie nach oben wandern. Teilweise werden dabei auch noch unterschiedliche Informationen gebraucht.
Um den ganzen Fehlerbehandlungsprozess in den Griff zu bekommen, gibt es nun Exceptions. Eine Exception ist aus OOP-Sicht nichts anderes, als eine Klasse, die durch einen speziellen Mechanismus das Durchreichen der Fehlermeldung vereinfacht.
Eine Exception kann unterschiedliche Informationen enthalten und lässt sich daher mit einem Paket vergleichen. Tritt irgendwo ein Fehler auf, wird ein Paket (Exception) erstellt. Diese wird nun automatisch durch die einzelnen Schichten durchgereicht. Jede Schicht kann sich das Paket angucken, etwas rausnehmen, etwas reintun oder das Paket sogar für angekommen erklären (und damit auch nicht mehr weitergeben). So hat dann jede Schicht alle Infos die sie braucht und kann flexibel darauf reagieren. Dabei werden alle weiteren Aktionen abgebrochen, bis die Exception behandelt ist.

Dabei muss eine Exception nicht zwangsweise ein Fehler sein. Wie oben schon erwähnt ist eine Exception eine Art „erweiterter Rückgabewert“, der dem Programmierer die Möglichkeit gibt, Informationen (zu Ausnahmesituationen) aus unteren Schichten bis dorthin durchzureichen, wo sie gebraucht werden. Meistens handelt es sich dann um einen „Fehler“, aber eben nicht immer. Insbesondere bei den Indy-Komponenten führt dies bei manchen Usern von Zeit zu Zeit zu Verwirrung(EIdConnClosedGracefully)…

Wenn man also die Möglichkeit hat, Exceptions zu vermeiden, sollte man das natürlich tun, denn dann spart man sich(bzw. dem Programm) die ganze Paketversendearbeit. Manchmal braucht man aber genauere Informationen bzw. manchmal bietet sich gar keine Möglichkeit die Exception zu verhindern. Und genau für solche Fälle gibt es Exceptions. Wie man nun damit umgeht, sehen wir im nächsten Kapitel…

Exceptions benutzen

Schauen wir uns nun einmal an, wie Exceptions benutzt werden. Dazu dient uns folgendes Grundgerüst:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
procedure TForm1.Button1Click(Sender: TObject);
begin
  if F1 then
    ShowMessage('Hallo.');
end;
 
function TForm1.F1: Boolean;
begin
  Result := F2('test') <> 0;
end;
 
function TForm1.F2(s: string): Integer;
begin
  if s = 'test' then
    Result := 0
  else if s = 'test2' then
    Result := 1
  else
    Result := -1;
  F3(Result);
end;
 
procedure TForm1.F3(i: Integer);
begin
  // mach was
end;

Die einzelnen Funktionen symbolisieren die einzelnen Schichten des Programmes. Jede Funktion wird von einer anderen aufgerufen. Durch Klick auf den Button werden also alle Funktionen ineinander verschachtelt aufgerufen…

Tritt nun in F3 ein Fehler auf, so wollen wir den Parameter in Button1Click auswerten können. Dazu leiten wir erst einmal eine neue Exception-Klasse ab:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  EMyException = class(Exception) // alle Exceptions beginnen per Konvention mit einem E; Die Klasse Exception ist Vorfahr aller Exceptions
  private
    FParam: Integer;
  public
    constructor Create(Msg: string; Param: Integer);
    property Param: Integer read FParam write FParam;
  end;
 
[...]
 
constructor EMyException.Create(Msg: string; Param: Integer);
begin
  inherited Create(Msg);
  FParam := Param;
end;

Wir haben also nun unsere eigene Exception-Klasse, die wir um eine weitere Eigenschaft und einen entsprechenden Konstruktor erweitert haben.

Mit der Zeile

1
raise EMyException.Create('Fehler', i);

können wir nun die Exception erzeugen(‚werfen‘).

Durch entsprechende try…except-Blöcke kann nun die Exception abgefangen und behandelt werden:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    if F1 then
      ShowMessage('Hallo.');
  except
    on E: EMyException do
    begin
      ShowMessage('Eine EMyException ist in Button1Click angekommen. '
        + 'Der Parameter war ' + IntToStr(E.Param) + sLineBreak
        + 'Die Meldung lautet:' + sLineBreak + E.Message);
    end;
    on Exception do // egal, welche Art von Exception es ist
    begin          // da aber die EMyException oben schon behandelt wurde, passiert hier nichts
      ShowMessage('Irgendeine Exception ist in Button1Click angekommen; '
        + 'Exception wird nicht weiter gegeben');
    end;
  end;
end;
 
function TForm1.F1: Boolean;
begin
  try
    Result := F2('test') <> 0;
  except
    on E: Exception do   // egal, welche Art von Exception es ist
    begin
      ShowMessage('Eine Exception ist in F1 angekommen.');
      raise;   // weiterreichen
    end;
  end;
end;
 
function TForm1.F2(s: string): Integer;
begin
  try
    if s = 'test' then
      Result := 0
    else if s = 'test2' then
      Result := 1
    else
      Result := -1;
    F3(Result);
  except
    on E: EMyException do   // nur, wenn es sich um eine EMyException handelt
    begin
      E.Message := 'Meldung aus F2' + sLineBreak + E.Message;  // Meldung anhängen
      raise; // weiterreichen
    end;
  end;
end;
 
procedure TForm1.F3(i: Integer);
begin
  raise EMyException.Create('Fehler', i);  // Exception werfen
end;

Wenn wir das Programm starten, erhalten wir folgende Meldungen:

—————————
Project1
—————————
Eine Exception ist in F1 angekommen.
—————————
OK
—————————

und dann:

—————————
Project1
—————————
Eine EMyException ist in Button1Click angekommen. Der Parameter war 0
Die Meldung lautet:
Meldung aus F2
Fehler
—————————
OK
—————————

Was passiert aber nun, wenn es sich um eine andere Exception-Klasse handelt? Probieren wirs einfach aus. Oben im Code sind schon Teile vorhanden, die sich darum kümmern. Zum Testen, ändern wir die Zeile

1
  raise EMyException.Create('Fehler', i);  // Exception werfen

in

1
  raise Exception.Create('Fehler');  // Exception werfen

Dabei erhalten wir folgende Meldungen:

—————————
Project1
—————————
Eine Exception ist in F1 angekommen.
—————————
OK
—————————

und danach:

—————————
Project1
—————————
Irgendeine Exception ist in Button1Click angekommen; Exception wird nicht weiter gegeben
—————————
OK
—————————

Hier wurde die Exception in F3 geworfen, in F2 automatisch durchgereicht, weil sie dort nicht behandelt wurde, in F1 registriert und weitergereicht und in Button1Click abschießend behandelt.

Schauen wir uns noch einmal Folgendes an:

1
2
3
4
5
6
7
8
9
10
11
    on E: EMyException do
    begin
      ShowMessage('Eine EMyException ist in Button1Click angekommen. '
        + 'Der Parameter war ' + IntToStr(E.Param) + sLineBreak
        + 'Die Meldung lautet:' + sLineBreak + E.Message);
    end;
    on Exception do // egal, welche Art von Exception es ist
    begin          // da aber die EMyException oben schon behandelt wurde, passiert hier nichts
      ShowMessage('Irgendeine Exception ist in Button1Click angekommen; '
        + 'Exception wird nicht weiter gegeben');
    end;

Was pasiert wohl, wenn wir die beiden on-Blöcke vertauschen? Ganz einfach: Der untere Block wird in keinem Fall ausgeführt. Auch, nicht, wenn es sich um eine EMyException handelt. Die Exception ist ja schon durch den vorhergehenden Block schon behandelt. Wichtig ist also, dass die allgemeinen on-Blöcke unten stehen und die spezifischen oben…

Wir sehen also, dass alle Informationen von ganz unten nach ganz oben wanden können. Dabei kann jede Schicht auf die Infos zugreifen und diese sogar verändern. Trotzdem müssen wir uns weder Gedanken über das Freigeben der Exception-Klasse (das macht Delphi automatisch), noch über das Durchreichen machen, was mit einem einfachen raise; getan war.
Und sollte eine Exception gar nicht behandelt werden (was eigentlich ein Programmierfehler ist), springt Delphi für einen ein und zeigt dem Benutzer die Fehlermeldung, damit der den Support kontaktieren kann… 😉

Kapitel: | Zurück | 1 | 2 | 3 | 4 | 5 | 6 | Weiter |

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.