Einfache Refactorings – Teil 2

Das Abstraktionsniveau anheben – Extract Class from Parameters

Das Extract Class from Parameters Refactoring hilft, wenn ist die Parameterliste einer Method über die Jahre etwas zu lang geworden ist. Manchmal stehen die Parameter in einem sinnvollen Zusammenhang zueinander, so dass man sie zu einer Klasse zusammenfassen kann. Das ist nicht immer der Fall, vor allem, wenn eine Methode für mehr als einen Aspekt zuständig ist. In solchen Fällen werden häufig einzelne Parameter oder Gruppen von Parametern für die unterschiedlichen Aspekte der Methode benötigt. In dem Fall kann es helfen, zunächst die Aspekte zu trennen. Dazu weiter unten mehr.

Stehen die Parameter einer Method in einem inhaltlichen Zusammenhang, ist es manchmal nützlich, sie zu einem eigenen Typ zusammenzufassen. Damit wird die Signatur der Methode leichter verständlich, weil der Typname die Bedeutung der einzelnen Parameter besser transportiert. Der Leser muss das gedankliche Zusammenfassen der Parameter nicht jedesmal selbst vornehmen, sondern kann dies anhand des gewählten Typnamens sofort erkennen. Es ergibt sich folglich eine weitere Abstraktionsebene. Folgendes Beispiel zeigt eine Methode mit vielen Parametern:

public string Format(string vorname, string nachname, string strasse, string hn, string plz, string ort) {
    return $"{vorname} {nachname}, {strasse} {hn}, {plz} {ort}";
}

Hier lässt sich durch ein Extract Class from Parameters Refactoring die Lesbarkeit verbessern:

public string Format(Adresse adresse, string vorname, string nachname) {
    return $"{vorname} {nachname}, {adresse.Strasse} {adresse.Hn}, {adresse.Plz} {adresse.Ort}";
}

Ich habe in diesem Beispiel die Parameter vorname und nachname recht willkürlich als eigenständige Parameter stehen lassen. Es kommt auf den jeweiligen konkreten Kontext an, in welcher Weise Sie die Parameter zu einer Klasse zusammenfassen. Hier wäre auch eine Zusammenfassung aller ursprünglichen Parameter denkbar gewesen. Im Beispiel hat mir ReSharper die folgende Klasse Adresse generiert:

public class Adresse
{
    private string strasse;
    private string hn;
    private string plz;
    private string ort;

    public Adresse(string strasse, string hn, string plz, string ort) {
        this.strasse = strasse;
        this.hn = hn;
        this.plz = plz;
        this.ort = ort;
    }

    public string Strasse {
        get { return strasse; }
    }

    public string Hn {
        get { return hn; }
    }

    public string Plz {
        get { return plz; }
    }

    public string Ort {
        get { return ort; }
    }
}

Fazit

Benutzen Sie Extract Class from Parameters, um die Signatur einer Methode leichter lesbar zu machen. Durch den zusätzlichen Bezeichner ergibt sich ein höheres Abstraktionsniveau.

Aspekte trennen – Extract Method

Häufig besteht eine Methode aus Blöcken von zusammengehörigen Codezeilen, die jeweils eine Teilaufgabe des gesamten Problems erledigen. Manchmal sind die Codeblöcke dann noch mit Kommentaren versehen, wie im folgenden Beispiel:

public IDictionary<string, string> ToDictionary(string configuration) {
    // Split configuration into settings
    var settings = configuration.Split(';');

    // Split settings into key/value pairs
    var pairs = new List<KeyValuePair<string, string>>();
    foreach (var setting in settings) {
        var keyAndValue = setting.Split('=');
        pairs.Add(new KeyValuePair<string, string>(keyAndValue[0], keyAndValue[1]));
    }

    // Insert pairs into dictionary
    var result = new Dictionary<string, string>();
    foreach (var pair in pairs) {
        result.Add(pair.Key, pair.Value);
    }

    return result;
}

In solchen Fällen kann die Lesbarkeit deutlich verbessert werden, indem die einzelnen Codeblöcke in Methoden ausgelagert werden. Dazu markieren Sie jeweils mehrere Zeilen und starten dann das Extract Method Refactoring Ihrer IDE. Den Vorgang wiederholen Sie möglichst so oft, bis die ursprüngliche Methode nur noch Methodenaufrufe enthält. Das Endergebnis sieht dann wie folgt aus:

public IDictionary<string, string> ToDictionary(string configuration) {
    var settings = SplitIntoSettings(configuration);
    var pairs = SplitIntoPairs(settings);
    var result = InsertIntoDictionary(pairs);
    return result;
}

In diesem Beispiel konnte durch mehrfaches Anwenden von Extract Method eine klare Trennung von Integration und Operation erreicht werden. Die resultierende Methode ToDictionary integriert die Methoden, die durch dreimaliges Anwenden von Extract Method entstanden sind. Damit wird das Prinzip Single Level of Abstraction (SLA) eingehalten, welches besagt, dass eine Methode nur ein Abstraktionsniveau haben soll. Für die ursprüngliche Methode galt das, sie war auf einem niedrigen Abstraktionsniveau. In der Version nach dem Refactoring ist das SLA Prinzip ebenfalls eingehalten. Der Code der Methode befindet sich vollständig auf einem höheren Abstraktionsniveau. Hätte ich nach dem ersten Extract Method aufgehört, wären unterschiedliche Abstraktionsniveaus in der Methode verblieben.

Die Trennung von Integration und Operation schafft eine Abstraktionseben, die zuvor nicht vorhanden war. Die gesamte Lösung des Problems ToDictionary stand ursprünglich auf niedrigem Abstraktionsniveau in der Methode. Die Methode war eine Operation auf niedrigem Abstraktionsniveau. Um dem Leser einen Anhaltspunkt zu geben, welche Bedeutung die Codeblöcke haben, waren diese jeweils mit Kommentaren versehen. Durch mehrmaliges Extract Method ist der Code der ToDictionary Methode nun abstrakter als zuvor. Die Lösung erschließt sich dem Leser leichter. Darüber hinaus kann ein Leser nun entscheiden, ob er nur am Abstrakten oder auch an den Details interessiert ist. Vor allem dadurch unterscheidet sich die Version nach dem Refactoring deutlich von der ursprünglichen. Dort hatte der Leser nicht die Wahl sondern war immer mit dem gesamten Code konfrontiert.

Das Extrahieren der Methoden führte ferner dazu, dass nun die Aspekte, aus denen die Lösung des Problems ToDictionary besteht, nicht mehr vermischt sondern getrennt sind.

Nicht immer ist das Aufteilen des Codes einer Method so einfach möglich wie im Beispiel gezeigt. Vor allem wenn Codebereiche in Schleifen oder Bedingungen eingeschachtelt sind, gelingt ein Extrahieren und vollständiges Trennen der Aspekte nicht mehr so ohne weiteres. Dennoch kann Extract Method auch dort die Lesbarkeit des Codes erhöhen, weil dadurch eine weitere Abstraktionsebene eingeführt wird. Folgender Codeausschnitt zeigt eine Schleife, in der ein Setting bearbeitet wird. Der Inhalt der Schleife ist mit zwei Zeilen sehr kurz, dennoch lohnt sich schon hier ein Extract Method Refactoring:

foreach (var setting in settings) {
    var key_and_value = setting.Split('=');
    result.Add(key_and_value[0], key_and_value[1]);
}

Nach dem Refactoring sieht die Schleife wie folgt aus:

foreach (var setting in settings) {
    AddSettingToResult(setting, result);
}

Als Leser muss ich nun nicht den gesamten Inhalt der Schleife Zeile für Zeile lesen, sondern es genügt, mich mit einem Methodenaufruf zu befassen. Sollten mich die Details der Methode interessieren, habe ich die Möglichkeit, in die extrahierte Methode zu schauen. Es ergibt sich hier also erneut eine weitere Abstraktionseben – als Leser kann ich wählen, ob ich tiefer in die Details einsteigen möchte oder nicht. Steht der Rumpf der Schleife vollständig mit allen Details innerhalb der Schleife, habe ich keine Wahl sondern bin gezwungen, mir den Schleifenrumpf Zeile für Zeile anzuschauen.

Fazit

Benutzen Sie Extract Method, um Codezeilen in eine Methode auszulagern. Dadurch nutzen Sie die Gelegenheit, dem Codebereich einen Bezeichner zu vergeben und so die Bedeutung mitzuteilen. Die ursprünglich vielleicht vorhandenen Kommentare werden durch die Einführung der Methode häufig überflüssig. Ferner ermöglichen Sie dem Leser durch die Einführung weiterer Methoden, die Lösungsebene auf einem höheren Abstraktionsniveau zu lesen. Ferner lassen sich so häufig die Aspekte trennen, so dass die ursprüngliche Methode nach dem Refactoring lediglich für die Integration der einzelnen Aspekte zuständig ist. Durch Extract Method kann oft das Prinzip Single Level of Abstraction erreicht werden.

Weiter zu Teil 3 der Serie.

 

 

1 Gedanke zu „Einfache Refactorings – Teil 2“

Schreibe einen Kommentar