Technical Digest: Refactoring - Zur richtigen Zeit, im richtigen Maß

von Steven Collins

Ich erlebe es häufig, dass Teams sich unsicher sind, wann der richtige Zeitpunkt zum Refactoring ist. Sie sind hin und hergerissen zwischen der Umsetzung neuer Features, welche Mehrwert für die Kunden bringen, und der Pflege des Bestandsystems. Selbst wenn die Entscheidung getroffen wurde, in die Wartung der Software zu investieren, stellt sich weiterhin die Frage, wie und in welcher Reihenfolge man die Refactoring-Schritte einplant. Dieser Artikel gibt meine Erfahrungen wieder, was ein geeignetes Vorgehen für Refactoring-Maßnahmen ist, wenn die Entscheidung bereits getroffen wurde, eine bestimmte Stelle im System zu refaktorisieren. Der Artikel befasst sich nicht damit, wie man allgemein mit technischer Schuld umgeht oder wie man die Weiterentwicklung und die Wartung eines Systems ausbalanciert.

Was bedeutet Refactoring?

Refactoring ist die Verbesserung der internen Struktur bei Beibehaltung der bestehenden, beobachtbaren Funktionalität. Ein Refactoring kommt also dann zum Einsatz, wenn das Softwaresystem bereits so arbeitet wie gewünscht, die interne Struktur aber die Weiterentwicklung des Systems behindert. Das kann aus verschiedenen Gründen der Fall sein. Beispielsweise kann der Code so schlecht sein, dass man nicht mehr versteht, wie man in dessen Struktur neue Features einbauen soll. Oder der Betrieb des Systems kann durch eine schlechte interne Struktur so aufwendig sein, dass nur wenig Zeit für neue Features bleibt.

Wie können Refactorings eingeplant werden?

Refactorings sind notwendiger Waste: Sie bringen dem Endkunden keinen unmittelbaren Nutzen, stellen aber sicher, dass das System auch in Zukunft noch nützlich für den Endkunden bleibt. In Scrum gehören Refactorings deshalb nicht ins Product Backlog. Das Team ist selbstorganisiert und muss dementsprechend selbst erkennen, welche Aufgaben bis wann erledigt werden müssen, damit das System wartbar bleibt und weiterentwickelt werden kann. Da Refactorings aber mitunter erhebliche Auswirkungen auf die Gesamtplanung haben, sollten technische Aufgaben wie Refactorings meiner Meinung nach ebenso geplant und geschätzt werden, wie User Stories aus dem Product Backlog.

Es ist erstrebenswert, wenn man ein großes Refactoring in kleine, technisch gewinnbringende Teilrefactorings aufteilt, um schneller einen technischen Mehrwert zu schaffen und flexibel auf Änderungen, neue Erkenntnisse, oder veränderte Prioritäten reagieren zu können. So kann ein Refactoring auch leichter und günstiger zurückgenommen werden, wenn man merkt, dass das Refactoring in die falsche Richtung läuft. Gibt es neue, dringende Anforderungen, besteht die Optionen, die weiteren Teilrefactorings in spätere Sprints zu verschieben, ohne Nachteile in Kauf nehmen zu müssen.

Kann man große Refactoring nicht in Teilrefactorings schneiden, die für sich allein nützlich sind, entsteht der technische Nutzen erst nach Abarbeitung aller Teilrefactorings. Während der Entwicklung verschlechtert man häufig den technischen Zustand des Systems zusätzlich, da so lange das Refactoring noch im Gange ist, Indirektion gebaut werden müssen, die den alten Code mit dem neuen Code verbinden. Je mehr dieser Indirektionen gezogen werden, desto mehr wird die Komplexität des Codes erhöht. Trotz dieser Nachteile empfehle ich, das Refactoring dennoch in Sprint-gerechte Teile aufspalten. Die Teilrefactorings dienen als Messpunkte, anhand denen man ablesen kann, ob die ursprüngliche Schätzung des Aufwands zutrifft und wie es mit dem Fortschritt des Gesamtrefactorings bestellt ist. Den Aufwand mehrerer kleinerer Aufgaben zu schätzen ist außerdem leichter, als den Aufwand einer sehr großen Aufgabe. Es gilt nur besonders darauf zu achten, dass Teilrefactorings nicht über einen längeren Zeitrum verteilt werden.

Zu welchem Zeitpunkt sollte das Refactoring eingeplant werden?

Kleinere Refactorings sind obligatorisch in der agilen Softwareentwicklung und werden deshalb zu jedem Zeitpunkt durchgeführt. Refactorings, die ungeplante Arbeiten reduzieren oder Betriebskosten senken, haben einen klaren Geschäftswert und können somit recht einfach gegen andere fachlichen User Stories priorisiert werden. Spannender ist die Frage, zu welchem Zeitpunkt man strategische Refactorings durchführt. Meiner Meinung nach macht Sinn, ein Refactoring in unmittelbar vor einer fachlichen Story zu priorisieren, welche das Refactoring motiviert. Der Grund ist einfach: Wenn der Code vor dem fachlichen Feature refaktorisiert wird, bezahlt sich das Refactoring quasi von selbst, da durch das Refactoring der Code so vereinfacht wird, dass das Feature schneller umgesetzt werden kann. Man produziert dadurch auch keinen Code auf Vorrat, da man immer mindestens eine Stelle durch das Refactoring profitiert, im optimalen Fall gleich mehrere.

Ich rate davon ab, im Anschluss an eine Story ein Refactoring durchzuführen, welches auf eine zukünftige Verbesserung oder Nutzung spekuliert, um das Risiko einer Fehleinschätzung und somit unnütze Arbeit zu vermeiden. Bei Abschluss einer User Story reicht es stattdessen aus, dass der Code einige Grundanforderungen von sauberer Programmierung genügt, beispielsweise klare Kommunikation des Designs, Reduktion von Kopplung und Redundanzen, einheitliche Abstraktionsebenen, angemessene Längen von Funktionen, etc. Durch diese geradezu mechanischen Aktivitäten am Ende, formt sich ein Design von ganz allein, das in den meisten Fällen besser ist, als man es im Voraus planen und entwerfen könnte.