Bad Smells beim Exception Handling

Im Buch „Der Weg zum Java-Profi“ von Michael Inden werden unter anderem häufige Unarten beim Exception-Handling beschrieben. Sieben dieser Beispiele werden im Folgenden vorgestellt:

• Unbehandelte Exceptions
• Unpassender Exception-Typ
• Exceptions zur Steuerung des Kontrollflusses
• Fangen der allgemeinsten Exception
• Throw/catch Throwable
• Rückgabe von null statt Exception
• Late Exceptions
• Unbehandelte Exceptions

Beispiel

try {
    //code
} catch (Exception e) {
    System.out.print("Fehler");
}

Erklärung
Fehler sollten dem Anwender kommuniziert oder wenigstens in einer Log-Datei festgehalten werden. Ist beides keine Option, sollten Exceptions so weit propagiert werden, bis eine sinnvolle Behandlung möglich ist.

Unpassender Exception-Typ

Beispiel

public static void fill(String path) {
 if(path == null)
       throw new NullPointerException("no path given");
 if(!new File(path).exists())
       throw new NullPointerException("path does not exists");
}

Erklärung
Der Exception-Typ sollte immer zum Fehler passen, sonst kann man evtl. nicht richtig darauf reagieren. In dem Beispiel ist es z.B. nicht einmal möglich, die beiden Fehler zu unterscheiden. Hier wäre eine FileNotFoundException besser gewesen.

Exceptions zur Steuerung des Kontrollflusses

Beispiel

public static int sum(int[] values) {
   int i = 0;
   int sum = 0;
   try {
       while (true) {
           sum += values[i];
           i++;
       }
   } catch (Exception e) {
       // ende des Arrays
   }
   return sum;
}

Erklärung
In diesem Beispiel wird der Exception-Mechanismus zum Steuern des Kontrollflusses missbraucht. Das ist zum einen sehr unüblich und wird die meisten Entwickler verwundern, zum anderen gehen dem catch auch andere Exceptions ins Netz, die evtl. auftreten.
Fangen der allgemeinsten Exception

Beispiel

} catch (Exception e) {
  // Code
}

Erklärung
Man sollte nicht unbedacht „Exception“ fangen, da sonst schwerwiegende Applikationsfehler verborgen bleiben können
throw/catch Throwable

Beispiel

public static void func() throws Throwable {
  try { }
  catch (Throwable e) { }
}

Erklärung
Man sollte Throwables weder fangen noch werfen, da sie Exceptions und Errors beinhalten.
Errors sind jedoch Fehler, bei denen das Programm nicht fortfahren sollte, z.B. VirtualMachineError (Virtual Machine is broken or has run out of resources necessary for it to continue operating). Errors sollte man nur fangen, wenn man z.B. einen eigenen Classloader implementiert und genau weiß, was man tut. Nur Exceptions sind behebbare Fehler und sollten behandelt werden.
Rückgabe von null statt Exception

Beispiel

public static Integer func(String zahl) throws Throwable {
  try {
      return Integer.valueOf(zahl);
  }
  catch (Exception e) {
      return null;
  }
}

Erklärung
Die Rückgabe von null erlaubt es, dem Aufrufenden Fehler einfach zu ignorieren. Tests finden evtl. keinen Fehler, weil sie gültige Parameter übergeben. In der Produktivumgebung kann es sich jedoch ändern und der Fehlerfall wird nicht behandelt. Der Zusammenhang einer Folge-Exception ist schwer nachzuvollziehen.

Late Exceptions

Beispiel

private Long number;
   public void setNumber(Long number) {
       this.number = number;
   }
   public Long getNumber() throws Exception {
       if(number==null)
       throw new Exception();
       return number;
   }
}

Erklärung
Eingabeparameter sollten gleich geprüft werden. Sie erst später zu prüfen und dann eine Exception zu werfen, ist irreführend. Man weiß hinterher nicht mehr, wodurch sie verursacht wurde.

Fazit
• Eigene Exception Klassen verwenden, wenn Fehler auftreten, die von der Business-Logik erkannt werden, um sie von anderen zu unterscheiden
• Nur behebbare Exceptions abfangen, die anderen propagieren
• Die meisten automatisch behebbaren Exceptions, wie Verbindungsabbrüche, nehmen uns Bibliotheken meistens schon ab
• Die vom Buch beschriebenen Fälle sind überzogen und kommen in der Praxis bei erfahrenen Programmierern fast nicht vor

Quellen
„Der Weg zum Java-Profi“ von Michael Inden