Refactoring – Was es ist und warum jeder Entwickler es machen sollte

Hallo Spaß-Coder.

kennt ihr das Gefühl, dass ihr euch nicht an euren eigenen Code herantraut? Ihr schreibt ein schönes Programm und nun soll eine Funktion oder ein Feature ergänzt werden. Aber der Code ist irgendwie unverständlich, unübersichtlich. Ist ja schließlich schon ein paar Monate her, dass ihr ihn geschrieben habt. Noch schneller kommt dieses Gefühl auf, wenn ihr im Team mit anderen zusammenarbeitet und Code von den Teamkollegen ändern müsst.

Der erste Schritt, den wir in einer solchen Situation in der Regel unternehmen ist, den Code zu verstehen. Dann versucht man die Stelle zu finden, an der das neue Feature integriert werden soll und passt den Code an oder erweitert ihn. Je nach Qualität des Codes, den ihr vorfindet, ist das ein mehr oder weniger schweres Unterfangen. Hierbei hilft uns das Refactoring.

Aber auch bei der Implementierung neuer Features ist es sinnvoll, nach dem Hinzufügen der Funktion noch einmal den Code zu betrachten und zu schauen, ob dieser auch sauber ist und unseren eigenen Ansprüchen genügt. Wer hat nicht schonmal nach ein paar Wochen in seinen eigenen Code geschaut und gedacht „Ohje, was habe ich denn da hinterlassen?“. Besonders bei der Arbeit in Teams hält solcher Code sehr schnell auf, da jeder im Team zunächst den Code verstehen und dann ggf. selbst refaktorisieren muss.

 

Was ist Refactoring?

Refactoring oder zu deutsch Refaktorisieren ist die Tätigkeit, Programmcode mit geeigneter Absicherung und in kleinen Schritten dahingehend zu verändern, dass er leicht verständlich und einfach zu erweitern ist, unter Beibehaltung des bisherigen Verhaltens.

Das heißt, wir Ändern das Verhalten des Codes nicht, wenn wir ihn refaktorisieren.

Ein Refactoring hilft uns in oben beschriebener Situation also doppelt:

  • der Code wird lesbarer und leichter verständlich
  • unser Feature lässt sich leichter einbauen, da der refaktorisierte Code leicht zu erweitern ist

Wir unterscheiden verschiedene Arten des Refactorings:

  1. Toolunterstütztes Refactoring
    Darunter verstehen wir alle Arten des Refactorings, bei der uns unsere IDE unterstützt. Darunter fallen etwa Umbenennungen von Variablen, Parametern, Funktionen und Klassen, aber auch das extrahieren von Funktionen in Methoden oder die automatische Umgestaltung von Abfrageausdrücken.
  2. Refactoring zur Verbesserung der Lesbarkeit
    Wenn wir Code ändern müssen, den wir nicht verstehen, müssen wir uns zwangsläufig damit auseinandersetzen. Vielleicht lesen wir ihn und schreiben uns den Ablauf auf einem Blatt Papier auf, vielleicht nutzen wir Log-Ausgaben oder den Debugger um herauszufinden, was passiert. Irgendwann haben wir den Punkt erreicht, da wissen wir, was der Code macht. Dies ist der richtige Zeitpunkt, um den Code so zu refaktorisieren, dass wir ihn auch beim nächsten Mal  verstehen. Dann aber ohne langes Analysieren und Debuggen.
  3. Refactoring zur Verbesserung der Erweiterbarkeit
    Haben wir den Code verstanden (und ggf. zur besseren Lesbarkeit umgebaut), stellen wir ggf. fest, dass wir die Erweiterung sehr schnell und einfach einfügen könnten, wenn der Code hier nur ein bisschen anders aufgebaut wäre. Stellen wir dies fest, sollten wir den Code genau so umbauen, bevor wir unsere Erweiterung vornehmen.
  4. geplantes Refactoring
    Wenn wir als Entwickler nicht regelmäßig refaktorisieren und den Code immer mehr vernachlässigen, stellen wir irgendwann fest, dass wir Zeit brauchen, um Teile unserer Software umfangreich neu zu gestalten. Es handelt sich um ein „großes“ Refactoring. Dieses sollte im Projekt- oder Sprintplan als Aufgabe auftauchen, da es eine relevante Zeit dauern wird.
    Gute Teams werden ein geplantes Refactoring selten brauchen. Das perfekte Team wird es niemals brauchen, da sie ständig den Code im Rahmen ihrer täglichen Arbeit sauber halten.

 

Warum Refactoring?

Ist das ständige Ändern von Code, ohne dass wir neue Funktionen oder Features hinzufügen nicht verschwendete Arbeit?

Unserer Ansicht nach nicht. Im Laufe der Zeit wird der Code schlechter und es wird schwieriger Änderungen einzufügen. Auch glauben wir nicht, dass wir als Softwareentwickler sofort im ersten Wurf den  perfekten Code schreiben können. Wir schaffen es nicht, bei jedem neuen Feature von Anfang an alle Standards und Namenskonventionen, alle Prinzipien und Empfehlungen einzuhalten. Dafür ist unsere Arbeit einfach zu komplex. Sicher, mit erhöhter Erfahrung werden wir mehr und mehr davon in unsere tägliche Arbeit aufnehmen können, aber kaum jemand wird den perfekten Code direkt beim ersten Wurf schreiben können.

Auch werden sich Anforderungen mit der Zeit ändern, Technologien und damit verbundene Prinzipien werden sich ändern. Spätestens dann sind wir gezwungen, unseren Code zu refaktorisieren.

Durch Refactoring kommen wir in kleinen Schritten immer weiter zu einem komponentenorientierten Design. Erstellen wir im ersten Wurf vielleicht eine Mammutklasse, die zunächst das gewünschte Feature enthält, refaktorisieren wir ihn anschließend immer weiter unseren Anforderungen entsprechend, sodass der Code sich in saubere Komponenten aufteilt und leicht zu erweitern ist.

Der wichtigste aller Gründe ist aber, dass sauberer, professioneller Code, der eine hohe Qualität hat und sicherstellt, sowie schnell, einfach und sicher zu erweitern ist, mittel- bis langfristig nur ein Ziel hat: eine hohe Wirtschaftlichkeit! Denn können wir schneller unser neues Feature einfügen, so können wir es auch schneller ausliefern.

 

Wie gehen wir beim Refactoring vor?

Refactoring ist für uns eine permanente Tätigkeit. Wir schreiben ein paar Zeilen funktionierenden Code, schauen sie uns noch einmal an und bauen sie wieder um, damit sie besser lesbar oder strukturierter sind. Dann schreiben wir weiteren funktionierenden Code, refaktorisieren ihn wieder und so weiter. Wir machen also immer wieder zwei Schritte:

  1. den Code zum Laufen bringen
  2. den Code sauber machen

Kent Beck hat sich dazu die Metapher der zwei Hüte überlegt. Wir tragen einen Hut, wenn wir den Code zum Laufen bringen und einen anderen, wenn wir den Code sauber machen. Wir haben nur einen Kopf, können also nicht beide Hüte gleichzeitig tragen. Wir dürfen aber regelmäßig die Hüte wechseln.

Stolpern wir beim Refactoring also z.B. über einen Fehler, sollten wir das Refactoring zu Ende bringen, den Fehler beheben (den Hut wechseln) und anschließend das Refactoring wieder aufnehmen (den Hut erneut wechseln). Wir sollten uns immer bewusst sein, ob wir den Code zum Laufen bringen oder sauber machen. Wichtig ist, dass wir immer auch den zweiten Schritt durchführen. Wir dürfen niemals aufhören, bevor wir den Code nicht sauber gemacht haben!

Unsere Verantwortung als Entwickler, der später diesen Code wieder ändert oder sein Beitrag zu einem Team leistet, ist es, permanent den Code zu bewerten und Designprobleme durch Refaktorisieren zu beheben.

 

Code Ändern aber das Verhalten beibehalten? Wie geht das denn?

Wie aber stellen wir sicher, dass beim Refactoring der bestehende Code nicht sein Verhalten ändert? Wir ändern schließlich den vorhandenen Code, teilweise die Struktur und den internen Aufrufstapel.

Hierbei ist es von essenzieller Wichtigkeit, dass wir für jedes von uns erwartete Verhalten ausreichend Tests haben. Bevor wir mit dem Refaktorisieren beginnen, müssen alle Tests grün sein! Nur so können wir guten Gewissens unseren Code verbessern, ohne die Funktionalität zu verändern.

Sollte es – aus welchen Gründen auch immer – nicht möglich sein, Unittests zu schreiben, gilt die Regel, dass wir besser einen Integrationstest schreiben, als gar keinen Test.

Wie ist also unser Fahrplan zum Refactoring?

  1. wir schreiben unser Feature
  2. decken es mit Tests ausreichend ab
  3. prüfen, ob die Tests grün sind
  4. führen Refactorings durch
  5. prüfen erneut, ob alle Tests grün sind

Wollen wir vorhandenen Code umbauen, beginnen wir im besten Fall mit Schritt 3. Sind noch keine Tests vorhanden, beginnen wir mit Schritt 2 um das Verhalten vor unserem Umbau zunächst abzusichern.

Abschließend haben wir sauberen Code und die Sicherheit, dass wir genau die Funktionalität haben, die wir – durch die Tests beschrieben – haben möchten.

Die Königsdisziplin dabei ist das sogenannte Test-Driven-Development. Darauf gehen wir aber detaillierter in einem eigenen Artikel ein.

 

Wie viel Refactoring ist denn nötig?

Nun, das kann nur jeder für sich selbst entscheiden. Der Code sollte mindestens ein wenig besser sein! Nach ein paar Refaktorisierungen in kleinen Schritten wird der Code mit der Zeit deutlich besser. Wichtig ist, dass Refactoring keine zusätzliche Arbeit ist, sondern einen Bestandteil er normalen Arbeit für uns als verantwortungsbewussten Entwickler darstellt.

Die benötigte Zeit für das Refactoring wird sich mittelfristig lohnen, da der Code leichter zu verstehen und besser zu ändern ist, wenn wir dabei die SOLID-Prinzipien beachten.

Da wir heute noch nicht wissen, was nächste Woche, in einem Monat oder in einem halben Jahr von unserem Code verlangt wird, ist eine evolutionäre Entwicklung wichtig. Sobald wir neue Anforderungen an den Code haben, können wir den Code entsprechend den neuen Anforderungen / Informationen anpassen. Wir sollten es aber vermeiden, zu weit vorauszudenken und den Code komplizierter zu gestalten, als es derzeit notwendig ist. Das Prinzip dahinter trägt auch einen Namen: YAGNI.

 

Zusammenfassung

Es ist wichtig und unsere Verantwortung als professioneller Entwickler, dass der Code, den wir schreiben, nicht nur funktioniert, sondern gut lesbar, leicht verständlich und effizient zu erweitern ist.

Refactoring beginnt im Kleinen und ist Bestandteil der täglichen Arbeit von uns als Entwickler. Wir refaktorisieren ständig unseren Code und den Code unseres Teams.

Nur so können wir mittel- bis langfristig sicherstellen, dass unsere Softwareentwicklung wirtschaftlich bleibt.

 

Viel Spaß beim Ausprobieren und Happy Coding wünschen

eure Spaß-Coder

 

Dieser Artikel basiert neben unseren Erfahrungen auf den Ausführungen aus:

  1. Martin, Robert C. Clean Code-Refactoring, Patterns, Testen und Techniken für sauberen Code: Deutsche Ausgabe. MITP-Verlags GmbH & Co. KG, 2013.
  2. Fowler, Martin Workflows of Refactoring.
    OOP 2014
  3. Hariri, Hadi Refactoring Legacy Code Bases
    Basta Sprint 2013

Schreibe einen Kommentar

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