Java optionals – Best Practices

Montag, 27.11.2017

Null-Referenzen sind aus der objekt-orientierten Welt nicht wegzudenken. Tony Hoare, auf den die Idee der Null-Referenz zurückgeht, nannte sie einen „Milliarden-Dollar-Irrtum“, der in den letzten Jahrzehnten zu unzähligen Fehlern, Schwachstellen und System-Abstürzen führte [Hoa1]. Anders ausgedrückt: NullPointerExceptions (NPEs) kosten die IT jährlich unglaublich viel Geld, Zeit und Nerven.

Optionals – die Lösung für einen Milliarden-Dollar-Irrtum?

An und für sich sind Null-Referenzen kein schlechtes Konzept – man muss schließlich auch die Abwesenheit eines Wertes darstellen können, jedoch bergen Null-Referenzen in unachtsamen Händen viele Gefahren: missverständliche APIs, Boilerplate Code (Null-Checks), unzählige NPEs.


Mit Java 8 wurde mit der Optional-Klasse eine potentielle Lösung für das Problem eingeführt: ein Container für Objekte über deren Lebenszyklus hinweg nicht durchgehend Existenz garantiert werden kann. Die Idee dahinter: indem ich einen Wert als Optional zurückgebe, kennzeichne ich damit, dass er möglicherweise nicht existiert und zwinge Aufrufende dazu, sich um diesen Fall zu kümmern. Dadurch werden viele mögliche NPEs verhindert.

Dies ist zwar grundsätzlich ein Schritt in die richtige Richtung, verführt jedoch auch zu Missbrauch und falscher Anwendung. Im Folgenden werden Szenarien mit typischen Fehlern bei der Verwendung von Optionals und jeweils Lösungsmöglichkeiten vorgestellt.

Beispiele für die falsche Nutzung von Optionals

Boilerplate Code wird durch Boilerplate Code ersetzt

Problem

Mit Boilerplate Code werden Ausdrücke bezeichnet, die sich häufig wiederholen, jedoch keinen größeren fachlichen oder technischen Zugewinn bringen. Ein Beispiel dafür ist der Null-Check:

Java Optionals











Hierbei handelt es sich um ein weit verbreitetes Szenario in dem Optionals eingesetzt werden, allerdings sehr häufig auf eine Art und Weise die keinerlei Verbesserung mit sich bringt:

Java Optionals


Die If-Klausel ist weiterhin vorhanden und mit dem mandatorischen Aufruf von get() auf dem Optional ist noch mehr Boilerplate hinzugekommen. Unterm Strich haben wir uns im Vergleich zu einem klassischen Null-Check verschlechtert.

Lösung
Stattdessen sollten die Methoden der Optional-Klasse genutzt werden, die direkt in Abhängigkeit von der Existenz des beinhalteten Objekts auf diesem arbeiten:

orElse

Java Optionals


Die Ausgabe auf der Konsole kann via ifPresent() ausgeführt werden:

ifPresent

Java Optionals

Die Nutzung von get() in Kombination mit isPresent() muss immer kritisch hinterfragt werden. In den allermeisten Fällen ist sie überflüssig und lässt sich durch map(), orElse() oder ifPresent() ersetzen.
Eine weitere Möglichkeit ist die Nutzung von orElseGet(). Diese Methode ist orElse() sehr ähnlich, nimmt aber einen Lambda-Ausdruck entgegen, der nur bei Bedarf ausgeführt wird. Im Gegensatz dazu werden Methoden, die orElse() übergeben immer ausgeführt (das Ergebnis wird aber nur verwendet, wenn das Optional nicht vorhanden ist):

orElseGet

Java Optionals


Wie man hier sehen kann, wurde die Methode fallbackMethod() bei der Nutzung von orElse() aufgerufen, obwohl der Inhalt des Optionals vorhanden war (das Ergebnis der Methode wird aber nur genutzt, wenn das Optional leer ist). Der Lambda-Ausdruck, der orElseGet() übergeben wird, wird stattdessen jedoch nur ausgewertet, wenn er wirklich benötigt wird. Hier kann Rechenzeit eingespart werden!

Optional-Flut

Problem

Da man meist nicht zu 100 Prozent sicher sein kann, ob ein Objekt über die Laufzeit einer Anwendung hinweg nicht vielleicht doch mal null gesetzt wird, machen wir einfach alle Methoden-Parameter und -Rückgabewerte zu Optionals. So können wir sicher sein, dass sich Aufrufende immer um die Möglichkeit eines Null-Pointers kümmern. NPEs gehören so der Vergangenheit an! Klingt doch genial, oder?

So einfach ist es leider nicht. Wer kann sich nicht an die lebhafte Diskussion um CheckedExceptions in der Java-Community erinnern? Anfangs als großartige Möglichkeit gefeiert, Entwickler/innen dazu zu zwingen sich um potentielle Fehlerszenarien zu kümmern, fristen CheckedExceptions heutzutage eher ein Schattendasein. Der Grund dafür ist unter anderem die Penetranz mit der sie sich durch die Schichten einer Software ziehen: „Oh, beim Aufruf dieser API muss ich mich um eine CheckedException kümmern… Ach ich werfe sie einfach weiter, soll sich doch jemand anderes damit rumschlagen“. So ist der Code irgendwann durchzogen von try/catch-Blöcken und throws-Anweisungen.

Ein ähnliches Problem besteht bei Optionals: der Umstand, dass diese Aufrufende zum handeln zwingen, ist Segen und Fluch zugleich: Segen, weil man die Gefahr von NPEs entschärfen kann; Fluch, weil die erzwungene Behandlung von Optionals den Code stark verunreinigen kann.

Lösung


Jede Nutzung von Optionals muss auf den Prüfstand gestellt werden:

Kann dieser Wert überhaupt jemals null sein?
Wenn ja, ist das überhaupt schlimm?
Wenn ja, können wir uns auch ohne ein Optional behelfen?
Gerade der 3. Punkt wird häufig übersehen. Zunächst ist nämlich jede Methode, die einen null-Wert zurück geben kann, in Frage zu stellen. Grundsätzlich ist das schlechtes API-Design und es muss geprüft werden, ob für den Fall eines nicht-vorhandenen Rückgabewertes z.B. ein leerer String o.ä. zurück gegeben werden kann.



Hier noch ein paar weitere Daumenregeln:

  • Collections von Optionals sind fast immer überflüssig. Stattdessen sollten Collections immer leer initialisiert werden bzw. Methoden leere Collections zurück geben.
  • Optionals sollten niemals als Übergabeparameter genutzt werden. Stattdessen sollten Methoden überladen werden: ist ein Parameter optional, wird eine weitere Signatur angeboten, in der er nicht vorhanden ist.
  • Klassen-Felder sollten niemals Optionals sein. Stattdessen sollte es im getter gewrappt werden:

Java Optionals


Die Empfehlung der Expert Group (EG) des JSR 335 (Java Specification Request zu Lambda Ausdrücken) lautet, Optionals nur als Return-Type zu nutzen. In der EG-Mailing-List wurde sogar vorgeschlagen, Optional in OptionalReturn umzubenennen [EG1].

Fazit

Zusammenfassend lässt sich sagen, dass Optionals eine wertvolle Ergänzung für das JDK sind. Allerdings müssen sie mit Bedacht eingesetzt werden. Als gute Lösung hat sich in meiner Erfahrung die Nutzung von Optionals an Domänen-Außengrenzen (als Rückgabewerte von Methoden) etabliert. Innerhalb einer Domäne sind sie oft gar nicht nötig, da man die internen Schnittstellen so designen kann, dass Null-Werte nicht bzw. nur äußerst selten überhaupt möglich sind.

Im Überblick:

  • nutzen Sie map(), orElse(), orElseGet(), oder ifPresent() statt isPresent()/get()
  • Collections von Optionals sind meist überflüssig
  • Optionals sollten nicht als Übergabeparameter oder Klassen-Felder genutzt werden
  • Entworfen wurden sie in erster Linie als Return-Type
  • Prüfen Sie Ihr API-Design: Null-Rückgaben können durch Alternativen ersetzt werden

Quellen

 

 


zurück zur Blogübersicht

Diese Beiträge könnten Sie ebenfalls interessieren

Keinen Beitrag verpassen – viadee Blog abonnieren

Jetzt Blog abonnieren!

Kommentare

Christian Nockemann

Christian Nockemann

Diplom-Wirtschaftsinformatiker Christian Nockemann arbeitet seit 2009 als IT-Berater und Software-Architekt bei der viadee IT-Unternehmensberatung. Sein Fokus liegt auf dem Design und der Entwicklung von Java-basierten Enterprise-Anwendungen. Eine besondere Bedeutung gibt er dabei Qualitätskriterien des Softwareerstellungsprozesses wie bspw. der Anwendung des Domain-Driven-Designs, dem sinnvollen Einsatz von Entwurfsmustern und der Einhaltung von Clean-Code-Richtlinien.

Christian Nockemann bei Xing  Christian Nockemann auf Twitter