Refactoring ist das Umstrukturieren von Code mit dem Ziel, zukünftige Änderungen leichter zu machen. Dabei wird die Funktionalität nicht geändert.
Lassen Sie mich noch ein paar Empfehlungen dazu geben.
Refactorings in kleinen Schritten machen. Viele kleine Schritte sind sicherer (und schneller) als ein großer Sprung. Die Refactorings sollten im Normalfall nur das aktuell zu implementierende Feature bzw. den aktuellen Bugfix unterstützen. Eine gute Frage dazu lautet: welches Refactoring kann ich jetzt machen, damit die neue Funktionalität danach “fast trivial” zu implementieren ist? Wenn man solche Refactorings macht, wird die Softwareentwicklung insgesamt schneller. Hier ist ein Diagramm zur Verdeutlichung dieser Idee. Wir zeigen zwei Software-EntwicklerInnen, die dieselben Features implementieren.
Über die Zeit nimmt die Funktionalität (y-Achse) bei beiden zu, beide haben nacheinander Features 1 bis 3 fertig. Die schwarze Linie zeigt den Verlauf mit Refactoring. Diese Linie verläuft manchmal wagerecht–die Entwicklerin arbeitet eigentlich nicht an dem Feature, sondern strukturiert den Code um. Was sie in dieser Zeit an Fortschritt einbüßt, wird später mehr als ausgeglichen durch die bessere Struktur des Codes. Im Vergleich dazu die rote Linie: der Entwickler arbeitet ständig an der Funktionalität. Dabei wird er von schlechter Code-Struktur behindert und ist deswegen insgesamt langsamer als seine Kollegin, die ab und zu ein sinnvolles Refactoring einschiebt.
Refactorings mit Unit Tests absichern. Mit Unit Tests kann man schnell und automatisiert prüfen, ob der Code noch das tut, was die Programmierer beabsichtigt hatten. Man merkt damit also schnell, wenn sich beim Refactoring ein Fehler eingeschlichen hat. Idealerweise geht das so: einen kleinen Refactoring-Schritt machen, alle Unit Tests laufen lassen. Wenn die Tests “grün” sind (alles OK), dann weiter mit dem nächsten Refactoring-Schritt. Mit Unit Tests geht das Refactoring wesentlich schneller als ohne, weil man nach jedem kleinen Schritt sofort sieht, dass sich an der Funktionalität nichts geändert hat. Hier haben wir also einen weiteren Grund dafür, eine möglichst vollständige Suite von Unit Tests aufzubauen und zu pflegen.
Dann erhebt sich natürlich die Frage: Was tun, wenn es in unserem Code keine oder nur sehr lückenhafte Unit Tests gibt? Dann wird es mühsamer und etwas anspruchsvoller. Kurz gesagt baut man die Unit Tests parallel zur Umstrukturierung des Codes auf. Code wird zuerst in kleinen Schritten in Richtung besserer Testbarkeit umstrukturiert, dann werden lokale Tests eingefügt, die wiederum die nächste kleine Umstrukturierung absichern usw. Eine ganze Reihe von Techniken zur Auflösung des Problems “Unser Code ist schlecht strukturiert und hat keine Tests” sind im Buch von Michael Feathers “Working Effectively with Legacy Code” beschrieben.
Refactoring laufend machen. Also nicht “später” aufräumen, sondern wenn (bevor) wir den betreffenden Bereich im Code ändern. Nicht spekulativ “auf Vorrat”, denn man liegt meist falsch, wenn man die beste Struktur “für alle zukünftigen Anforderungen” vorhersagt. Wohl die wichtigste und einfachste Empfehlung lautet: sinnvolle Refactorings sollte man einfach machen und sich nicht durch Zeitdruck oder Angst vor neuen Bugs davon abhalten lassen.
Matthias Berth