Befreit die APIs von Nullen

In vielen Schnittstellen werden noch immer null Werte zurück geliefert. Das führt zu aufwendigen Code in der eigenen Anwendung, da alle Rückgabewerte auf null geprüft werden müssen.

Person me = gedcom.findPerson(name);
if (me != null) {
  Person father = me.getFather();
  if (father != null) {
    return father.getName() != null || father.getName().isEmpty();
  }
}
return false;

Dieses Beispiel prüft, ob der Name des Vaters gesetzt ist und nicht leer ist. Dabei müssen die Rückgabewerte zwei mal geprüft werden, da sonst ggf. eine NullPointerException geworfen wird.

Um den Code lesbarer zu gestalten gibt es zwei Lösungen, das Null Design Pattern oder der Einsatz der Optional Klasse aus Java 8.

Null Design Pattern

Bei dem Null Design Pattern wird nicht null zurückgeliefert, sondern ein explizites Objekt für nicht vorhandene Werte. Die getFather() und die findPerson() Methoden liefern also immer ein Objekt zurück. Bei fehlenden Werten wird ein ein spezielles Null Objekt zurückgeliefert. Unser obiges Beispiel mit Null Objekten, liest sich dann wie folgt.

return gedcom.findPerson(name).getFather().getName().isEmpty();

Da alle Methoden einen Wert liefern, können wir sie direkt hintereinanderschalten. Wir haben auch die getName() Methode so geändert, dass sie entweder einen Namen oder einen leeren String zurück liefert. Für Namen ist der leere String ein ideales Null Objekt.

Es gibt verschiedene Möglichkeiten um Null Objekte für die Person Klasse zu realisieren. Entweder erstellt man Subklassen für die eigentlichen Werte (RealPerson) und Null (NullPerson) oder man erstellt eine Konstante vom Typ Person. In diesem einfachen Fall ein Hybrid aus beiden.

private static Person NULL = new Person() {
  public String getName() {
    return "";
  }
  public Person getFather() {
    return this;
  }
}

Damit wir prüfen können, ob eine Person tatsächlich existiert oder ein Null Objekt ist, benötigen wir noch eine entsprechende Methode.

public static boolean exists(Person person) {
  return person != NULL;
}

Damit können wir dann auch eine Methode schreiben, die den ältesten bekannten Vorfahren findet.

public static Person primalKnownAnchestor(Person person) {
  Person anchestor = person;
  while (Person.exists(anchestor.getFather())){ 
    anchestor = anchestor.getFather();
  }
  return anchestor;
}

Optional Klasse

Die Optional Klasse kapselt Werte einer anderen Klasse und bietet damit eine weitere Möglichkeit, optionale Werte nicht als null zu repräsentieren.

Optional<Person> me = gedcom.findPerson(name);
Optional<Person> father = me.map(p -> p.getFather());
Optional<String> name = father.map(p -> p.getName());
Optional<String> notEmptyName = name.filter(String::isEmpty);
return notEmptyName.isPresent();

Dieses Beispiel hätte auch in einer Zeile geschrieben werde können, aber hier sind, zur Verdeutlichung, alle Zwischenergebnisse notiert. Die Methode findPerson() liefert in diesem Beispiel immer ein Objekt vom Typ Optional<Person> zurück, dass einen Wert vom Typ Person enthalten kann. Die folgenden Aufrufe der Methoden map() und filter() verarbeiten den im Optional gespeicherten Wert, wenn er existiert. Am Ende wird geprüft, ob in der Variable notEmptyName ein Wert gespeichert ist. Denn dieser kann durch die filter Methode entfernt worden sein. Verkürzt geschrieben sieht unser Beispiel wie folgt aus.

return gedcom.findPerson(name).map(p -> p.getFather()).map(p -> p.getName()).filter(String::isEmpty).isPresent();

In diesem ersten Beispiel wurde nur die findPerson() Methode geändert. Wir haben also immer noch eine getFather() Methode die null zurück liefern kann. Ändern wir nun die API so, dass auch die getFather() Methode ein Optional<Person> zurück liefert, dann sähe unser Beispiel wie folgt aus.

return gedcom.findPerson(name).flatMap(p -> p.getFather()).map(p -> p.getName()).filter(String::isEmpty).isPresent();

Wir müssen die Methode flatMap() verwenden, das wir sonst Zwischenwerte vom Typ Optional<Optional<Person>> erhalten und das wäre dann doch zu optional. Unsere Methode für den ältesten bekanntesten Vorfahren können wir jetzt aber auch mit Optional schreiben.

public static Person primalKnownAnchestor(Person person) {
  Person anchestor = person;
  while (anchestor.getFather().isPresent()) { 
    anchestor = anchestor.getFather().get();
  }
  return anchestor;
}

Die get() Methode, liefert den Wert der im Optional gespeichert ist oder wirft eine Exception. Da wir aber die Existenz des Wertes zuvor prüfen, können wir gefahrlos auf den Wert zugreifen.

Fazit

Welchen Ansatz man auch wählt, beide sorgen dafür, dass der eigene Code aufgeräumter und kompakter geschrieben werden kann. Persönlich gefällt mir das Null Pattern mit seinen kompakteren Code besser, aber die Verwendung von Optional als API Rückgabewert, vereinfacht das Verständnis von Schnittstellen ungemein. Es muss nicht mehr dokumentiert werden, dass ein Aufruf nicht immer ein Ergebnis liefert. Der Typ des Rückgabewertes verrät es schon.