Legacy Code hat einen schlechten Ruf. Dabei ist es funktionierende Software, die Produktion überlebt hat, Edge Cases behandelt, die niemand dokumentiert hat, und sich ihre Komplexität ehrlich verdient hat. Aber mit Legacy Code zu arbeiten kann schmerzhaft sein — besonders wenn es keine Tests, keine Dokumentation gibt und die ursprünglichen Entwickler längst weitergezogen sind.
Im letzten Jahr habe ich Claude Code intensiv eingesetzt, um Legacy-Codebasen zu modernisieren. Nicht als Zauberstab, der alles auf einmal umschreibt, sondern als sorgfältiger Partner, der mir hilft, Code zu verstehen, zu testen und schrittweise zu verbessern. Hier ist, was ich gelernt habe.
Schritt Null: Verstehen vor dem Anfassen
Der größte Fehler beim Refactoring von Legacy Code ist, direkt mit Änderungen loszulegen. Mit Claude Code gibt es keine Ausrede, die Verstehensphase zu überspringen.
Wenn ich auf ein unbekanntes Modul stoße, lasse ich es mir zunächst von Claude Code erklären:
claude "Erkläre den Datenfluss in src/services/orderProcessing.js —
was sind Inputs, Outputs, Seiteneffekte und Fehlerpfade?"
Claude Code liest die gesamte Datei (und ihre Imports) und erstellt eine strukturierte Erklärung. Das ist um Größenordnungen schneller, als sich manuell durch Callback-Ketten zu hangeln. Bei tief verschachteltem Legacy Code mit impliziten Abhängigkeiten spart allein dieser Schritt Stunden.
Tipp: Frag gezielt nach Seiteneffekten. Legacy Code liebt versteckte Mutationen — globale Zustandsänderungen, Datenbankschreibvorgänge in Utility-Funktionen, Event-Emitter, die kaskadierende Updates auslösen. Claude Code ist hervorragend darin, diese aufzudecken.
CLAUDE.md für Legacy-Projekte erstellen
Bevor ich mit Refactoring beginne, erstelle ich eine CLAUDE.md-Datei im Projekt-Root. Das ist die Kontextdatei von Claude Code, und für Legacy-Projekte ist sie unschätzbar wertvoll:
# CLAUDE.md — Legacy Order System
## Architektur
- Express.js-Monolith, keine Framework-Konventionen
- PostgreSQL über rohe `pg`-Queries (kein ORM)
- Hintergrund-Jobs via node-cron (siehe src/jobs/)
## Bekannte Fallstricke
- `req.user` wird durch Custom Middleware in src/auth/legacy-auth.js befüllt
- Die `processOrder`-Funktion in src/services/orders.js hat Seiteneffekte:
sie sendet E-Mails UND schreibt ins Audit-Log
- Umgebungsvariablen werden direkt zugegriffen (kein Config-Modul)
## Refactoring-Regeln
- Niemals Funktionssignaturen ändern, die vom API-Layer genutzt werden
- Immer Tests hinzufügen, bevor eine Funktion geändert wird
- In neuem Code async/await gegenüber Callbacks bevorzugen
Diese Datei fungiert als institutionelles Wissen, das über Sessions hinweg bestehen bleibt. Jedes Mal, wenn Claude Code an diesem Projekt arbeitet, liest es zuerst CLAUDE.md und respektiert die Einschränkungen. Ich aktualisiere sie, während ich mehr über die Codebase lerne.
Die goldene Regel: Tests vor Änderungen
Legacy Code ohne Tests ist ein Minenfeld. Bevor ich irgendetwas refactore, nutze ich Claude Code, um Testabdeckung für das bestehende Verhalten zu generieren:
claude "Schreibe Integrationstests für die processOrder-Funktion in
src/services/orders.js. Decke den Happy Path, ungültige Eingaben
und den Fall ab, dass der E-Mail-Service nicht erreichbar ist.
Nutze Jest. Mocke Datenbank und E-Mail-Service."
Claude Code liest die Implementierung, identifiziert die Verzweigungen und generiert Tests, die das aktuelle Verhalten festhalten — inklusive der Eigenheiten. Diese Tests werden mein Sicherheitsnetz. Wenn ein Refactoring etwas kaputt macht, fangen die Tests es sofort auf.
Das ist das wichtigste Muster, das ich gefunden habe: Zuerst das bestehende Verhalten testen, dann mit Zuversicht refactoren.
Sichere Refactoring-Strategien
Callbacks zu async/await
Legacy-Node.js-Code besteht oft aus einer Pyramide von Callbacks. Claude Code bewältigt diese Migration zuverlässig:
// Vorher: Callback-Hölle
function getUser(id, callback) {
db.query('SELECT * FROM users WHERE id = $1', [id], (err, result) => {
if (err) return callback(err);
const user = result.rows[0];
db.query('SELECT * FROM profiles WHERE user_id = $1', [user.id], (err2, profileResult) => {
if (err2) return callback(err2);
user.profile = profileResult.rows[0];
callback(null, user);
});
});
}
// Nachher: sauberes async/await
async function getUser(id) {
const userResult = await db.query('SELECT * FROM users WHERE id = $1', [id]);
const user = userResult.rows[0];
const profileResult = await db.query('SELECT * FROM profiles WHERE user_id = $1', [user.id]);
user.profile = profileResult.rows[0];
return user;
}
Der Schlüssel ist, das Funktion für Funktion zu machen und nach jeder Änderung die Tests laufen zu lassen. Claude Code versteht die Aufrufkette und kann sagen, welche Aufrufer aktualisiert werden müssen.
Klassenkomponenten zu Hooks
Bei React-Legacy-Codebasen ist die Migration von Klassenkomponenten zu funktionalen Komponenten mit Hooks eine häufige Aufgabe:
claude "Refactore src/components/Dashboard.jsx von einer Klassenkomponente
zu einer funktionalen Komponente mit Hooks. Erhalte alles Verhalten
einschließlich des componentDidMount-API-Aufrufs, der shouldComponentUpdate-
Optimierung und der Error Boundary."
Claude Code wandelt this.state zu useState, componentDidMount zu useEffect um und schlägt vor, wann useMemo oder useCallback als Ersatz für shouldComponentUpdate sinnvoll sind. Es warnt auch, wenn etwas nicht direkt migriert werden kann — wie Error Boundaries, die weiterhin Klassenkomponenten benötigen.
Konfiguration extrahieren
Legacy Code hat oft überall verstreute hardcodierte Werte. Ich nutze Claude Code, um sie zu finden und zu extrahieren:
claude "Finde alle hardcodierten URLs, Ports, Timeouts und Magic Numbers
in src/services/. Erstelle ein config.ts-Modul, das sie zentralisiert
mit Environment-Variable-Overrides."
Das ist eine Aufgabe, die manuell Stunden dauern und fehleranfällig wäre. Claude Code durchsucht systematisch und erstellt ein sauberes Config-Modul.
Umgang mit fehlender Dokumentation
Legacy-Projekte haben selten nützliche Dokumentation. Claude Code kann sie aus dem Code selbst generieren:
claude "Generiere API-Dokumentation für alle Express-Routen in src/routes/.
Inkludiere HTTP-Methode, Pfad, erwarteten Request Body, Query-Parameter,
Authentifizierungsanforderungen und mögliche Response-Codes.
Formatiere als Markdown."
Ich committe diese generierte Dokumentation und verifiziere und korrigiere sie dann manuell. Selbst wenn sie zu 80% korrekt ist, spart sie enorm viel Zeit im Vergleich zu einem Start von Null.
Inkrementell statt Big-Bang
Die Versuchung bei AI-Tools ist, massive Rewrites zu versuchen. Widerstehe dem. Ich folge einem strikt inkrementellen Ansatz:
- Verstehen — ein Modul analysieren (Claude Code erklären lassen)
- Testen — bestehendes Verhalten absichern (Tests mit Claude Code generieren)
- Refactoren — ein Pattern nach dem anderen (Callbacks, Benennung, Struktur)
- Verifizieren — Tests laufen lassen
- Dokumentieren — was sich geändert hat und warum in der Commit-Message
- Wiederholen — für das nächste Modul
Jeder Schritt ist ein kleiner, reviewbarer Commit. Wenn etwas schiefgeht, kann man einen Commit reverten, statt einen massiven Diff zu entwirren.
Was Claude Code nicht ersetzt
Claude Code ist kein Ersatz dafür, das eigene System zu verstehen. Es beschleunigt das Verständnis, aber man muss trotzdem:
- Erklärungen verifizieren — es kann ungewöhnliche Muster fehlinterpretieren
- Architekturentscheidungen treffen — es kann Optionen vorschlagen, aber die Richtung bestimmst du
- Den Business-Kontext verstehen — warum der Code etwas tut, ist genauso wichtig wie was er tut
Legacy-Code-Modernisierung ist ein Marathon, kein Sprint. Claude Code macht jeden Schritt schneller und weniger riskant, aber die Disziplin kleiner, getesteter, inkrementeller Änderungen bleibt bei dir.
Fazit
Claude Code hat grundlegend verändert, wie ich an Legacy-Codebasen herangehe. Die Kombination aus AI-gestütztem Verständnis, Testgenerierung und inkrementellem Refactoring bedeutet, dass ich Code mit Zuversicht modernisieren kann — selbst Code, den ich noch nie gesehen habe. Der Schlüssel ist Geduld: zuerst verstehen, dann testen, dann refactoren. Keine Abkürzungen.


