Fehler in Programmen sind oftmals unerwartete Reaktionen im Programmfluss. Es können zwei Arten von Fehlern auftreten. Fehler die man verarbeiten (handled) und welche die man nicht verarbeiten kann (unhandled). Solche Schwierigkeiten nennt man im Fachjargon auch Ausnahmen (engl. Exceptions). Das Exceptions-Modell ist in vielen gängigen Programmiersprachen wie etwa C++, PHP (seit Version 5) oder Delphi vorzufinden.
Wie funktionieren Exceptions?
Stellen Sie sich die Hierachie eines Großunternehmens vor. Von unten nach oben gibt es normale Mitarbeiter, Abteilungsleiter, Filialleiter und dann den Vorstand. Angenommen ein Mitarbeiter bekommt durch einen Zufall per Post die Quartalszahlen für das Unternehmen. Der Mitarbeiter kann damit nicht wirklich was anfangen und gibt es weiter an den Abteilungsleiter. Der widerum gibt es an seinen Vorgesetzten. Solange bis die Nachricht den Vorstand erreicht. Dieser setzt sodann entsprechende Hebel in Bewegung. So ähnlich läuft das auch mit Exceptions, nur dass die Hierarchie des Unternehmens die Aufrufliste (engl. Callstack) des Programms darstellt. Ganz oben steht der Prozess bzw. der Einstiegspunkt des Programms und ganz unten die Funktion oder Methode welche die Exception ausgelöst hat.
Wie bei dem Beispiel mit dem Unternehmen wird auch hier die Exception an die aufrufende Methode in der Hierarchie weitergereicht. Kann diese Methode die Exception nicht verarbeiten, wird diese an dessen aufrufende Methode gereicht. Das geht solange, bis der Einstiegspunkt erreicht ist. Ist bis dahin die Exception nicht verarbeitet, wird dem Betriebssystem eine unbehandelte Ausnahme gemeldet was eine Prozessterminierung zur Folge hat.
Was sind Asserts?
Asserts sind Programmabschnitte in denen bestimmte Vorraussetzungen geschaffen sein müssen, damit der weitere Programmfluss die gewünschten Ergebnisse liefert. Schlägt eine Assertion fehl, wird der “Entwickler” darüber informiert. Warum der Entwickler? Asserts sind ein Debugging Konzept und nicht für den Release Modus bestimmt. Eine Assertion ist viel mehr ein Hinweis als ein Fehler.
Was ist mit Secret gemeint?
Mit Secret (zu Deutsch: Geheimnis) meine ich das verschweigen von Fehlern. Wenn eine Methode/Funktion nicht funktioniert, kann sie zum Beispiel den Wert false zurückliefern um anzuzeigen, dass etwas schief gelaufen ist. Es könnten aber auch Fehlercodes in Form von Ganzzahlen zurückgegeben werden. Nehmen wir als Beispiel die “CreateWindowEx”-Funktion der WindowsAPI. Im Fehlerfall gibt diese NULL zurück, und der Fehler kann über die Funktion GetLastError() abgerufen werden. Die Fehlerbehanldung wird somit komplett an den Aufrufenden übergeben. Werden die Rückgabewerte nicht verarbeitet, so passiert nichts und das Programm fährt fort. Der Fehler wird somit verschwiegen und es treten merkwürdige Ergebnisse auf (z. B. wie in diesem Fall: Ein Fenster wird nicht angezeigt).
Beispiele für Secrets: Frühere PHP Versionen, C/C++ und Windows API
Anwendungsbereiche
Als Faustregel sollte man Exceptions nur verwenden wenn man Methoden ausserhalb des Programms z. B. einer externen Bibliothek aufruft.
Es geht hierbei nicht um “fremden” Quellcode. Es ist viel mehr eine Art Verantwortungsbereich. In einem Industrieunternehmen teilt der Abteilungsleiter dem anderen auch nicht mit, dass sich Herr Christian W. aus Abschnitt B, Halle 1 gestern Vormittag den Fuß verstaucht hat weil ihm eine Kiste auf den Fuß gefallen ist (ausser vielleicht am Mittagstisch). Regulär ist es aber für die Produktion unerheblich. Interessant wird dagegen wenn der genannte Herr nicht mehr arbeiten kann und die Produktion dadurch zum erliegen kommt. Und genau hier fliegt die Exception. Ein Modul hat die Verantwortung richtig zu funktioneren. Kann es das nicht, sollte dies über eine Exception dem Aufrufer mitgeteilt werden. Alle internen “Problemchen” des Moduls, welche die Funktionalität nicht beeinträchtigen interessieren den Aufrufer nicht. Folglich sollten diese “Problemchen” über Asserts behandelt werden.
Secrets, (k)eine Alternative
Eine “Alternative” zu dem oben genannten Anwendungsbereich sind die Secrets. Ich empfehle diese aber nur zu verwenden, sofern die angewendete Programmiersprache kein Exception-Modell unterstützt. Eine aufgerufene Methode aus einer fremden Bibliothek könnte beispielsweise einen Boolean zurückliefern; true wenn alles gut lief; false wenn ein Fehler auftrat. Bis auf den Performancegewinn ergeben sich mit diesem Verfahren nur Nachteile:
- Jede aufrufende Methode im Callstack muss jeden Rückgabewert der von ihr aufgerufenen Methode verarbeiten können bzw. an die obere Methode weiterleiten. Bei Exceptions funktioniert das automatisch, in dem die Ausnahme an die obere Methode weitergereicht wird.
- Fängt eine Methode den Rückgabewert nicht ab bzw. verarbeitet diesen, wird der Fehler verschluckt. Das fehlerhafte Verhalten wirkt sich aber dennoch auf die Anwendung aus. Durch diese Problematik können die merkwürdigsten Verhaltensweisen des Programms auftreten ohne das eine genaue Struktur des Fehlers ensteht. Das erschwert die Problemsuche erheblich.
- Exception Handling wird von den meisten Entwicklungsumgebungen unterstützt und vereinfacht das Debugging erheblich.
Fazit
Prinzipiell ist es wichtig, wie mit allen Werkzeugen zweckmässig und Verantwortungsvoll umzugehen. Exceptions sind mächtig doch auch mit Vorsicht zu genießen. Ein gesunder Mix aus Assertions, welche im eigenen Scope verwendet werden, und Exceptions welche Scopeübergreifend arbeiten sollte wohl das Beste aus den Befehlen rausholen. Secrets sollte man aufgrund der fehlenden Code-Transparenz komplett meiden.