Ausnahmebehandlung
aus Wikipedia, der freien Enzyklopädie
Eine Ausnahme oder Ausnahmesituation (engl. exception) bezeichnet in der Computertechnik ein Verfahren, Informationen über bestimmte Programmzustände – meistens Fehlerzustände – an andere Programmebenen zur Weiterbehandlung weiterzureichen.
Kann in einem Programm beispielsweise einer Speicheranforderung nicht stattgegeben werden, wird eine Speicheranforderungs-Ausnahme ausgelöst. Ein Computerprogramm kann zur Behandlung dieses Problems dafür definierte Algorithmen abarbeiten, die den Fehler ignorieren, beheben oder anzeigen.
Sachgemäße Anwendung von Ausnahmen ermöglicht es Programmen, flexibler auf unvorhergesehene Probleme zu reagieren und sich fehlertoleranter zu verhalten.
Inhaltsverzeichnis |
[Bearbeiten] Strukturierte Ausnahmebehandlung
Die größte Rolle in der Programmierung spielt dabei die strukturierte Ausnahmebehandlung, eine Technik, die den Code zur Ausnahmebehandlung vom normalen Anwendungscode getrennt hält. Anstatt beispielsweise bei jedem Funktionsaufruf einen Rückgabewert, der den Erfolg anzeigt, zu prüfen und darauf zu reagieren, kann man die betreffende Funktion in Ausnahmesituationen eine Ausnahme auslösen lassen, die alle für die Problemerkennung und -behandlung erforderlichen Informationen in sich trägt.
Da die verursachende Funktion (oder die Funktion, die das Problem feststellt) in ihrem Kontext den Fehler möglicherweise nicht angemessen behandeln kann, wird die Exception so lange an aufrufende Funktionen zurückgereicht, bis schließlich eine die Exception „fängt“. Dies baut man sinnvollerweise an einer Stelle im Code ein, wo abzusehen ist, was für Konsequenzen diese Ausnahme haben kann und wie man darauf am besten reagiert (zum Beispiel neue Benutzereingabe, Programmabbruch, Abbruch einer bestimmten Operation). Außerdem ist jedweder Code-Abschnitt, der bezüglich der Ausnahmebehandlung nichts ausrichten kann, frei von Fehlerbehandlungsroutinen.
Ein weiterer entscheidender Vorteil gegenüber der Fehlerbehandlung über Rückgabewerte ist, dass eine Exception nicht ignoriert werden kann. Ein Programmierer könnte vergessen, einen Rückgabewert zu prüfen, aber eine Exception wird immer weiter zurückgereicht, bis sie in der Programmstartenden Funktion ankommt, falls der Programmierer vergisst, sie aufzufangen. In diesem Fall führt das oft zu einem Programmabbruch. Dies mag etwas subtil erscheinen, das Resultat ist jedoch meistens, dass Programme, die trotz Fehlerbehandlung per Exceptions stabil laufen, auch wirklich stabil laufen. Demgegenüber kann das Ignorieren eines Fehlerwertes viele Male keine drastischen Konsequenzen haben und irgendwann könnte das Programm eventuell doch unerwartete Ergebnisse produzieren.
Programmiersprachen, die Ausnahmebehandlung unterstützen, sind zum Beispiel C++, Delphi, Java, C#, Python, PHP (ab Version 5), Eiffel, Ada und Visual Basic .Net.
Verschiedene Hardware-Architekturen (wie zum Beispiel die IA-32-Architektur von Intel) unterstützen eine Exception-Behandlung auf Hardware-Ebene durch das Betriebssystem. Hierbei werden bei bestimmten ungültigen Operationen Software-Interrupts ausgelöst, die einen Einsprung in den privillegierten Betriebssystemkern verursachen. Dieser kann dann anhand der Exception das Programm mit einer Fehlermeldung beenden oder den Fehler an einen Debugger weiterleiten.
[Bearbeiten] „checked“ Exceptions
Java hat hinsichtlich der Ausnahmebehandlung eine besondere Finesse: Der Compiler prüft mit, ob alle Ausnahmen, die jemals geworfen werden könnten, auch wirklich behandelt werden, bzw. zwingt den Programmierer dazu, das explizit anzugeben, wenn er eine Exception nicht fangen will. Daher ist es in Java kaum noch möglich, dass eine Exception ein Programm unerwartet zum Absturz bringt. Beispiel: Das Öffnen einer Datei kann aus verschiedenen Gründen schiefgehen (keine Rechte, Datei nicht vorhanden). Der Programmierer muss explizit angeben, was geschehen soll, wenn eine Datei nicht geöffnet werden kann. Ausgenommen hiervon sind Ausnahmetypen, die jederzeit auftreten können, wie zum Beispiel Indexfehler bei Array-Indizierung. Diese sind meistens die Folge von Fehlern im Programm, so dass es hier nicht sinnvoll ist, die Ausnahme zu behandeln (statt dessen sollte man den Fehler beheben).
Dieses System ist umstritten. Gegner des Systems kritisieren häufig, dass dadurch Änderungen schwerer möglich bzw. unmöglich sind. Man könne das Verhalten (bzgl. der geworfenen Ausnahmen) einer Methode nicht mehr einfach ändern, da man damit die Signatur, also die Schnittstelle der Methode verändere. Client-Code, der diese Methode dann nutze, würde sich nicht mehr kompilieren lassen. Demgegenüber argumentieren Verfechter dieses Systems, dass das Brechen mit Client-Code ohne „checked“ Exceptions genauso stattfinden würde. Anstatt eines Compilerfehlers würde dann eine ungefangene Ausnahme das bisher abgesicherte Programm gefährden und das möglicherweise sogar unbemerkt.
[Bearbeiten] Auslösen von Exception
Eine Exception kann an jeder Stelle im Programmcode ausgelöst werden. Dabei wird fast immer ein Objekt einer Exceptionklasse erzeugt und mit dem Schlüsselwort throw
oder raise
abgeschickt. Bei manchen Programmiersprachen (zum Beispiel C++) darf statt der Exceptionklasse auch jeder andere Datentyp verwendet werden.
[Bearbeiten] Abfangen von Exceptions
Wird eine Exception im Programmablauf nicht explizit abgefangen, dann wird sie von der Laufzeitbibliothek aufgefangen. Die Exception wird als Fehlermeldung angezeigt; je nach Art der Exception wird die Anwendung abgebrochen oder fortgesetzt. Anwendungen ohne eigene Benutzeroberfläche werden immer abgebrochen.
Häufige Fehler bei der Ausnahmebehandlung sind
- Exceptions werden ohne weitere Aktionen geschluckt. Somit geht die eigentliche Fehlerursache verloren.
- Exceptions werden durch eine eigene (häufig unzutreffende) Meldung ersetzt.
Es ist sinnvoll, Exceptions abzufangen, um zusätzliche Informationen anzureichern und erneut auszulösen.
ein Beispiel für Delphi
try ErstelleObjektA; BerechneEinkommen(name); except on E:Exception do begin // Exception wurde abgefangen und wird um einen aussagekräftigen Hinweis ergänzt E.Message := 'Fehler beim berechnen des Einkommens von '+name+#13#10+ E.Message; // ursprüngliche Meldung anhängen Raise; // veränderte Exception erneut auslösen end; finally EntferneObjektA; //dies wird auf jeden Fall ausgeführt end;
ein Beispiel für C++
// Probiere was aus try { funktion1(); funktion2(); ... } catch (std::invalid_argument &e) { std::cerr << "Falsches Argument:" << e.what() << std::endl; } catch (std::range_error &e) { std::cerr << "Ungültiger Bereich:" << e.what() << std::endl: } catch (...) { std::cerr << "Sonstige Exception" << std::endl: };
ein Beispiel für Visual Basic.NET
' Versuche ... try ' ... Die methode oder prozedur ... BerechneEinkommen(name) ' bei ausnahme catch e AS Exception ' gib Ausnahme aus MessageBox.Show("Fehler -> " & e.message) ' Führe auf jeden fall aus finally MessageBox.Show("Das wird trotzdem ausgeführt") end try
ein Beispiel für Java
try { //Berechne ... } catch (RuntimeException e) { //z.B. IndexOutOfBoundsException, NullPointerException usw. System.err.println("Offensichtlich ein Programmierfehler!"); throw e; //Leite nach oben weiter } catch (Exception e) { //Fange alle restlichen Ausnahmefehler ab e.printStackTrace(); } catch (OutOfMemoryError e) { //Ein Error ist keine Exception und muss separat abgefangen werden e.printStackTrace(); } catch (Throwable t) { //Das hier fängt wirklich alles ab t.printStackTrace(); } finally { //Ob Exception oder nicht, führe das hier auf jeden Fall aus. System.out.println("Berechnung beendet oder abgebrochen"); }
[Bearbeiten] Technische Umsetzung
In nativ compilierten Sprachen wie C++ wird meistens Code generiert, der zur Laufzeit Informationen zur Ausnahmebehandlung protokolliert. In C++ werden „abgesicherte“ Bereiche von einem try-Block umfasst. Jeder Eintritt in und Austritt aus einem try-Block wird auf einer dafür vorgesehenen Datenstruktur (meistens ein Stack) notiert. Wird eine Ausnahme ausgelöst, kann diese Datenstruktur nach einer passenden Fehlerbehandlung durchsucht werden und der Kontrollfluss so lange aufgerufene Funktionen verlassen, bis er den ausgewählten Handler erreicht.
Bei vielen Virtuellen Maschinen wie zum Beispiel JVM oder .NET kann auf den Overhead des ständigen Protokollierens verzichtet werden, da der Bytecode bei Auftreten einer Ausnahme nach einem passenden Handler abgesucht werden kann. Somit verursachen nur noch tatsächlich ausgelöste Ausnahmen Overhead und nicht mehr jede mögliche.