Niemand will mit Legacy Code arbeiten. Die Gründe sind vielfältig und nachvollziehbar, generell ist Legacy Code auch sehr uncool und häufig ist gar nicht klar, wie damit umgegangen werden soll.
Definition Legacy Code
Bei der Definition, was denn Legacy Code überhaupt ist, gibt es viele Meinungen. Oft wird darunter alter Code verstanden, oder Code, der von nicht mehr im Unternehmen befindlichen Entwicklern geschrieben wurde. In manchen Fällen wird auch der Sourcecode einer älteren Version als Legacy Code bezeichnet. Beliebt ist diese Bezeichnung auch für Code, deren Design/Architektur als nicht-optimal empfunden wird.
Ich sehe das viel pragmatischer und stimme diesbezüglich mit Michael C. Feathers überein: Er bringt es in seinem Buch Working Effectively with Legacy Code auf folgenden Punkt:
The main thing that distinguishes legacy code from non-legacy code is tests, or rather a lack of tests. (Michael C. Feathers)
Code ohne automatisierten Tests ist Legacy Code. So einfach. Dabei ist es vollkommen egal, wann der Code geschrieben wurde und wer das getan hat. Kann er nicht automatisiert getestet werden bzw. wird er es nicht, ist es Legacy Code. Punkt.
Legacy Code verringern
Mit obiger Definition ist es doch einfach, Legacy Code zu entfernen. Es gilt einfach Tests zu schreiben. Zahlreiche Bücher beschreiben zudem, wie man denn Legacy Code umbaut und getestet bekommt. Kaum ein Buch beschreibt allerdings, wie dies im “laufenden Betrieb” möglich ist.
Wir müssen Bugs fixen, Features implementieren, wir haben keine Zeit für zusätzlichen Aufwand.
Die Realität sieht dann aber oft anders aus. Häufig macht alter, ungetesteter Code in großen Mengen viele Probleme. Neue Implementierungen an einer Stelle führen zu Fehlern auf einer ganz anderen (natürlich völlig unerwarteten) Stelle. Die Probleme treten allerdings erst beim Kunden auf, wodurch Bugfixes dringend durchgeführt werden müssen, wodurch wiederum andere Arbeit liegen bleibt. So baut sich zusätzlicher Druck auf. Als Ergebnis werden Abkürzungen genommen, also weniger Wert auf ein sinnvolles Design gelegt, die Stabilität leidet. Die Software wird immer anfälliger und alle Beteiligten investieren immer mehr Zeit in “lebenserhaltende Maßnahmen”.
Wie kommt man nun aus dieser Abwärtsspirale heraus, wenn Zeit rar ist?
Ich sage es mal so: Nichts tun und auf zusätzlichen Aufwand herausreden, oder dass ein Testing gar nicht möglich ist, bringt definitiv keine Verbesserung. Im Gegenteil, es wird immer schlimmer. Man muss also Hand anlegen und aktiv an einer Verbesserung arbeiten.
Stete Verbesserung notwendig
Monatelanges Refactoring und das, über die Jahre, versäumte Aufholen von Tests wird (meist) nicht möglich sein. Kaum jemand akzeptiert (finanziell) monatelanges Entwickeln “ohne sichtbaren Output”. Was aber durchaus möglich ist: Für einen Bugfix können Tests geschrieben werden, gegebenenfalls ist auch ein Refactoring kleinerer Codestellen möglich. Das ist vielleicht nicht viel, macht aber eine Codestelle schon sehr viel sicherer.
Lebt das jeder im Team, gibt es über Monate hinweg viele neue Tests. Das Ergebnis ist eine verbesserte Stabilität. Natürlich ist der Anfang schwer und man glaubt nur durch ein großes Refactoring eine Verbesserung herbeiführen zu können. Die Macht von vielen kleinen Verbesserungen darf man nicht unterschätzen.
Nicht einschüchtern lassen
Vielfach werden diese kleinen Refactorings mit Tests nicht durchgeführt, da der Entwickler der Meinung ist, diese Änderung würde nichts bringen. Das mag durchaus so erscheinen, wenn man 100.000e oder gar Millionen Sourcecode-Zeilen vor sich hat. Aber nehmen wir ein kleines Rechenbeispiel:
Jeden Tag können für 200 Zeilen Code Tests geschrieben werden (um das Funktionieren von Bugfixes zu verifizieren). Das Team hat 5 Mitglieder. Dann sprechen wir am Tag von 1000 Zeilen getesteten Code – und das ist recht gering angesetzt. Grob hochgerechnet reden wir dann von 200.000 getesteten Sourcecode-Zeilen nach einem Jahr, oder ca. 13% der Software, wenn das Projekt in Summe 1,5 Millionen Zeilen Code umfasst.
Mehr getesteter Code bedeutet weniger Bugmeldungen == höhere Kundenzufriedenheit und mehr Zeit für wertschöpfende Maßnahmen.
Nun gut, man könnte nun behaupten, dass es ja 7,5 Jahre bedarf, damit die gesamte Software getestet ist. Das ist wohl wahr, vergessen werden darf jedoch nicht, dass für diese getesteten Stellen Fehler sofort aufgedeckt werden können und daher gefixt werden, bevor der fehlerhafte Sourcecode an den Kunden ausgeliefert wird. Wir haben also nach einem Jahr vermutlich um 10% weniger Fehlermeldungen von Kunden und entsprechend weniger Belastung. In dieser verbleibenden Zeit können entweder neue Features entwickelt werden (was dem Unternehmensergebnis direkt zuträglich wäre) oder aber vermehrt in die Stabilität der Software investieren.
Dass darüber auch die Kundenzufriedenheit gesteigert wird (und vermutlich eher weitere Aufträge nach sich zieht), versteht sich von selbst.
Neue Funktionen nicht ohne Tests
Für neue Funktionen sind verpflichtend (sinnvolle) Tests zu schreiben. Daher wird alles Neue schon testbar aufgebaut und die Wahrscheinlichkeit von durchgerutschten Fehlern ist gering. Zu Beginn ist durchaus etwas mehr Zeit für das Schreiben der Tests anzuberaumen. Der notwendige Aufwand verringert sich durch die aufgebaute Erfahrung aber sehr schnell.
Damit das wirklich funktioniert, muss der Aufwand für Tests mitgeschätzt werden. So kommt der Entwickler nicht zusätzlich unter Druck. Druck würde an dieser Stelle nur dazu führen, dass auch für neuen Code keine Tests geschrieben werden, wir also wieder in unser altes Schema verfallen.
Grundvoraussetzung
Entwickler sind am Ende einer langen Wertschöpfungskette. Dadurch ist der Druck besonders hoch. Vor allem, wenn schwerwiegende Probleme auftreten. Generell haben Probleme sehr schnell gelöst zu werden und Features müssen ohnehin sofort implementiert werden.
Damit Schwierigkeiten in der Stabilität, erhöhter Support-Aufwand und damit hohe “unproduktive” Kosten gesetzt werden können, muss die Entwicklung von allen unterstützt werden. Gerne werden für Probleme auch Workarounds gefunden, die den Kunden zwar befriedigen, für die eigene Software allerdings keine Lösung bringen und das Problem nur verschleiern, oder über die Zeit noch schlimmer werden lassen.
Natürlich ist dabei auch Geduld gefragt. Was über Jahre hinweg versäumt wurde, kann nicht in wenigen Tagen oder Wochen behoben werden. Erste Erfolge sind wohl Monate entfernt, aber sie kommen sicher und sind – das ist das Schlimme an dieser Geschichte – nicht sofort ersichtlich. Es sind die Probleme, die nicht mehr auftreten, aber genau deshalb werden sie auch nicht mehr wahrgenommen. Die produktiven Stunden nehmen allerdings zu und daran kann man eine Verbesserung durchaus messen.
Fazit
Legacy Code ist Sourcecode, für den keine automatisierten Tests bestehen. Es bedarf etwas Mut und Wille um Tests zu schreiben, notwendige Umbauten durchzuführen. Auch wenn dies mühsam und kaum schaffbar erscheint, selbst die kleinste Verbesserung ist eine Verbesserung. Viele kleine Verbesserungen werden über die Zeit zu einer großen Verbesserung. Es gilt also, einfach zu starten, so aussichtslos es auch erscheinen mag, denn nur so kann die Software überhaupt besser werden. Nichtstun macht alles schlimmer. Handle jetzt und sorge für Verbesserungen. Jede noch so kleinste Verbesserung hilft. Alle im Unternehmen sitzen im selben Boot.
Update 13.12.2020: Überarbeitung und Ausbesserungen
Erstveröffentlichung: 23.10.2016
> Mit obiger Definition ist es doch einfach, Legacy Code zu entfernen.
In der Tat, denn das Problem wurde wegdefiniert.
Und wenn man nun eine große Menge Legacy Code nimmt und ihn vollständig automatisiert testet, dann hat sich an dem Code erst mal nichts geändert und er behält dieselben Probleme wie vorher. “Legacy” beschreibt ein Problem und es ist nicht ein Mangel an Tests.
Und wenn man zusätzlich den Code noch refactored, so dass er testbar wird (also alle Komponenten von ihrer Außenwelt isoliert), dann hat sich architekturell nicht sehr viel geändert und man hat nach wie vor die typischen Probleme, die man mit Legacy Code hat.
Legacy scheint schwer zu definieren aber jeder merkt’s, wenn er “Legacy” vor sich sieht.
Das Einführen von automatisierten Tests geht ohne Codeveränderung/Refactoring in der Regel nicht, da gerade auf Testbarkeit keine Rücksicht genommen wurde. Der Code muss also angepasst werden und kann so – wenngleich meist auch nur in kleineren Schritten – verbessert werden. Tests stellen ein Funktionieren sicher. Wenn zumindest die kritischen Teile der Software automatisiert getestet werden, dann kann man auch an der Architektur drehen, vorher ist das ein Blindflug und verschlimmert die Situation wohl nur.
Im Grunde ist es auch egal wie man “Legacy” definiert. Ohne Einführung von automatisierten Tests kommt man aus dieser Ecke nicht mehr raus. Was es allerdings alles bedarf um überhaupt nicht in diese Situation zu kommen, wäre Stoff für einen weiteren Beitrag …