Google Guava – Fehler vermeiden und sichtbar machen

Die schlimmsten Programmier-Fehler sind die, die keiner bemerkt. Fehler vermeiden ist daher ein wichtiger Grundsatz. Wichtigstes Hilfsmittel sind hier umfangreiche Tests. Aber das reicht eben nicht immer. Daher sollten Fehler auch gleich im Code der Anwendung selbst vermieden werden. Und für den Fall dass doch mal etwas zur Laufzeit schief geht, sollte das zumindest klar gemeldet werden und nicht irgendwo in einem catch-Block verschwinden. Google Guava kann hier helfen. Gleichzeitig hilft es auch dabei, den Umgang mit einem der größten Nerv-Faktoren von Java zu vereinfachen: der Unmenge an explizit abzufangenden Checked Exceptions.

Fehler vermeiden…

Parameter prüfen

Eine beliebte Fehlerquelle sind ungültige Parameter die an eine Methode übergeben werden. Im besten Fall führen die irgendwo zu einer Exception. Im schlimmsten Fall läuft die Methode aber durch und liefert ein falsches Ergebnis zurück.

Java bietet im wesentlichen drei Exceptions mit denen Methoden auf ungültige Parameter reagieren können:

  • NullPointerException Wird geworfen wenn ein Parameter null ist, aber eine gültige Referenz erwartet wurde. Wird meistens beim Zugriff auf den Parameter „aus Versehen“ geworfen.
  • IllegalArgumentException Wird geworfen wenn ein Parameter einen ungültigen Wert enthält. Beispielweise eine negative statt einer positiven Zahl.
  • IllegalStateException Wird geworfen wenn eine Methode zu einem unzulässigen Zeitpunkt aufgerufen wird. Beispielsweise wenn eine Stop- vor der Start-Methode aufgerufen wird.

Das ist soweit natürlich nichts neues. Meistens sind die Überprüfungen der Parameter auch trivial, könnten aber gerne mit weniger Schreibaufwand verbunden sein:

Drei Zeilen sind natürlich nicht viel, trotzdem geht es mit Guava auch einfacher und vor allem viel lesbarer:

Ähnliche Check-Methoden gibt es auch für die beiden anderen Exceptions.

Optionals statt null

Ein „Klassiker“ unter den Exceptions ist die NullPointerException. Sie tritt immer dann auf, wenn auf einen null-Wert statt eine Objekt-Instanz zugegriffen wird. Aber wie kommt es überhaupt dazu, dass ein Wert „aus versehen“ null ist? Der Grund ist, dass null häufig als Synonym für „nicht definiert“ oder „nicht gefunden“ verwendet wird. Fragen wir beispielsweise einen Personen-Dienst nach einer Person die dieser nicht kennt, antwortet er uns evtl. mit null (oder wirft eine Exception). Dumm nur, wenn wir direkt danach auf diesen Rückgabewert zugreifen und nicht beachtet haben, dass dieser ja auch fehlen könnte. Denn dann fliegt eine NullPointerException.

Google Guava bietet hier mit Optionals eine Alternative, die zwar nicht wirklich gut aber immerhin besser ist. Ein Optional ist ein Wrapper der um ein Objekt oder einen null-Wert gelegt wird. Durch diesen Wrapper hat man immer eine gültige Objekt-Instanz, selbst wenn der Inhalt null ist.

Bei Methodenaufrufen können weggelassene, optionale Parameter durch ein Optional statt null ersetzt werden. Methoden können statt null einfach ein leeres Optional zurückgeben. Die API von Optional hilft auf den Inhalt der Wrapper zuzugreifen, ohne dabei eine NullPointerException zu riskieren.

Optional hat keine Konstruktoren, dafür aber statische Methoden mit denen Instanzen erzeugt werden:

  • Optional.of(reference) erzeugt einen Optional-Wrapper dessen Inhalt garantiert nicht null ist. Wird die Methode mit einem null-Wert aufgerufen, fliegt eine Exception.
  • Optional.fromNullable(reference) akzeptiert als Wrapper-Inhalt sowohl Objekt-Instanzen als auch null-Werte. Damit eignet es sich vor allem für die Integration von fremden APIs.
  • Optional.absent() erzeugt ein leeres Optional und damit also einen Ersatz für null-Werte.

Zugegriffen wird auf ein Optional bzw. dessen Inhalt in erster Linie mit drei einfachen Methoden:

  • Optional.isAbsent() prüft ob das Optional einen Wert beinhaltet. Falls nicht, wird true zurückgegeben.
  • Optional.get() gibt den Inhalt des Optionals zurück, allerdings nur wenn es auch einen Inhalt gibt. Ist das Optional leer, wird eine Exception geworfen. Bevor .get() verwendet wird, sollte also mit .isAbsent() der Zugriff überprüft werden.
  • Optional.or(defaultValue) ist eine praktische Hilfsmethode, die entweder den Inhalt des Optionals zurück gibt oder, falls es keinen Inhalt gibt, den als Parameter mitgegebenen Default-Wert.

Wie man sieht, ist Optional ein kleines aber enorm praktisches Hilfsmittel. Wird es konsequent eingesetzt, sind NullPointerExceptions tatsächlich so gut wie ausgeschlossen und beschränken sich meistens auf die Interaktion mit fremdem Code.

Allerdings handelt man sich auch ganz entscheidende Nachteile ein: der Schreibaufwand steigt und die Lesbarkeit des Codes sinkt. Außerdem bremst Optional performance-kritischen Code durch den Overhead der zusätzlichen Wrapper-Objekte aus.

Damit bleibt es Abwägungs- und Geschmackssache ob man Optional verwendet. Sie funktionieren zwar, kommen aber an den eleganten Umgang mit null-Werten den diverse neue Programmiersprachen nativ mitbringen nicht annähernd heran. In einem späteren Blog-Artikel werde ich mich damit und mit weiteren Alternativen zur Vermeidung von NullPointerExceptions noch genauer auseinandersetzen.

 

…und notfalls richtig behandeln

Falls aber doch irgendwo eine Exception auftritt, sollte diese zumindest richtig behandelt werden. Java setzt dabei in erster Linie auf Checked Exceptions, also Exceptions die von einem try-catch-Block abgefangen werden müssen. Im Gegensatz dazu müssen Unchecked Exceptions nicht abgefangen werden. In Java sind dies jedoch fast ausschließlich Exceptions die für unerwartete Fehler, etwa wieder NullPointerExceptions, stehen und jederzeit geworfen werden können.

Vom Konzept der Checked Exceptions kann man halten was man mag (möglichst nichts Gutes), der Umgang damit fängt aber schnell an nervtötende Routine zu werden. Denn meistens sieht es so aus, dass Exceptions sowieso nicht behandelt werden können, da sie einen Fehlerfall darstellen der das weitere Ausführen des Programms unmöglich macht. Daher folgt die Fehlerbehandlung meistens dem Mantra „loggen und weiterwerfen“, was dann meistens so aussieht:

Der Nachteil an dieser Vorgehensweise ist, das nun die Methode die in ihr möglicherweise auftretende Exception mit throws SomeException selbst als Checked Exception deklariert hat. Damit müssen alle anderen Methoden die doStuff() aufrufen, ebenfalls die gleiche Fehlerbehandlung implementieren. Das kann sich über mehrere Ebenen nach oben fortsetzen.

Einfacher ist es, aus der Checked eine Unchecked Exception zu machen. Denn das etwas schiefgegangen ist, wurde ja bereits protokolliert und viel mehr lässt sich nicht machen. Die Exception kann daher im Call Stack nach oben durchgeworfen, bis sich üblicherweise ein Exception Handler oder etwas ähnliches darum kümmert und beispielsweise einen Fehlerdialog anzeigt. Dazu wird eine Runtime-Exception benötigt.

Mit Google Guava geht das Umwandeln in eine Runtime-Exception angenehm schnell:

Throwables.propagate(exception) verpackt die Exception in eine Runtime-Exception (falls es nicht bereits eine ist), die dann problemlos geworfen werden kann.

Veröffentlicht in Allgemein, Java, Softwareentwicklung Getagged mit: , , , , ,

Schreibe einen Kommentar

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

*

* Copy This Password *

* Type Or Paste Password Here *