Zum Hauptinhalt

Fehlerbehandlung

Gundlagen

Beim Programmieren und beim Ausführen eines Programms können die verschiedensten Fehler auftreten. Einige dieser Fehler können bereits während des Programmierens von der IDE erkannt werden. Dies sind zum Beispiel Syntaxfehler oder ein Aufruf von einer Methode, die nicht deklariert wurde. Fehler, die die Entwicklungsumgebung nicht erkennen kann, sind beispielsweise Fehler in der Programmlogik, also wenn der Programmierer schicht das falsche Programm schreibt. Die dritte Klasse von Fehlern sind die Laufzeitfehler, also Fehler, die erst auftreten während das Programm ausgeführt wird. Ein Laufzeitfehler wäre zum Beispiel, wenn man versucht in eine Datei zu schreiben, für die das Programm keine Schreibberechtigung hat.

Jeder hat schon einmal einen nicht behandelten Laufzeitfehler erlebt. Dies äußert sich dadurch, dass das Programm einfach abstürzt. Damit Java-Programme nicht abstürzen müssen, gibt es ein Fehlerbehandlungskonzept, dass es dem Programmierer ermöglicht Ausnahmen, die sogenannten Exception, im Programmfluss zu “behandeln”. Er kann hierfür einen try-Block um kritische Funktionsaufrufe schreiben. Danach folgt ein catch-Block. Tritt während der Ausführung des Programms eine Exception auf, so stoppt die Ausführung an genau dieser Stelle wie bei einem Programmabsturz. Sie wird aber anschließend im catch-Block fortgesetzt.

Im folgenden Beispiel verzichten wir auf die Prüfung, ob die Datei aus der wir lesen wollen existiert. Wir lesen einfach. Wenn die Datei dann nicht existiert, bricht das Programm ab und schreibt eine Fehlermeldung und läuft danach normal weiter.


File datei = new File("existiert-nicht.txt"); try { Scanner scnr = new Scanner(datei); while (scnr.hasNextLine()) { System.out.println(scnr.nextLine()); } scnr.close(); } catch (Exception e) { System.out.println("Datei konnte nicht gelesen werden: " + datei.getAbsolutePath()); }

Wie das Beispiel zeigt, wird beim Catch-Block, wie bei einer Methodendeklaration auch, ein Parameter definiert. In die dabei definierte Variable e wird ein Objekt übergeben, welches Informationen über die Fehlerursache liefert. Eine Beschreibung des Fehlers kann mit der getMessage() Methode abgefragt werden.

Als weitere wichtige Komponente enthält das Exception-Objekt den sogenannten Stacktrace. Hierbei handelt es sich um eine Auflistung der Methoden-Aufrufe, die zu dem Fehler geführt haben. Das Konzept des Stacks funktioniert so, dass jedes Mal wenn die Ausführung einer Methode begonnen wird, ein Eintrag hinzugefügt wird. Sobald dann das Ende dieser Methode erreicht wird, wird der Eintrag wieder vom Stack entfernt. Man kann sich dies wie einen Stapel vorstellen, auf den immer oben ein Element drauf gelegt wird und immer nur das oberste Element wieder entfernt werden kann. Wenn ein Fehler auftritt, ist das oberste Element im Stack also der Ort an dem der Fehler aufgetreten ist. Das erste beziehungsweise unterste Element auf dem Stack ist immer die Main-Methode.

Es ist immer sinnvoll den Stacktrace auszugeben, da er sehr wichtig für die Fehleranalyse und Programmverbesserung ist. Dies geschieht am einfachsten mit der Methode printStacktrace() die zum Beispie die folgende Ausgabe erzeugen könnte:


 java.io.FileNotFoundException: existiert-nicht.txt (No such file or directory)
     at java.base/java.io.FileInputStream.open0(Native Method)
     at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
     at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
     at java.base/java.util.Scanner.<init>(Scanner.java:639)
     at test.Main.leseDatei(Main.java:21)
     at test.Main.main(Main.java:11)
Die Ausgabe des Stacktrace beginnt mit einer Zeile in der der Fehler benannt wird. Sie beginnt mit dem Namen der Exceptionklasse, der einen ersten Hinweis auf den Ursprung des Problems gibt. Danach folgt eine Nachricht, die für diese spezielle Fehlerursache hinterlegt wurde. In den nachfolgenden Zeilen wird dann jeweils ein Funktionsaufruf dargestellt. Meistens genügt es, sich auf Zeilen zu konzentrieren, die mit dem eigenen Code zu tun haben. Um diese zu finden sucht man einfach nach Zeilen mit dem eigenen Package. In diesem Beispiel das Package “test”. Von diesen Zeilen ist die Oberste meist die entscheidende. In dieser Zeile meint “test”, das Package. Mit “Main” wird die Klasse benannt, in der der Fehler aufgetreten ist und “leseDatei” ist die Methode. In Klammern folgt noch einmal der Klassenname und die Zeile des Aufrufs.

Wenn man diese Information richtig verstanden hat, kann man schnell die Ursache eines Fehlers finden und beheben. Die optimale Korrektur für unseren Fehler wäre, vor dem Zugriff auf die Datei zu prüfen, ob sie existiert und lesbar ist.

Eine allgemeine Antwort, wie Fehler korrekt behandelt werden gibt es leider nicht. Als Richtwert kann man sagen, dass es besser ist Fehler zu vermeiden als sie nachträglich zu behandeln. Bei allen anderen Fehlern muss man überlegen, ob man den Zustand des Programms wieder so korrigieren kann, dass es ein korrektes Ergebnis liefert und diesen wieder herstellen. Wenn dies nicht möglich ist, sollte man nur eine aussagekräftige, verständliche Mitteilung für den Nutzer ausgeben und dann das Programm beenden.

In einigen Situationen ist es nicht möglich eine Exception sinnvoll zu behandeln. Für diesen Fall sieht Java die throws-Erweiterung bei der Methodendeklaration vor. Wir hatten sie in der Vergangenheit schon öfter benutzt, damit der Compiler unsere Methoden akzeptiert.

Manchmal ist es sinnvoll, dass spezieller Code in jedem Fall ausgeführt wird, unabhängig davon, ob nun ein Fehler aufgetreten ist oder nicht. Hierfür kann nach oder statt dem catch-Block ein finally-Block geschrieben werden. Was darin steht, wird in jedem Fall am Ende der Fehlerbehandlung ausgeführt.


try { leseDatei(new File("datei.txt")); } catch(Exception e) { // Fehler wird behandelt } finally { aufraeumen(); }