Exception Handling – Exceptions nicht zur Ablaufsteuerung missbrauchen – Teil 1

22.03.2023

Ein fiktiver Dialog, der ganz praktisch erklärt, warum Exceptions nicht zur Ablaufsteuerung missbraucht werden sollten.

John: Hey, ich habe gehört, dass man Exceptions nicht zur Ablaufsteuerung verwenden sollte. Warum ist das so?

Marc: Ja, das stimmt. Denn dies kann zu ineffizientem Code und unerwartetem Verhalten führen.

John: Wie kann das passieren?

Marc: Jedes Mal, wenn eine Ausnahme auftritt, muss ein Stack-Trace erzeugt werden. Dies kostet Zeit.

John: Okay, aber ich dachte, man soll nicht vorzeitig optimieren und lieber zunächst auf verständlichen Code achten (Vermeide vorzeitige Optimierung)?!

Marc: Das stimmt schon, aber welche Vorteile hinsichtlich Verständlichkeit sollte es denn bieten, wenn man Exceptions zur Ablaufsteuerung verwendet?

John: Hm… auch wieder wahr.

Marc: Exceptions zur Ablaufsteuerung zu verwenden, führt sogar zu schwer verständlichem Code. Schau dir mal dieses Code-Beispiel an …

public int lengthPlus(String str) {
   int len = 2;
   try {
      len += str.length();
   } catch (NullPointerException e) {
      log.info("argument was null");
   }
   return len;
}

… und vergleiche es mit folgendem:

public int lengthPlus(String str) {
   int len = 2;
   if (str != null) {
      len += str.length();
   } else {
      log.info("argument was null");
   }
   return len;
}

Welches liest sich besser?

John: Hm… ich würde sagen das Zweite. Es drückt klar aus, dass ein Null-String ein valider Fall ist. Das erste dagegen ist verwirrend. Es stellt den Null-Fall als Ausnahme dar und man fragt sich, warum diese dann einfach ignoriert wird.

Außerdem fällt mir gerade folgendes auf: Wenn die eigentliche Businesslogik im try-Block mehr als ein Einzeiler wäre, wäre nicht direkt ersichtlich, wozu die NullPointerException gehört. Dann ist es nicht nur schwer verständlich, sondern auch gefährlich. Denn die NullPointerException könnte vielleicht auch an anderer Stelle in der Businesslogik auftreten, an der es sich wirklich um einen Fehler handelt, und würde dann fälschlicherweise ignoriert.

Marc: Genau. Wenn der Null-Fall wirklich einen Fehler darstellt, dann sollte gleich zu Beginn die Ausführung mit einer Exception abgebrochen werden (Throw early-Prinzip).
Dies kann z.B. mit Objects.requireNonNull geschehen:

public int lengthPlus(String str) {
   Objects.requireNonNull(str);
   return 2 + str.length();
}

Update: Sofern die NullPointerException früh im Code auftritt, ohne dass zuvor Seiteneffekte geschehen, ist es besser, die NullPointerException einfach geschehen zu lassen, denn mittlerweile liefert die JVM hier eine bessere Fehlermeldung. Siehe dazu folgende Videos von Adam Bien: Don’t throw NullPointerExceptions, Just Let Them Happen, Better (Helpful) NullPointerException Output with Java 17

John: Konkreter Fall: Eine Datei kann nicht gefunden werden. Hier würde man eine Exception einsetzen, oder?

Marc: Kommt drauf an…

John: Worauf?

Marc: Ob der Fall auch tatsächlich ein Fehlerfall ist, der den normalen Ablauf des Programms unterbrechen sollte. Das folgende (stark vereinfachte) Stück Code ist mir in einem unserer Projekte begegnet:

public File findFile(String path) throws FileNotFoundException {
   ...

   if (file == null) {
      throw new FileNotFoundException(...);
   }

   return file;
}

Das Problem war hier, dass der Aufrufende nicht selbst entscheiden konnte, ob es sich um einen Fehlerfall handelt, wenn die Datei nicht gefunden werden konnte. Irgendwann wurde die Methode dann an einer anderen Stelle wiederverwendet, wo die Nichtexistenz der Datei einen validen Fall darstellte. Also wurde die FileNotFoundException zur Ablaufsteuerung missbraucht und abgefangen. Es wurde lediglich geloggt, dass die Datei nicht existiert. Irgendjemand fügte dann zum Logging die Ursache hinzu. Normalerweise ist dies auch eine gute Idee, die Ursache (Cause) zu loggen. Ansonsten tut man sich schwer, Fehler einer Codestelle zuzuordnen. Doch hier führte es dazu, dass im Logfile viele FileNotFound-Stacktraces auftraten, die tatsächlich gar keine Fehler darstellten.

John: Oh ja, Fehleranalysen in einem solchen Logfile machen keinen Spaß…

Marc: Richtig. Deswegen haben wir es wie folgt umgebaut:

public Optional findFile(String path){
   ...
   return Optional.ofNullable(file);
}

So kann der Aufrufer nun selbst entscheiden.

Exceptions sollten wir wirklich nur dann verwenden, wenn ein Ereignis vorliegt, welches das normale Verhalten des Programms unterbrechen muss. Alles andere ist verwirrend, fehleranfällig, und kann zudem die Performance beeinträchtigen.

John: Okay, vielen Dank für die Erklärung!

Quellen / Verweise

Bildnachweis:
– Das Titelbild zur Blogserie „Exception Handling“ basiert auf einer Grafik von mohamed_hassan auf Pixabay.

Zurück zur Übersicht

Kommentar verfassen

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

*Pflichtfelder

*