RSS-Feed anzeigen

Landei

Zweimal ist einmal zuviel

Bewerten
In diesem Post möchte ich einmal ein Designprinzip beleuchten, das dem bekannten "don't repeat yourself" ähnelt, aber damit nicht identlisch ist. Ich würde es "don't say it twice" nennen (auch wenn das kein hübsches Akronym bildet). Es geht darum, das Ding, an dem man gerade werkelt, nicht öfter zu nennen als unbedingt nötig. Der Grund ist simpel: Man muss solche Referenzen immer synchron halten, wodurch sie anfällig für Copy-Paste-Fehler sind, nämlich wenn man nach dem Kopieren vergißt, eine dieser Referenzen zu ändern.

Es ist interessant, an wievielen verschiedenen Stellen einem dieses Problem begegnet. Ein paar Beispiele:

if und switch

Java Code:
  1. if (x < 10) {
  2.    s = "too small";
  3. } else {
  4.    s = "okay";
  5. }

Besser und lesbarer finde ich hier den ternären Operator: String s = (x < 10) ? "too small" : "okay";. Switch hat ein ähnliches Problem, wenn es für eine Zuweisung gebraucht wird, nur ist das nicht ganz so leicht zu lösen (mit Lambdas kann man sich eine entsprechende DSL basteln).

for-Schleifen

Wer kennt solchen Code nicht?

Java Code:
  1. List<String> names = new ArrayList<>();
  2. for(Person p : getPersons()) {
  3.    names.add(p.getName());
  4. }

Hier ist nicht nur die Doppelnennung von ages ein Problem, sondern auch das sehr niedrige Abstraktionsniveau, bei dem wir detailliert beschreiben, wie wir durch die Personen-Liste iterieren und was wir mit jedem einzelnen Element tun. Das ist nicht das, was wir eigentlich wollen, sondern eher: "Nimm die Personenliste und wandle sie in eine Liste mit Namen um". Das Stichwort hier ist "umwandeln". Diese deskriptive Herangehensweise lässt sich seit Java 8 auch umsetzen, allerdings etwas umständlicher als in anderen Sprachen, nämlich über den Umweg mittels Streams:

List<String> names = getPerson().stream().map(Person::getName).collect(Collectors.toList());

Ich finde es schade, dass man den Collections nicht wenigstens für einfache Fälle ein map und filter spendiert hat, sondern dieser Umweg immer notwending ist. Allerdings ist es manchmal eine gute Strategie, bei umfangreicheren Berechnungen Zwischenergebnisse als Stream anstatt als Collection zu halten, und erst am Ende umzuwandeln.

Referenzen auf Felder

Nehmen wir das Beispiel Collections.sort(persons, (p0,p1) -> Integer.compare(p0.getAge(), p1.getAge()));. Hier wird zweimal das Feld age angesprochen. Besser wäre es, nur zu sagen "vergleiche Personen nach ihrem Alter", und mit einer Helper-Methode der API geht das auch: Collections.sort(persons, Comparator.comparingInt(Person::getAge));.

instanceof

Dieses Beispiel ist etwas anders, da es sich diesmal auf eine Klasse bezieht:

Java Code:
  1. if(x instanceof String.class) {
  2.    String s = (String) x;
  3.    System.out.println(s.length());
  4. }

Aber wieder ist das Problem, dass wir den instanceof-Test und den Cast in der Schleife synchron halten müssen. Wieder kann ein Helper - diesmal ein eigener - Abhilfe schaffen:

Java Code:
  1. public static <T> ifInstanceOf(Object o, Class<T> clazz, Consumer<T> consumer) {
  2.     if(clazz.isInstance(o)) {
  3.         consumer.accept((T) o);
  4.     }    
  5. }
  6.  
  7. ifInstanceOf(x, String.class, s -> System.out.println(s.length()));

Hier sieht man auch sehr schön eine Stärke der Lambdas: Es lassen sich sehr gut Kontrollstrukturen und -flüsse abstrahieren. Früher war es schwierig, eine Schleife, z.B. einen Test oder einen Try-Block zu abstrahieren, weil es sehr umständlich war, das gewünschte individuelle Verhalten irgendwie hineinzubekommen. Jetzt kann diesen Part ein Lambda (in diesem Fall als Consumer) übernehmen.

Ich kann nur dazu ermutigen, genauer hinzuschauen, wenn einem eine Mehrfachnennung über den Weg läuft. Das lässt sich nicht immer vermeiden, aber in vielen Fällen bietet sich die Chance zum Abstrahieren.

Aktualisiert: 28.07.2015 um 21:15 von Landei

Kategorien
Kategorielos

Kommentare

Kommentar schreiben Kommentar schreiben

Trackbacks