Legacy-Code mit KI zu refactoren heißt, einem sehr schnellen und sehr selbstsicheren Mitarbeiter genau den Teil deiner Codebasis zu geben, den niemand ganz versteht. Das kann gut gehen. Agenten haben Geduld mit 3.000-Zeilen-Dateien, es macht ihnen nichts aus, eine Funktion an zweihundert Stellen umzubenennen, und sie lesen alten Code, ohne zu seufzen. Es kann aber auch schiefgehen: ein „Aufräumen“, das eine Rundungsregel ändert, einen undokumentierten Seiteneffekt verschwinden lässt oder zu einem Rewrite wird, den niemand mehr reviewen kann.
In diesem Leitfaden geht es um den ersten Fall. Er zeigt, wie du vor der ersten Änderung ein Sicherheitsnetz baust, wie du jeden Schritt klein und umkehrbar hältst, wie du Migrationen wie JavaScript zu TypeScript oder ein Framework-Upgrade durchziehst und wie du prüfst und zurücknimmst, was der Agent getan hat.
Warum Legacy-Code auch für Agenten schwer ist
Legacy-Code ist Code, vor dessen Änderung du Angst hast, meist weil er wenige Tests und viele ungeschriebene Regeln hat. Was ihn für Menschen schwer macht, macht ihn auch für einen Agenten schwer:
- Verhalten steckt an seltsamen Stellen. Ein Datumsformat in einem Helper, ein Retry in einem Decorator, eine globale Variable, die woanders gelesen wird.
- Manche Bugs sind Features. Ein nachgelagertes System hängt vielleicht von einem falschen Wert ab. Wer ihn „repariert“, bricht eine Integration, die er nie gesehen hat.
- Toter Code sieht lebendig aus und lebendiger tot. Reflection, stringbasiertes Dispatching, Cronjobs und Templates rufen Funktionen auf, die keine Suche findet.
- Das Kontextfenster ist endlich. Ein Agent sieht die Dateien, die er liest. Vom Batch-Job in einem anderen Repo, der dieses Modul importiert, weiß er nichts.
Ein Agent hat außerdem eine Angewohnheit, die hier gefährlich ist: Er verbessert Dinge. Bitte ihn, eine Funktion zu extrahieren, und er behebt vielleicht gleich den Randfall, der ihm aufgefallen ist, benennt Variablen um und modernisiert die Syntax. Jede Änderung mag für sich in Ordnung sein. Zusammen ergeben sie einen Diff, bei dem du nicht mehr erkennst, was sich ändern sollte.
Schritt 1: Zuerst das Sicherheitsnetz
Lass den Agenten keine Zeile Produktionscode ändern, bevor das Verhalten, das dir wichtig ist, durch Tests festgehalten ist. Bei Legacy-Code heißt das Charakterisierungstests: Tests, die festhalten, was der Code jetzt tut, nicht was er tun sollte.
Ein Prompt, der funktioniert:
Ändere keinen Code unter
src/. Schreib Charakterisierungstests fürsrc/orders/pricing.js, die das aktuelle Verhalten festhalten, auch alles, was falsch aussieht. Decke jeden Zweig ab, den du findest, auch Fehlerpfade. Wo das Verhalten nach einem Bug aussieht, lass den Test die aktuelle Ausgabe prüfen und füge einen Kommentar hinzu, der mitSUSPICIOUS:beginnt. Führe die Tests aus und zeig mir, dass sie bestehen.
Ein paar Tipps:
- Über die öffentliche Schnittstelle testen. Tests, die das Modul so aufrufen wie der Rest der App, überleben ein Refactoring. Tests, die in private Helper greifen, brechen, sobald du sie verschiebst.
- Echte Eingaben verwenden. Beispiel-Payloads aus Logs (ohne personenbezogene Daten), Fixture-Dateien oder eine Kopie der Staging-Datenbank decken seltsame Fälle viel besser ab als erfundene Eingaben.
- Golden Files sind hier in Ordnung. Bei einem Berichtsgenerator oder einem Export ist es ein schneller und ehrlicher Charakterisierungstest, die ganze Ausgabe aufzuzeichnen und zu vergleichen, solange die Ausgabe deterministisch ist.
- Prüfen, dass die Tests fehlschlagen können. Mach den Code an ein paar Stellen von Hand kaputt oder lass ein Mutation-Testing-Tool über das Modul laufen. Bleiben die Tests grün, hat das Netz Löcher.
Der Leitfaden Unit-Tests mit KI schreiben behandelt Charakterisierungstests, Coverage und Mutation Testing ausführlicher.
Die SUSPICIOUS:-Notizen sind wertvoll. Das sind die Bugs, die du später behebst, mit Absicht, in eigenen Commits und nachdem jemand entschieden hat, dass das neue Verhalten richtig ist.
Schritt 2: In kleinen, umkehrbaren Schritten refactoren
Steht das Netz, halte jeden Schritt klein genug, um ihn in ein paar Minuten zu reviewen und zurückzunehmen, ohne etwas anderes anzufassen.
Struktur und Verhalten trennen
Die nützlichste Regel: Ein Refactoring-Commit ändert nur Struktur, eine Verhaltensänderung bekommt ihren eigenen Commit. Sag das dem Agenten direkt:
Das ist ein reines Refactoring. Ändere kein Verhalten, behebe keine Bugs, ändere keine öffentlichen Signaturen. Wenn dir etwas auffällt, das geändert werden sollte, liste es am Ende auf.
Ändern sich die Charakterisierungstests in einem Refactoring-Commit, stimmt etwas nicht. Daraus ergibt sich eine einfache Prüfung: git diff --stat main -- tests/ sollte keine Änderungen an bestehenden Tests zeigen.
Eine Art von Änderung pro Schritt
Gute Schrittgrößen:
- Eine Funktion oder Klasse extrahieren.
- Ein Modul verschieben und seine Imports anpassen.
- Eine veraltete API in der ganzen Codebasis ersetzen.
- Ein Interface an einer Grenze einführen, damit alter und neuer Code nebeneinander existieren können.
Lass den Agenten nach jedem Schritt die Tests ausführen und mit einer klaren Nachricht committen, bevor er weitermacht. Ein Plan hilft: „Schlag das Refactoring als nummerierte Liste von Schritten vor, die jeweils die Tests grün lassen. Warte auf mein OK, bevor du anfängst.“ Die meisten Coding-Agenten haben dafür einen Planungsmodus; der Plan Mode von Claude Code zum Beispiel erkundet und schlägt vor, ohne zu editieren.
Ablösen statt neu schreiben
Muss ein Teil wirklich ersetzt werden, stell die neue Implementierung hinter einem Interface oder einem Flag neben die alte, zieh die Aufrufer einen nach dem anderen um und lösch den alten Code, sobald ihn nichts mehr nutzt. Das ist das Strangler-Fig-Muster. Es macht aus einem riskanten Umschalten viele kleine, langweilige Schritte, und Langweiliges können Agenten gut.
Schritt 3: Migrationen mit Agenten
Bei Migrationen sparen Agenten die meiste Zeit, weil die Arbeit breit und repetitiv ist. Dort werden die Diffs aber auch riesig. Der Trick: Werkzeuge erledigen den mechanischen Teil, der Agent die Ermessensentscheidungen.
JavaScript zu TypeScript
Ein Weg, bei dem die App die ganze Zeit funktioniert:
- TypeScript neben JavaScript einführen. Setz in der
tsconfig.jsonallowJs: true, damit.js- und.ts-Dateien nebeneinander existieren, und führenpx tsc --noEmitin der CI als Typprüfung aus. - Locker anfangen. Starte mit
strict: false. Schalte optionalcheckJsein oder füge einzelnen JS-Dateien// @ts-checkhinzu, um Probleme zu finden, bevor du sie umbenennst. - Blattmodule zuerst konvertieren. Dateien ohne interne Imports, etwa Utilities und Konstanten, sind am einfachsten. Danach arbeitest du dich zu den Einstiegspunkten hoch.
- Ein Ordner pro Schritt. Bitte den Agenten,
src/utils/nach.tszu konvertieren, Typen hinzuzufügen, die Tests grün zu halten undtsc --noEmitbestehen zu lassen. Dann der nächste Ordner. - Schrittweise verschärfen. Ist alles
.ts, schaltenoImplicitAnyein, dannstrictNullChecks, dann das vollestrict, und behebe dazwischen die Fehler.
Zwei Regeln für den Agenten: kein any ohne Kommentar, der erklärt, warum, und kein @ts-ignore, um Fehler verschwinden zu lassen. Sonst ist der schnellste Weg zu einem grünen Compiler, den Compiler Zeile für Zeile abzuschalten.
Framework- und Bibliotheks-Upgrades
Bei einem Major-Versionssprung zählt die Reihenfolge:
- Zuerst den Migrationsleitfaden lesen. Lass den Agenten den offiziellen Upgrade-Guide zusammenfassen und die Breaking Changes auflisten, die deinen Code betreffen, mit Dateipfaden. Das ist ein reiner Leseschritt.
- Den offiziellen Codemod ausführen, falls es einen gibt. Viele Projekte liefern Codemods, etwa
npx @next/codemodfür Next.js; für Java decken OpenRewrite-Rezepte viele Upgrades ab. Ein Codemod ist deterministisch, ein Agent nicht. Lass den Codemod erledigen, was er kann. - Den Rest dem Agenten überlassen. Die Fälle, die der Codemod nicht geschafft hat, die eigenen Wrapper, die Tests, die sich auf altes Verhalten verlassen haben.
- Eine Major-Version nach der anderen. Wer von 3 auf 6 in einem Sprung geht, stapelt alle Breaking Changes in einen Diff.
Halte das erwartete Ergebnis im Prompt fest: „Der Build läuft durch, die Testsuite besteht, keine neuen Warnungen in der Konsole.“ Ein vages „aktualisiere auf die neueste Version“ lädt den Agenten ein, nebenbei fünf andere Pakete hochzuziehen.
Schritt 4: Die Arbeit mit Worktrees isolieren
Ein langes Refactoring gehört nicht in deinen Hauptarbeitsordner, und parallele Schritte sollten sich keinen teilen. Ein Git-Worktree gibt jedem Schritt einen eigenen Branch und Ordner:
git worktree add ../app-ts-utils -b refactor/ts-utils
git worktree add ../app-ts-api -b refactor/ts-api
Jetzt konvertiert ein Agent utils, ein anderer api, und du arbeitest im Hauptordner weiter. Ist ein Schritt fertig und reviewt, mergst du ihn; führt er ins Leere, entfernst du Worktree und Branch. Mehr dazu in Git-Worktrees für parallele Agenten.
Vorsicht bei parallelen Schritten, die dieselben Dateien anfassen. Zwei Agenten, die im selben Modul umbenennen, erzeugen Konflikte, und beim Auflösen von Konflikten in einem Refactoring schleicht sich Verhalten ein. Teile die Arbeit nach Ordnern oder Modulen auf, nicht nach Art der Änderung.
Schritt 5: Jeden Schritt reviewen
Ein Refactoring-Diff lässt sich leicht durchwinken, weil er aussieht, als täte er nichts. Genau deshalb liest du ihn. Darauf achtest du:
- Haben sich Tests geändert? Bei einem reinen Refactoring sollten bestehende Tests unverändert bleiben.
- Haben sich öffentliche Signaturen geändert? Exportierte Funktionen, API-Antworten, Datenbankspalten, Event-Namen.
- Wurde etwas gelöscht? Frag den Agenten, warum jede entfernte Funktion ungenutzt war. „Keine Referenzen gefunden“ ist kein Beweis, wenn es Reflection oder String-Dispatching gibt.
- Hat sich die Fehlerbehandlung geändert? Eine verschluckte Exception, die jetzt durchschlägt, oder umgekehrt, ist eine Verhaltensänderung.
- Ist der Diff über den Schritt hinausgewachsen? Ein Schritt „verschiebe dieses Modul“ sollte nicht nebenbei zwanzig andere Dateien neu formatieren. Verlange die Formatierung in einem eigenen Commit oder gar nicht.
Ein zweites Modell, das jeden Turn reviewt, fängt einiges davon ab. Wie das funktioniert, steht in KI-Code-Review.
Schritt 6: Den Rollback planen, bevor du ihn brauchst
Ein Rollback ist billig, wenn du ihn vorbereitest:
- Kleine Commits.
git revert <sha>entfernt einen Schritt sauber. Ein Commit mit 4.000 Zeilen lässt sich nur als Ganzes zurücknehmen. - Revertieren statt umschreiben. Ist etwas auf main, revertiere den Commit oder den Merge, statt mit Force-Push über geteilte History zu schreiben.
- Bisect, wenn etwas durchrutscht. Mit kleinen grünen Commits findet
git bisectden Schritt, der das Verhalten gebrochen hat, in wenigen Durchläufen. - Flags für riskante Umschaltungen. Leben alter und neuer Code nebeneinander, schaltet ein Flag den neuen Pfad ohne Deployment ab.
- Rückgängig innerhalb einer Sitzung. Bevor etwas committet ist, kannst du den letzten Turn eines Agenten rückgängig machen; siehe Änderungen von Claude Code rückgängig machen.
Häufige Fehler
- „Refactore dieses Modul“ ohne vorhandene Tests. Du bekommst einen Rewrite und keine Möglichkeit, ihn zu prüfen.
- Refactoring und Bugfixes vermischen. Ändert sich Verhalten, kannst du nicht sagen, ob das beabsichtigt war.
- Ein einziger riesiger Migrations-Branch. Er driftet von main weg, Konflikte häufen sich, und niemand kann ihn reviewen.
- „Ungenutzt“ glauben. Die Suche sieht keine dynamischen Aufrufe, keine anderen Repos und keine geplanten Jobs.
- Den Agenten den Compiler stummschalten lassen.
any,@ts-ignoreund abgeschaltete Lint-Regeln verstecken die Arbeit, statt sie zu erledigen. - Parallele Schritte mergen, ohne sie zusammen zu testen. Zwei grüne Branches können nach dem Merge rot sein, und genau dafür gibt es eine Merge Queue.
So geht es in AgentVera
AgentVera führt Coding-Agent-CLIs pro Projekt nebeneinander aus und hat Git eingebaut, was gut zum schrittweisen Vorgehen bei einem Legacy-Refactoring passt.
- Ein Worktree pro Schritt. Öffne im Worktree-Panel für jeden Schritt einen Branch und Ordner und starte direkt darin einen Agenten, Claude Code im einen, Codex oder Gemini CLI im anderen. Dein Hauptordner bleibt, wie er ist.
- Checkpoints für jeden Turn. Zu Beginn jedes Turns wird der Arbeitsordner als Snapshot in einer versteckten Git-Ref gesichert, und pro Agent bleiben die letzten 60 Turns erhalten. Sieh dir an, welche Dateien ein Turn geändert hat, und mach genau diesen Turn rückgängig, wenn der Agent zu weit gegangen ist.
- Review am Ende jedes Turns. Das automatische Code-Review liest den Code des Agenten mit dem Claude-Modell deiner Wahl und listet Befunde mit Schweregrad und Datei:Zeile; auf Wunsch gehen die Befunde an den Agenten zurück, bis zu zwei Runden hintereinander.
- Verify gegen deine Kriterien. Schreib „reines Refactoring, alle Tests bestehen, keine bestehende Testdatei geändert“ in deine Nachricht. Verify macht daraus Kriterien, und wenn der Turn endet, führt ein separater Verifier, der keine Dateien bearbeiten kann, die Tests aus und markiert jedes Kriterium mit einem Beleg.
- Merge Queue für parallele Schritte. Branches werden einzeln auf den neuesten Stand des Ziels gemergt, und der Prüfbefehl läuft auf dem gemergten Ergebnis. Ein grüner Branch wartet standardmäßig auf deine Freigabe.
- Parallel Team für große Migrationen. Lässt sich eine Migration in unabhängige Teile zerlegen, führt ein Parallel Team jeden Teil in einem eigenen Worktree aus, mergt die fertigen Teile in einen Integrations-Branch und führt die abschließende Prüfung aus, sodass du einen einzigen Pull Request reviewst.
- Git, ohne die App zu verlassen. Commit-Manager, Diffs und Graph-History liegen im rechten Panel, sodass das Reviewen eines Schritts und das Zurücknehmen eines anderen schnell geht.
Fazit
KI macht Legacy-Code nicht sicher änderbar; das tun Tests und kleine Schritte. Was KI ändert, sind die Kosten dieser Schritte. Charakterisierungstests schreiben, einen Ordner nach TypeScript konvertieren oder einen Codemod laufen lassen und danach aufräumen war früher die Arbeit, für die niemand Zeit hatte. Mit Agenten ist sie billig, also kannst du es dir leisten, es richtig zu machen: zuerst das Netz, eine Änderung nach der anderen, jeder Schritt reviewt, jeder Schritt umkehrbar.
Um AgentVera auszuprobieren, starte auf der Startseite.