Eine redundante Geschichte der Programmierung: Von Parnas bis Scala

Ausnahmebehandlung

In älteren Programmiersprachen (Fortran, Algol, Cobol, Pascal, C) gab es keine automatische Ausnahmebehandlung. Nach jedem Unterprogrammaufruf musste der Aufrufer manuell überprüfen, ob das Unterprogramm erfolgreich oder fehlerhaft terminierte. Erschwerend kam hinzu, dass es auch keine einheitliche Konvention gab, wie ein Unterprogramm seinem Aufrufer den Misserfolg mitzuteilen hätte. Bei den in C geschriebenen Unix-Diensten wurde sowohl ein Sonderwert des Funktionsergebnisses als auch die globale Variable errno eingesetzt. Nach einem Aufruf FILE* pFile = fopen (filename, „r“); zum Öffnen einer Datei musste man prüfen, ob errno einen Wert ungleich 0 hat. Da automatische Aufräummechanismen fehlten, konnte man festgestellte Fehler nicht im Arbeitsspeicher sammeln, sondern musste sie sofort melden. Das schränkte allerdings die universelle Verwendbarkeit eines Unterprogramms ein, da das Meldeziel dann nicht durch den Aufrufer wählbar war. Die korrekte Behandlung eines Funktionsaufrufs in C auf Unix, hier der Funktion fopen, sah also wie folgt aus:

FILE* pFile = fopen(filename, "r");
if(errno!=0){
    perror(filename); //prints errno and filename
    fprintf(stderr, "at file %s in line %dn", __FILE__, __LINE__);
    errno = FAILURE;
    return NULL; 
}

Man kann sich leicht vorstellen, dass saubere Fehlerbehandlung hochredundant war und den eigentlichen Algorithmus nicht mehr erkennen ließ. Zudem war sie so schreibaufwändig, dass die wenigsten Programmierer sie praktizierten. Zum Glück bot C mit Präprozessormakros ein Mittel, diese Redundanz teilweise zu beseitigen. So könnte man die Behandlung in Listing 2 von if bis return NULL;} in einen Makro auslagern, der mit dem Kontext und dem Funktionsergebnis im Fehlerfall zu parametrieren wäre:

#define ERRCHECK(context, failResult) ...

Der Aufruf von fopen könnte dann kürzer aussehen:

FILE* pFile = fopen (filename, "r");
ERRCHECK(filename, NULL)

Das konnte aber immer noch nicht das Versagen von solchen Funktionen lösen, die in Ausdrücken kombiniert werden, zum Beispiel f(x)*g(x), da ERRCHECK erst an der nächsten Anweisungsgrenze anwendbar war. Das was hier manuell durchexerziert wurde, leisten heutige Sprachen, wenn eine Funktion eine Ausnahme wirft. Die Standardbehandlung (Meldung mit Stack Trace und Programmabbruch) ist sichergestellt, eine individuelle Behandlung ist dennoch möglich. Automatische Ausnahmebehandlung wurde von Ada eingeführt. C++ übernahm sie erst spät, Java enthielt sie von Anfang an mit einem API-Zugriff auf den Stack Trace einer abgefangenen Ausnahme.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.