Die if-Kaskade und „übertragbarer“ Code


Jeder von uns hat schon solchen Code geschrieben:

if (bmi >= 40) {
    System.out.println("Serious obesity");
} else if (bmi >= 30) {
    System.out.println("Obesity");
} else if (bmi >= 25) {
    System.out.println("Overweight");
} else if (bmi >= 18) {
    System.out.println("Standard");
} else {
    System.out.println("Underweight");
}

Ja, ich weiß dass der BMI ein schlechter Maßstab für Über- oder Untergewicht ist. Davon abgesehen ist es unvermeidlich, dass man als Anfänger solchen Code schreibt. Aber dieses Code-Beispiel hat mich zum Nachdenken angeregt. Warum ist dieser Code so schlecht? Die einfache Antwort ist, dass er sowohl das Single Responisbility Principle verletzt, als auch eine Code-Duplizierung darstellt, also gegen das DRY-Prinzip verstösst (nämlich in Form von fünf System.out.println-Aufrufen). Der Code tut zwei Dinge auf einmal: Er trifft eine Fallunterscheidung, und er gibt etwas auf der Konsole aus. Beheben wir das:

String result;
if (bmi >= 40) {
    result = "Serious obesity";
} else if (bmi >= 30) {
    result = "Obesity";
} else if (bmi >= 25) {
    result = "Overweight";
} else if (bmi >= 18) {
    result = "Standard";
} else {
    result = "Underweight";
}
System.out.println(result);

Schon besser, aber die Fallunterscheidung könnte auch anderswo nützlich sein, packen wir sie in eine Methode:

public static String evaluate(double bmi) {
    if (bmi >= 40) {
        return "Serious obesity";
    } else if (bmi >= 30) {
        return "Obesity";
    } else if (bmi >= 25) {
        return "Overweight";
    } else if (bmi >= 18) {
        return "Standard";
    } else {
        return "Underweight";
    }
}
...
System.out.println(evaluate(bmi));

Jetzt ist aber alles gut, oder nicht? Kommt darauf an, für manche Anwendungsfälle mag es ausreichend sein, aber ich sehe immer noch zwei(!) Probleme.

Das erste Problem ist Skalierbarkeit. Was ist, wenn man mehr Fallunterscheidungen treffen will? Oder wenn man die Fälle z.B. aus einer Datenbank oder Properties-Datei laden will?

Das zweite Problem – das, was mich zum Nachdenken gebracht hat – geht in eine ähnliche Richtung, ist aber etwas schwerer zu fassen: Der Code ist nicht „übertragbar“. Ich kann die Fallunterscheidungen nicht leicht durch meine Applikation transportieren, sie sind duch eine Methode eng an eine bestimmte Klasse gekoppelt. Wäre es nicht besser, wir würden statt einer Methode eine Datenstruktur schreiben, die die Fallunterscheidung kapselt? Könnte es vielleicht sogar sein, dass es in Java solch eine Datenstruktur schon gibt?

Ja, es gibt diese Datenstruktur bereits, und sie nennt sich TreeMap (oder jede andere Map, die das Interface NavigableMap implementiert):

private final static NavigableMap<Double, String> CATEGORIES = categories();

public static NavigableMap<Double, String> categories() {
    NavigableMap<Double, String> map = new TreeMap<>(); 
    map.put(40.0, "Serious obesity");
    map.put(30.0, "Obesity");
    map.put(25.0, "Overweight");
    map.put(18.0, "Standard");
    map.put(0.0, "Underweight");
    return map;
}
...
public static String evaluate(NavigableMap<Double, String> categories, double bmi) {
    return categories.floorEntry(bmi).getValue()
}
...
System.out.println(evaluate(CATEGORIES, bmi)); 

Natürlich kommt es ganz auf den Anwendungsfall an, wie und wo man die TreeMap initialisiert. Der Punkt ist aber, dass man sie leicht in verschiedener Weise füllen kann (Skalierbarkeit) und sie dann auch frei durch die Anwendung hindurchschleusen kann. Statt sie wie hier statisch zu erzeugen, könnte sie genauso als Konstruktor-Argument übergeben werden u.s.w. Als Datenstruktur besitzt sie eine Art von Mobilität, die eine Methode nicht ohne weiteres bieten kann.

Wie schon gesagt muss man abwägen, ob man die zusätzliche Flexibilität im gegebenen Kontext wirklich braucht, oder ob es schon in Overengineering ausartet. Aber man muss die Möglichkeit des weitergehenden Refactorings erst einmal sehen, um eine qualifizierte Entscheidung fällen zu können. Das Beispiel zeigt auf jeden Fall, dass es sich lohnen kann, auch bei „einfachem“ Code genauer hinzuschauen.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

w

Verbinde mit %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.