Ein Schritt zurück bringt uns voran

Wer sich schon einmal an einer Staffelei versucht hat, der weiß, wie wichtig es ist, hin und wieder einen Schritt zurückzugehen. Der Schritt zurück gibt dem Maler die Möglichkeit, sein Werk im Ganzen zu betrachten. Häufig verliert der Künstler bei den Detailarbeiten das Gesamtwerk aus den Augen. Dann kann es passieren, dass die verschiedenen Bildbereiche nicht mehr zueinander passen und das Werk misslingt. Durch das Innehalten und prüfen des Bildes arbeitet also auch der Künstler iterativ und kann den kreativen Prozess steuern.

Eine grundsätzliche Schwäche der Software Entwickler ist die sofortige Ausgestaltung einer technische Lösung für ein bestehendes Problem. Statt in einer ersten Analyse zu verstehen, welches Problem es zu lösen gilt, wird leider allzu häufig die technische Umsetzung im bestehenden Kontext besprochen.

Ob es sich dabei um ein ungünstiges Datenbankmodell, eine ineffiziente Kommunikation zwischen Teilsystemen oder um die Anpassung einer einzelnen Methode handelt, ist völlig egal.

Als Beispiel für das vorschnelle Handeln eines Programmieres, bei der Umsetzung einer Anforderung, soll folgende kurze Comparator Implementierung dienen.

Comparator<AncestorTree> dateCompare = new Comparator<AncestorTree>() {
  @Override
  public int compare(AncestorTree at1, AncestorTree at2) {
    if (at1.lastModifiedTime() != null) {
      if (at2.lastModifiedTime() != null) {
        return at1.lastModifiedTime().compareTo(at2.lastModifiedTime());
      } else {
        return at1.lastModifiedTime().compareTo(at2.creationTime());
      }
    } else {
      if (at2.lastModifiedTime() == null) {
        return at1.creationTime().compareTo(at2.creationTime());
      } else {
        return at1.creationTime().compareTo(at2.lastModifiedTime());
      }
    }
  }
}

Hier wird exzessiv mit If-Else-Anweisungen gearbeitet, um auch ja keine Kombination von Erstellungs- und Änderungszeitraum zu übersehen.

Welchen Zweck dient dieser Comparator eigentlich? Software Forensiker ahnen es schon, die Stammbäume sollen wohl nach dem Änderungsdatum sortiert werden. Weil aber neue Stammbäume kein Änderungsdatum haben wird alternativ das Erstellungsdatum verwendet.

Es kann sogar davon ausgegangen werden, dass zu Beginn nur mit dem Erstellungsdatum sortiert und später auf das Änderungsdatum umgestellt wurde.

Schon in den Beiträgen Zauberei mit Wahrheiten und Morgan Le Fays Rückkehr, wurde vor den Gefahren des Lavafluss Anti-Pattern und unübersichtlicher If-Else-Kaskaden gewarnt.

Bevor aber der Source-Code durch ein Refactoring von redundanten Else-Anweisungen befreit wird, gehen wir einen Schritt zurück und überlegen noch einmal, was der Zweck dieses Comporator ist.

Zwei Stammbäume sollen nach ihrem Änderungsdatum sortiert werden. Wenn dieses nicht vorhanden ist, soll das Erstellungsdatum verwendet werden.

In diesem Fall wäre es also eine gute Idee, erst einmal das jeweilige Vergleichsdatum zu bestimmen. Die im Beitrag Defaultwerte für NULL Parameter vorgestellte Methode requireNonNullElse vereinfacht den notwendigen Code sehr elegant. Nachdem beide Werte bestimmt wurden, wird der Vergleich durchgeführt.

Die oben vorgestellte groteske If-Else-Kaskade, verwandelt sich so in den nachfolgenden Dreizeiler.

Comparator<creationTime> dateCompare = (at1, at2) -> {
  Date date1 = Objects.requireNonNullElse(at1.lastModifiedTime(), at1.creationTime());
  Date date2 = Objects.requireNonNullElse(at2.lastModifiedTime(), at2.creationTime());
  return date1.compareTo(date2);
}

Nicht nur ein Maler muss hin und wieder einen Schritt zurückgehen, auch Software Entwicklern und Architeken hilft es, weitere Planungen nur nach einem prüfenden Blick aufs Ganze durchzuführen.