Exception Handling

Facade Pattern

Mit dem Facade Pattern soll ein positives Design Pattern vorgestellt werden. Weiterhin wird gezeigt, wie das Facade Pattern auf Exceptions übertragen werden kann. Nicht selten werden von Methoden mehrere Checked Exceptions geworfen, besonders wenn  in der Methode einige andere „umgebungsintensive“ APIs benutzt werden. Will man beispielsweise eine XML-Datei von einer URL laden, parsen und die enthaltenen Daten in einem eigenen Objekt zurückgeben, so bekommt man es mit mindestens einer IOException, SAXException und einer ParserConfigurationException zu tun. Da die Daten auch semantisch fehlerhaft sein können, kommen eventuell noch weitere Exceptions hinzu. Was ist notwendig, um dem Benutzer des APIs wenige, aber aussagekräftige Fehlermeldungen zu liefern?

Die SQLException versteckt beispielsweise viele interne Fehler in einer einzigen Exception. Das hat sicherlich den Vorteil, dass der Code recht einfach bleibt, aber sobald man eine SQLException fängt und den Fehler im Detail behandeln will, wird es problematisch. Die SQLException liefert mit getErrorCode() einen herstellerspezifischen Fehlercode und mit getSQLState() einen Fehlertext nach (X/Open SQLState Convention). Aber wer garantiert, dass getErrorCode() oder getSQLState() auch mit der nächsten Java-Version, bzw. mit der nächsten Version der Datenbanksoftware, auch die gleichen Codes und Meldungen liefert?
Das Konzept der SQLException ist zwar im Ansatz ganz praktisch, aber im Detail sehr unsicher, nicht zuletzt, da man das Feature der Compiler-Überprüfung verliert – denn der Compiler erkennt falsch geschriebene Exceptions und reagiert mit einem Compilerfehler.

Sinnvoller ist es, die Ausnahmen mithilfe von typsicheren Konstanten, bzw. Enumerations (ab Java 5), zu definieren:

public class PluginInitialisationException extends Exception {

  public enum PluginInitialisationError {
    PLUGIN_LIFECYCLE ("Plugin lifecycle has ended"),
    ILLEGAL_ACCESS ("Illegal access while creating the plugin instance"),
    INSTANTIATION ("Error while creating the plugin instance"),
    CLASS_CAST ("Plugin class cannot be cast"),
    CLASS_NOT_FOUND ("Plugin class not found");
    
    private String message;
    
    private PluginInitialisationError(String message) {
      this.message = message;
    }
  }
  
  private PluginInitialisationError error;
  
  public PluginInitialisationException(PluginInitialisationError error) {
    this.error = error;
  }
  
  public PluginInitialisationException(PluginInitialisationError error, Exception cause) {
    super(error.message);
    this.error = error;
    this.initCause(cause);
  }
  
  public String getErrorMessage() {
    return error.message;
  }
}

Durch diese Art der Fehlercodierung liefert man dem API-Benutzer nur eine einzige Exception, ohne aber die gewünschten Details und das Feature der Compiler-Prüfung zu verlieren.

Kontrakte

Kontrakte sind die Vorbedingungen, die bei einem Methodenaufruf eingehalten werden müssen. Um sich und den Benutzern eines APIs das Leben einfach zu machen, sollte jede API-Methode diese Bedingungen dokumentieren und prüfen. Falls ein API-Benutzer gegen einen Kontrakt verstößt, reagiert man mit einer Exception, um ihn zur Einhaltung des Kontrakts zu zwingen.

Sollen Kontraktverletzungen mit Checked oder Unchecked Exceptions signalisiert werden? In der Java-Welt hat sich folgende allgemeine Unterscheidung durchgesetzt: Wann immer der Anwender die Möglichkeit hat, einen Fehler zu kompensieren, implementiert man eine Checked Exception. Dies sind oft Fehler aus der Programmumgebung, z.B. eine IOException beim Speichern einer Datei (Datei existiert nicht, fehlende Rechte, kein Speicher verfügbar). Hier kann der Anwender eine andere Datei oder anderen Speicherort wählen, oder für genügend Platz auf der Platte sorgen. Im Gegensatz dazu kann der Anwender nicht für Programmierfehler wie das Überschreiten eines Listen- oder Array-Indexes verantwortlich gemacht werden. Diese gehen eindeutig auf die Rechnung des Programmierers und werden von der JVM mit einer RuntimeException beantwortet. Zu den Programmierfehlern gehört eben auch die Nichteinhaltung eines Kontrakts. Kontraktprüfungen können sehr umfangreich werden und produzieren dann eine große Menge an Exceptions. Es bietet sich daher an, die Kontraktverletzungen mit Unchecked Exceptions zu behandeln, um den Benutzer, bzw. seinen Programmcode, nicht mit unnötigem Exception Handling zu belasten.

Das Java API bietet schon von Haus aus einige nützliche RuntimeExceptions, z.B. die NullPointerException, IllegalArgumentException oder IllegalStateException an, mit denen man einen großen Teil der Fälle abdecken kann. Auch bei RuntimeExceptions und erbenden Klassen lässt sich das Facade Pattern gewinnbringend einsetzen.

Kommentare

Schreibe einen Kommentar

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