Befreit die APIs von Nullen

in vielen Schnittstellen werden noch immer null Werte zurückgeliefert. 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 wir zwei Mal die Rückgabewerte prüfen, da wir sonst ggf. eine NullPointerException erhalten können.

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 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ückliefert. Für Namen ist der leere String ein idealer Null Value.

Es gibt verschiedene Möglichkeiten um Null Values 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 Value 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ückliefern kann. Ändern wir nun die API so, dass auch die getFather() Methode ein Optional<Person> zurückliefert, 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.