Ausbruchsversuch gelungen

Hallo Spaß-Coder.

In diesem Artikel beschäftigen wir uns wieder mit dem Thema Refactoring und stellen zwei weitere Refaktorisierungsmuster vor.

Ihr habt eine Klasse, in der ihr eine bestimmte Methode ändern müsst. Habt ihr schon einmal erlebt, dass die Klasse für einen Test nicht instanziiert werden kann? Zu viele Abhängigkeiten an Datenbanken, Dateisystem oder anderen komplexen Klassen verhindern leider die Klasse per Konstruktor zu erzeugen. Was nun? Die Methode ohne Unit-Tests zu ändern ist zu riskant. Aus diesem Grund müssen erst einmal Tests geschrieben werden – aber wie?

Vielleicht ist es in solchen Fällen möglich mit Hilfe eines Mocking-Frameworks die gewünschte Methode testbar zu machen und alle Abhängigkeiten zu „entfernen“. Sollte dies nicht möglich sein (oder wilden Spaghetti-Mocking-Code ergeben), helfen vielleicht diese beiden Muster.

 

Expose Static Method

Manchmal müssen wir Klassen verändern, die wir nicht testen können, weil die Methode, die wir ändern möchten leider privat ist. Wir ändern aber keinen Code, der nicht getestet ist, weil die Gefahr zu hoch ist, dass wir etwas kaputtmachen.

Verwendet die betroffene Methode keine Instanzvariablen oder andere Methoden der Klasse, können wir diese einfach in eine öffentliche, statische Methode umwandeln. Diese statische Methode lässt sich nun problemlos testen.

In aller Regel führen Methoden dieser Art dazu, dass die Klasse eine geringe Kohäsion aufweist. Wenn ihr gerade nicht mehr genau wisst, was das nochmal gleich bedeutet und warum das nicht  gut ist, lest nochmal die Artikel Teile und Herrsche sowie Aus Prinzip nur eine Verantwortlichkeit. Darin sind wir auf das Thema bereits eingegangen.

Wir wollen bei der Refaktorisierung möglichst kleine, risikoarme Schritte machen. Deshalb ist es besser nicht gleich die Kohäsion gänzlich aufzulösen, sondern zunächst den betroffenen Code durch Tests abzusichern und anschließend erst zu verändern.

 

Anleitung

Wie aber gehen wir nun vor, um unsere Methode statisch und damit testbar zu machen?

Die kurze Anleitung dazu besteht aus wenigen Schritten:

  1. Unit-Test für die neue, statische Methode schreiben
  2. Methodenrumpf der Methode in eine neue, statische Methode extrahieren
  3. Code kompilieren (lean on the compiler)
  4. Nicht-statische Verwendungen innerhalb der neuen Methode ebenfalls statisch machen

Haben  wir alle Schritte ausgeführt, sollte unser Code kompilierbar sein und der Unit-Test aus Schritt 1. grün sein.

 

Hinweise

Manchmal möchten wir die Methode aber gar nicht nach außen sichtbar machen. Wir möchten unsere Klassen schließlich absichern. Insbesondere, wenn wir in Schritt 4. weitere nicht-statische Abhängigkeiten auflösen.

In einem solchen Fall bieten uns Sprachen wir Java oder C# die Möglichkeit, die Methode auf den aktuellen Bereich zu beschränken. In Java können wir die Klasse package scoped machen; in C# nutzen wir das Schlüsselwort internal. Beides führt dazu, dass wir in unserem Test, welcher im gleichen package liegt oder Zugriff auf internals hat, auf die Methode zugreifen können.

Eine weitere Möglichkeit liegt darin, die Methode protected zu definieren und die Test-Klasse davon abzuleiten. In diesem Fall können wir auf die Methode zugreifen, nicht abgeleitete Klassen aber nicht. (Dies funktioniert dann auch in Sprachen wie C++).

 

Break Out Method Object

Was aber tun wir, wenn unsere zu testende Methode sehr lang ist und viele Abhängigkeiten hat? Einen Test zu schreiben würde bedeuten, die gesamte, riesige Klasse zu instanziieren und alle Abhängigkeiten in unserem Test aufzubauen. Das Muster Expose Static Method ist hier mitunter ungeeignet, wenn es einfach zu viele Abhängigkeiten gibt, die Kohäsion der Methode also recht hoch ist.

In einem solchen Fall hilft es, der Methode eine eigene Klasse zu spendieren. Wenn sie so groß und umfangreich ist, tut sie oft ohnehin viel zu viel und gehört häufig gar nicht in die Klasse, in der wir sie vorgefunden haben.

Beim Muster Break Out Method Object ziehen wir die zu testende Methode mitsamt allen Abhängigkeiten in eine eigene Klasse. Dabei werden nur diejenigen Abhängigkeiten mitgenommen, die von der zu testenden Methode verwendet werden. Alle anderen bleiben in der bisherigen Klasse. Dies sorgt für eine bessere Trennung der Aufgaben, verringert die Abhängigkeiten und erleichtert es uns, die Methode in der neuen Klasse zu testen.

 

Anleitung

Das Erstellen einer neuen Klasse und das überführen der Methode dort hinein ist ein wenig aufwendiger als im obigen Muster.

Hier die Schritt-für-Schritt Anleitung dazu:

  1. Neue, leere Klasse für die Methode erstellen
  2. Konstruktor mit der Signatur der bisherigen Methode erstellen
    • ggf. muss eine Instanz der bisherigen Klasse übergeben werden (bei Zugriffen auf Instanzvariablen; erstes Argument des Konstruktors)
  3. Für alle Konstruktor-Parameter Instanzvariablen erstellen und im Konstruktor zuweisen
  4. Leere Methode ohne Parameter erstellen (häufig run())
  5. Methodenrumpf aus der alten Klasse in die leere Methode kopieren und kompilieren (lean on the compiler)
  6. Kompilierfehler korrigieren, ggf. Zugriff auf alte Klasse durch public Methoden oder via Getter ermöglichen
  7. Alte Methode so ändern, dass die neue Klasse instanziiert und dann verwendet wird
  8. Ggf. Extract Interface nutzen, um die Abhängigkeit von der ursprünglichen Klasse aufzuheben

 

Hinweise

Wenn wir eine Methode, die vielleicht sogar privat ist, in eine neue Klasse überführen, führt das dazu, dass wir die Sichtbarkeit der Methode erhöhen. Darüber hinaus haben wir nun eine Klasse, die an genau einer Stelle instanziiert wird, nämlich da, wo sie bisher stand. Das könnte zu einem unguten Gefühl führen.

Die Vorteile sind hier vielleicht nicht direkt ersichtlich. Neben dem offensichtlichen Punkt, dass wir die Methode nun mit viel weniger Aufwand testen können, haben wir eine gute Basis geschaffen, das Design der Klassen weiter zu verbessern. Haben wir in Punkt 8 ein Interface herausgezogen, können wir nun den nächsten Schritt gehen und die Abhängigkeit auflösen, z. B. in dem wir das Dependency Inversion Principle anwenden.

Durch die Aufteilung mithilfe des Break Out Method Object laden wir andere Entwickler (und auch uns selbst) dazu ein, das Design weiter zu verbessern. Wir können z.B. die lange Methode mithilfe von Extract Method weiter aufteilen und so den Code besser lesbar machen.

 

Zusammenfassung

Gewachsener Code, der nicht durch Tests abgesichert ist, ist wie ein Minenfeld. Man weiß nie so genau, was beim nächsten Schritt passiert.

Durch die beiden hier aufgeführten Refactoring Patterns bekommen wir die Möglichkeit, ohne großes Risiko, interne Methoden testbar zu machen. Dann können wir gefahrlos das Design der Klassen weiter verbessern oder unsere Änderung an der Klassen vornehmen.

 

Wir wünschen euch viel Spaß beim Ausprobieren.

Eure Spaß-Coder

 

Dieser Artikel basiert neben unseren Erfahrungen auf folgenden Quellen:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.