Softwarearchäologie: Eine praktische Methode zum Verstehen von Altcode
Erlernen Sie einen schrittweisen Ansatz zur sicheren Untersuchung undokumentierter, veralteter Codebasen – von der Analyse der Commit-Geschichte bis zum Refactoring, ohne die Produktivumgebung zu beeinträchtigen.
Es gibt eine bestimmte Art von Angst, die jeder Entwickler irgendwann erlebt.
Man öffnet eine Datei – sie umfasst über 4.000 Zeilen. Es ist kein einziger Kommentar zu sehen, die Hälfte der Variablen trägt Namen wie x2 oder tempFinal_REAL, und irgendwo in der Mitte befindet sich eine Funktion namens doStuff(), die, soweit man erkennen kann, still und heimlich für die Abrechnungen in der ganzen Firma verantwortlich ist.
Man führt git blame durch – die Spur führt zu jemandem, der das Unternehmen vor sechs Jahren verlassen hat. Eine Suche in Slack ergibt nichts darüber, warum all das überhaupt existiert. Die einzige Person, die sich vielleicht noch erinnern könnte, ist im Urlaub, und ehrlich gesagt wäre sie wahrscheinlich auch mit den Details nicht mehr ganz sicher.
Das ist Softwarearchäologie.
Es wird in keiner Organisationsstruktur oder Stellenausschreibung erwähnt, aber wenn Sie bereits ein oder zwei Jahre damit verbracht haben, Software zu entwickeln, haben Sie es bereits praktiziert. Sie haben alte Codeabschnitte genauso durchforstet wie ein Feldarchäologe eine Ausgrabungsstätte – langsam, sorgfältig und in dem Versuch, herauszufinden, was die ursprünglichen Entwickler eigentlich gedacht haben, obwohl sie längst nicht mehr da sind und nichts mehr klären können.
Was folgt, ist ein Leitfaden dafür, diese Arbeit gut zu erledigen – ohne den Verstand zu verlieren oder dabei die Produktivität zu beeinträchtigen.
Was ist Softwarearchäologie eigentlich?
Softwarearchäologie bedeutet, alte oder undokumentierte Codeabschnitte zu untersuchen, zu interpretieren und zu verstehen, meist damit man sie sicher warten, erweitern oder schließlich ersetzen kann.
Es handelt sich um eine andere Art der Arbeit als das übliche Debuggen. Beim Debuggen geht man davon aus, dass man das System versteht und etwas darin kaputtgegangen ist. Die Softwarearchäologie beginnt mit dem gegenteiligen Ansatz – man versteht das System noch überhaupt nicht, und die eigentliche erste Aufgabe besteht darin, dieses Verständnis aufzubauen, bevor man etwas ändern darf.
Das ist der Unterschied zwischen einem Mechaniker, der ein Automodell wartet, an dem er bereits seit Jahren arbeitet, und einem, der einen 1962er Peugeot restauriert, den er noch nie zuvor geöffnet hat. Dasselbe Schraubenset – völlig unterschiedliche Denkweise.
Die meisten Ingenieure wählen diese Tätigkeit nicht freiwillig – sie geraten einfach hinein. Man beginnt einen neuen Job, und sechs Wochen später reicht jemand einem ein Ticket bezüglich „des alten Inventarservices“. Plötzlich schreibt man keinen neuen Code mehr – man führt Ausgrabungen durch.
Warum diese Fähigkeit wichtiger ist, als die Leute zugeben
Es gibt eine unangenehme Tatsache: Der Großteil der Software, die die Welt tatsächlich antreibt, ist nicht neu. Sie ist alt, über Jahre hinweg zusammengeschustert worden, nur teilweise verstanden und stellt für diejenigen, die nominell dafür verantwortlich sind, stillschweigend eine Quelle der Sorge dar.
Banken nutzen immer noch COBOL-Programme, die in den 1970er Jahren geschrieben wurden. Fluggesellschaften leiten Flüge über Systeme, die älter sind als die Piloten, die diese Flugzeuge fliegen. Selbst Start-ups, die in vollem Tempo arbeiten, sammeln innerhalb von ein oder zwei Jahren veralteten Code an – Code, der unter Zeitdruck von jemandem zusammengestellt wurde, der inzwischen zu einem anderen Team gewechselt hat, um ein Problem zu lösen, das nirgends schriftlich festgehalten wurde.
Falls das Schreiben neuer Code das Einzige ist, was Sie können, sind Sie auf neue Projekte beschränkt. Doch wenn Sie alten Code wirklich lesen können – so wie ein Detektiv eine Tatortstelle untersucht – werden Sie zur Anlaufstelle für Ingenieurteams, wenn etwas Schwieriges repariert werden muss. Das ist ein professioneller Vorteil, über den selten offen gesprochen wird.
Die Denkweise des Archäologen
Bevor es um konkrete Techniken geht, gibt es eine Perspektivänderung, die alles danach erleichtert.
Gehen Sie davon aus, dass der Code zu irgendeinem Zeitpunkt für jemanden sinnvoll war.
Diese eine Neubetrachtung ist wichtiger als jede Technik auf dieser Liste. Wenn man sich verworrenen, unverständlichen Code ansieht, fällt es leicht zu dem Schluss zu kommen, dass der Verfasser nicht wusste, was er tat. Das ist fast nie die wahre Geschichte. Viel häufiger gab es einen nahenden Termin, eine Einschränkung, die man heute nicht mehr sieht, eine Systemanforderung, die inzwischen verschwunden ist, oder einen Anforderungsstand, der im Jahr 2016 völlig vernünftig war und seitdem einfach nicht mehr überprüft wurde.
Stellen Sie sich vor, Sie betreten ein altes Haus und entdecken dort in einer seltsamen, scheinbar willkürlichen Stelle eine Tragstrebe. Sie scheint keinen Zweck zu erfüllen – bis man erfährt, dass dort früher eine Wand stand, die abgerissen wurde und diese Strebe als Einzige verhinderte, dass das Dach einstürzte. Legacy-Codebasen sind voller solcher Streben. Bevor Sie irgendetwas anfassen, müssen Sie herausfinden, was jede von ihnen still und heimlich stützt.
Diese Denkweise bringt zwei Vorteile mit sich: Sie hält einen bei den eigenen Annahmen bescheiden und ersetzt Frustration durch Neugier. Tatsächlich ist Neugier ein weitaus effektiveres Werkzeug zum Fehlerbeheben als Ärger jemals sein könnte.
Techniken zur Analyse von Legacy-Code
1. Die Commit-Geschichte wie ein Tagebuch lesen
Die Git-Geschichte ist das Beste, was man an eine echte Zeitmaschine herankommt. Prüfen Sie nicht nur den aktuellen Zustand einer Datei – verfolgen Sie stattdessen, wie sie dorthin gelangt ist.
Durch Ausführung von git log --follow auf einer bestimmten Datei kommt oft eine echte Geschichte zum Vorschein: Eine Funktion, die in letzter Minute vor einer wichtigen Kundenpräsentation eilig hinzugefügt wurde, eine überstürzte Korrektur, die spät am Freitagabend eingepflegt wurde, oder ein Kommentar mit der Aufschrift „Temporärer Workaround, nach dem Q3-Start entfernen“, der inzwischen vier Jahre alt ist.
Ich stieß zufällig auf eine seltsame if-Anweisung, die genau einen Kunden-ID-Fall behandelt, ohne jegliche Erklärung. Eine Überprüfung mit git blame zeigte eine Notiz vom Verfasser, in der er erklärte, dass die Produktionsdaten eines bestimmten Kunden einen Tippfehler enthielten, und dass die Prüfung nur dazu dienen sollte, alles zusammenzuhalten, bis dieser Kunde seinen Fehler behebt hatte. Diese vorübergehende Lösung blieb fünf Jahre lang stillschweigend in Kraft. Das Verständnis der Hintergründe veränderte schließlich die Art und Weise, wie das Team sie entfernte – sie gingen vorsichtig vor, durchführten eine ordnungsgemäße Datenmigration, anstatt einfach die Prüfung zu löschen und zu hoffen, dass nichts kaputtgeht.
2. Folgen Sie den Daten, nicht nur dem Code
Der Code zeigt Ihnen, was *könnte* passieren. Die Daten zeigen Ihnen, was *tatsächlich* passiert ist.
Gehen Sie direkt zur Datenbank und prüfen Sie sie. Schauen Sie sich die tatsächlichen Datensätze an. Wenn eine Tabelle eine status-Spalte mit Werten wie 1, 2, 7 und 99 enthält, versuchen Sie nicht, ihre Bedeutung ausschließlich durch das Lesen des Codes zu ermitteln – holen Sie stattdessen die tatsächlichen Einträge für jeden Wert ab und verfolgen Sie, was in der Praxis mit ihnen geschehen ist.
Betrachten Sie ein type-Feld in einer alten Bestelltabelle, bei der die Codebasis nur Werte von 1 bis 5 berücksichtigte, während die Produktionsdaten Tausende von Zeilen mit type = 0 zeigten. Es stellte sich heraus, dass 0 bedeutete „vor dem Bestehen des type-Feldes erstellt“ – ein Teil der Systemgeschichte, der zwar aus dem aktuellen Code verschwunden war, aber weiterhin in den Daten deutlich sichtbar vorhanden war.
3. Sprechen Sie mit den „Geistern“ (auch bekannt als die noch anwesenden Personen)
Auch wenn die Person, die einst einen Teil des Systems entwickelt hat, längst nicht mehr da ist, besitzt in der Regel jemand in der Nähe einen Teil des Kontexts – sei es die Person, die diese Person ursprünglich eingestellt hat, der Support-Engineer, der seit Jahren damit befasst ist, oder der Produktmanager, der sich noch an „den Vorfall“ erinnert.
Stellen Sie statt allgemeiner Fragen eher spezifische, weniger druckvolle Fragen. „Was macht dieser Code?“ lädt zum Raten ein, weil es zu offen formuliert ist. „Erinnern Sie sich an etwas Ungewöhnliches bezüglich der Handhabung von Rückerstattungen durch die Abrechnungsabteilung um das Jahr 2021?“ ist konkret genug, um tatsächliche Erinnerungen hervorzurufen.
4. Erstellen Sie eine Karte, bevor Sie irgendetwas anfassen
Archäologen beginnen niemals zufällig mit dem Ausgraben – sie kartieren zunächst den Fundort. Wenden Sie dieselbe Disziplin auf eine Codebasis an.
Ziehen Sie, auch wenn es nur grob ist, auf Papier oder in einer Dokumentation den tatsächlichen Ablauf der Datenströme durch das System nach: Welche Komponenten lösen welche anderen aus, welche Teile speichern Informationen ab und auf welche anderen Komponenten sind welche angewiesen. Es geht nicht darum, ein perfektes Diagramm zu erstellen – es reicht aus, ein genügend detailliertes Bild zu haben, damit unerwünschte Probleme nicht mehr auftreten.
Ein nützlicher Trick: Wählen Sie eine konkrete Handlung aus der Praxis, wie zum Beispiel „Ein Benutzer kündigt sein Abo“, und verfolgen Sie sie von Anfang bis Ende, wobei Sie jede Datei und Funktion notieren, durch die sie geht. Durch das Verfolgen dieser einen Ablaufkette wird oft bereits der Großteil dessen sichtbar, was Sie über das gesamte System wissen müssen.
5. Schreiben Sie Tests, bevor Sie refaktorisieren
Falls eine Codebasis überhaupt keine Tests enthält – was in Legacy-Systemen häufig der Fall ist – widerstehen Sie dem Drang, alles auf einmal zu reparieren. Schreiben Sie stattdessen sogenannte Charakterisierungstests – Tests, die lediglich festhalten, was der Code derzeit tut, unabhängig davon, ob dieses Verhalten korrekt ist.
Dadurch erhalten Sie gleich zwei Vorteile: ein Sicherheitsnetz für zukünftige Änderungen sowie erzwungene Klarheit, da Sie das aktuelle Verhalten nur dann genau beschreiben können, wenn Sie es tatsächlich verstehen. Der größte Wert liegt hier im Akt des Schreibens der Tests und nicht nur in ihrem Ausführen.
6. Ändern Sie immer nur eine Sache auf einmal
Sobald Sie ein unübersichtliches System endlich verstanden haben, besteht die Versuchung, das Ganze in einem einzigen, umfassenden Pull Request neu zu schreiben. Tun Sie das nicht.
Legacy-Systeme sind in der Regel anfälliger, als es auf den ersten Blick scheint – genau deshalb, weil keine einzelne Person das vollständige Bild aller Abhängigkeiten besitzt. Kleine, rückgängig machbare Änderungen – nacheinander vorgenommen und jede einzeln überprüft, bevor man fortfährt – sind der Weg, um zu vermeiden, dass aus dem System ein weiteres rätselhaftes Ergebnis entsteht, über das sich zukünftige „Archäologen“ den Kopf zerbrechen müssen.
7. Dokumentieren Sie, was Sie herausfinden – für die nächste Person
Alles, was Sie herausfinden und verstehen können, lohnt es sich zu bewahren. Schreiben Sie es irgendwo auf. Selbst ein kurzer, informeller, unvollständiger Dokument mit einem Titel wie „Wie der Legacy-Billing-Service tatsächlich funktioniert“ ist ein Geschenk für die Person, die nach Ihnen diesen Code übernimmt – und das könnte sehr wohl Sie selbst in sechs Monaten sein, wenn Sie alles vergessen haben.
Eine kurze Geschichte: Die Funktion, die niemand verstand
In einem Unternehmen hatte eine bestimmte Funktion den Spitznamen „das Ungeheuer“ bekommen – 900 Zeilen, tief im Zahlungsabwicklungsprozess versteckt, an denen sich niemand zu schaffen machen wollte. Neue Mitarbeiter wurden bei ihrer Einarbeitung davor gewarnt, teilweise als Scherz, teilweise als echte Warnung wie in lokalen Volksmärchen.
Irgendwann beschloss jemand, diese Funktion stattdessen gründlich zu analysieren, anstatt sie zu meiden. Anstatt einen Neuschreibversuch zu unternehmen, verfolgten sie die tatsächlichen Transaktionen während ihres Ablaufs in der Funktion, kartierten jeden logischen Pfad und schrieben Charakterisierungstests, um jeden möglichen Weg abzudecken. Der gesamte Prozess dauerte etwa eine Woche.
Was sie entdeckten, war keine Unordnung – es handelte sich um ein durchaus kohärentes System zur Handhabung von fünf verschiedenen Zahlungsdienstleistern, von denen jeder seine eigenen Besonderheiten und Randfälle hatte. Es wurde von jemandem entwickelt, der unter echten Einschränkungen reale, konkrete Probleme löste, ohne die Möglichkeit, danach noch alles zu ordnen. Sobald es vollständig kartiert war, verlor die Funktionsweise ihren beängstigenden Charakter. Sie blieb zwar komplex, doch nun handelte es sich um eine Komplexität, die tatsächlich verstanden werden konnte.
Im Grunde genommen besteht die gesamte Disziplin darin, Angst in eine Karte umzuwandeln.
Fazit
An der Softwarearchäologie ist nichts Glanzvolles. Niemand listet „fähig, den verwirrenden Code anderer zu entschlüsseln“ als Schlüsselkompetenz in seinem Lebenslauf auf. Dennoch bleibt sie eine der nützlichsten – und zugleich am meisten vernachlässigten – Fähigkeiten in der Softwareentwicklung.
Alter Code sollte nicht einfach als Müll abgetan werden – er ist ein Zeugnis von Entscheidungen, die unter Druck und mit Einschränkungen getroffen wurden, von denen man möglicherweise nie die volle Kenntnis hat. Gehen Sie damit mit Neugier statt mit Urteil um, und Sie werden sicherere Änderungen vornehmen können, wobei der Prozess vermutlich viel lohnender ist, als Sie erwarten.
Nächstes Mal, wenn ein bestimmter Datei Sie dazu veranlasst, den Laptop zuzuklappen und lieber nach draußen zu gehen, halten Sie kurz inne und atmen Sie tief durch. Was Sie da sehen, ist kein Schutt – es handelt sich um einen Ausgrabungsort, der erst verstanden werden muss.
Nehmen Sie einen Pinsel, nicht eine Baggermaschine.
Verwandte Artikel
- Neun Code-Ebene-Gewohnheiten, die das Arbeiten von Senior-Engineers vertrauenswerter machen — Es werden neun konkrete Programmierpraktiken erläutert – von Schutzklauseln bis hin zu strengem Datenmodellieren –, die den Code widerstandsfähiger, lesbarer und unter Druck leichter zu debuggen machen.
- Zehn wiederkehrende JavaScript-Gewohnheiten, die heimlich Ihre Codebase untergraben — Es werden zehn häufige Fehlerquellen in JavaScript und TypeScript erläutert, von lockerer Gleichheit bis hin zu Zustandsmutationen, und sicherere Muster vorgestellt, um jede davon zu ersetzen.