Legacy-Code mit Claude Code sicher modernisieren
Ein praktischer Workflow für Legacy-Code mit Claude Code: Tests, TypeScript-Migration, Risiken, lauffähige Beispiele und Prüfung.
Modernisierung scheitert, wenn sie zu groß startet
Legacy-Code ist nicht einfach alter Code. Es ist Code, dessen Verhalten schwer beweisbar ist, dessen Tests dünn sind und dessen Änderung schnell Bestellungen, Abrechnung, Support oder interne Abläufe beschädigen kann. Claude Code ist in dieser Lage hilfreich, aber nicht als Knopf für eine Massenneuschreibung.
Der sichere Ablauf ist: zuerst untersuchen, dann bestehendes Verhalten mit Tests festhalten, danach in kleinen Schritten refaktorisieren. Charakterisierungstests dokumentieren, wie sich das System heute verhält, auch wenn dieses Verhalten nicht ideal ist. Das Harness ist das Arbeitsgerüst für den Agenten: Tests, Berechtigungen, Befehle, Projektregeln und Review-Checklisten.
Die offizielle Dokumentation zu Claude Code common workflows beschreibt genau solche Schritte: Codebasis verstehen, refaktorisieren, mit Tests arbeiten, PRs erstellen und Worktrees nutzen. Für Legacy-Code ist das die richtige Haltung. Claude Code erhöht die Umsetzungsgeschwindigkeit, aber Menschen behalten Risiko, Priorisierung und Abnahme.
Drei typische Anwendungsfälle
Modernisiere nicht zuerst den hässlichsten Code, sondern den Code, bei dem weniger Unsicherheit echten Schaden verhindert.
| Anwendungsfall | Ziel | Was Claude Code leisten kann | Was Menschen prüfen müssen |
|---|---|---|---|
| Bestellung, Rechnung, Zahlung | Fehler bei Geld und Kundenstatus vermeiden | Verhalten kartieren, Tests ergänzen, Grenzfälle finden | Steuern, Rabatte, Rundung, Compliance |
| JavaScript zu TypeScript | Künftige Änderungen sicherer machen | Typen hinzufügen, any reduzieren, Typfehler stapelweise beheben | Öffentliche API und Build-Kompatibilität |
| Callback-Hölle oder riesige Funktionen | Wartbarkeit verbessern, Verhalten behalten | Verantwortlichkeiten trennen, Namen vorschlagen, Diffs erklären | Fehlerpfade, Retries, Seiteneffekte |
Bei ClaudeCodeLab nutze ich dieses Muster für alte Publishing-Skripte, Checkout-Integration und Content-Transformationen. Entscheidend ist nicht, dass Claude Code schnell Code schreibt. Entscheidend ist, dass jede Änderung klein genug für ein ernsthaftes Review ist und eine Prüfung dahintersteht.
flowchart LR
A[Untersuchen] --> B[Verhalten mit Tests sichern]
B --> C[Klein refaktorisieren]
C --> D[Typen und Dependencies ordnen]
D --> E[Menschliches Risiko-Review]
E --> B
Mit einem Read-only-Audit beginnen
Der erste Prompt sollte Änderungen ausdrücklich verbieten. In dieser Phase brauchst du eine Landkarte des Systems, keinen Patch auf unvollständiger Grundlage.
Lies @src/legacy und @test.
Ändere noch keine Dateien.
Gib dieses Audit zurück:
1. Wichtige Dateien und Verantwortlichkeiten
2. Externe I/O, Datenbank, API, Dateischreibzugriffe und Seiteneffekte
3. Verhalten, das kompatibel bleiben muss
4. Fehlende Tests und riskante Zweige
5. Sicherste Reihenfolge für kleine Änderungen
Wenn eine Regel unklar ist, schreibe "menschliche Bestätigung nötig" statt zu raten.
Die Seite How Claude Code works erklärt, dass Claude Code Dateien lesen, Befehle ausführen und Code bearbeiten kann. Diese Fähigkeit ist nützlich, aber im ersten Schritt muss sie begrenzt werden. In alten Systemen kann eine logisch wirkende Verbesserung einen Rückgabewert verändern, auf den externe Nutzer angewiesen sind.
Minimalbeispiel zum Ausführen
Das Beispiel ist bewusst klein, folgt aber demselben Ablauf wie eine echte Modernisierung.
mkdir legacy-modernization-demo
cd legacy-modernization-demo
npm init -y
npm install -D vitest typescript @types/node
npm pkg set type="module"
npm pkg set scripts.test="vitest run"
npm pkg set scripts.typecheck="tsc --noEmit"
mkdir -p src/legacy test
Der alte Order Processor mischt Validierung, Berechnung und Rückgabeformat in einer Datei. Das ist kein künstlich schlechter Code, sondern eine typische Form von Code, der lange funktioniert hat und deshalb ungern angefasst wird.
// src/legacy/orderProcessor.js
export function processOrder(order) {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return { status: "error", message: "items is required" };
}
const subtotal = order.items.reduce((sum, item) => {
return sum + item.price * item.qty;
}, 0);
const discount = order.customer?.type === "vip" ? subtotal * 0.1 : 0;
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Nun sichern wir das bestehende Verhalten mit Vitest. Diese Tests beschreiben nicht das perfekte Modell, sondern den Vertrag, den wir nicht unabsichtlich brechen dürfen.
// test/orderProcessor.test.ts
import { describe, expect, it } from "vitest";
import { processOrder } from "../src/legacy/orderProcessor.js";
describe("processOrder legacy behavior", () => {
it("calculates total for a regular customer", () => {
const result = processOrder({
items: [
{ id: "A1", qty: 2, price: 1000 },
{ id: "B2", qty: 1, price: 500 }
],
customer: { id: "C1", type: "regular" }
});
expect(result).toMatchObject({
status: "confirmed",
total: 2500,
discount: 0
});
});
it("applies a 10 percent VIP discount", () => {
const result = processOrder({
items: [{ id: "A1", qty: 1, price: 10000 }],
customer: { id: "C2", type: "vip" }
});
expect(result.status).toBe("confirmed");
expect(result.total).toBe(9000);
expect(result.discount).toBe(1000);
});
it("returns an error when items are empty", () => {
const result = processOrder({
items: [],
customer: { id: "C3", type: "regular" }
});
expect(result.status).toBe("error");
expect(result.message).toContain("items");
});
});
Führe npm test aus. Erst wenn die Tests grün sind, sollte Claude Code bearbeiten.
Lies @src/legacy/orderProcessor.js und @test/orderProcessor.test.ts.
Migriere diesen Code nach TypeScript, während die Tests grün bleiben.
Regeln:
- Öffentlichen Funktionsnamen processOrder behalten
- status, total, discount und message kompatibel halten
- Erst Typdefinitionen ergänzen, dann Verantwortlichkeiten trennen
- Danach npm test und npm run typecheck ausführen
- Erklären, welche Kompatibilität jeder Diff bewahrt
Eine besser prüfbare TypeScript-Struktur
Nach der Modernisierung sind Typen, Validierung, Berechnung und Orchestrierung getrennt. Ziel ist nicht Abstraktion um der Abstraktion willen, sondern überprüfbare Geschäftslogik.
// src/orderTypes.ts
export type CustomerType = "regular" | "vip";
export type OrderItem = {
id: string;
qty: number;
price: number;
};
export type OrderInput = {
items: OrderItem[];
customer: {
id: string;
type: CustomerType;
};
};
export type OrderResult =
| {
status: "confirmed";
total: number;
items: OrderItem[];
discount: number;
}
| {
status: "error";
message: string;
};
// src/validators.ts
import type { OrderInput } from "./orderTypes";
export function validateOrder(order: OrderInput | null | undefined): string | null {
if (!order || !Array.isArray(order.items) || order.items.length === 0) {
return "items is required";
}
return null;
}
// src/calculators.ts
import type { CustomerType, OrderItem } from "./orderTypes";
export function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
export function calculateDiscount(subtotal: number, customerType: CustomerType): number {
return customerType === "vip" ? subtotal * 0.1 : 0;
}
// src/orderProcessor.ts
import { calculateDiscount, calculateSubtotal } from "./calculators";
import type { OrderInput, OrderResult } from "./orderTypes";
import { validateOrder } from "./validators";
export function processOrder(order: OrderInput): OrderResult {
const validationMessage = validateOrder(order);
if (validationMessage) {
return { status: "error", message: validationMessage };
}
const subtotal = calculateSubtotal(order.items);
const discount = calculateDiscount(subtotal, order.customer.type);
return {
status: "confirmed",
total: subtotal - discount,
items: order.items,
discount
};
}
Passe den Testimport auf ../src/orderProcessor an und führe npm test sowie npm run typecheck erneut aus. Ein Diff dieser Größe lässt sich noch sauber prüfen. Wenn derselbe PR auch Verzeichnisse verschiebt, Dependencies aktualisiert, Formatierung ändert und Fachbegriffe umbenennt, sinkt die Review-Qualität.
Dependency-Updates getrennt behandeln
Ein häufiger Fehler ist, Refactoring und Major-Upgrades zu mischen. Wenn Tests brechen, ist unklar, ob TypeScript, eine API-Änderung, der Bundler oder die Geschäftslogik schuld ist.
Lass Claude Code zuerst nur inventarisieren.
Lies package.json und den Lockfile.
Aktualisiere noch nichts.
Gib eine Tabelle aus mit:
- package
- aktuelle Version
- empfohlene Zielversion
- ob es ein Major-Upgrade ist
- URL des offiziellen Migration Guides
- wahrscheinlich betroffene Dateien
- Tests, die vor dem Update ergänzt werden sollten
Für destruktive oder breite Änderungen sollte das Berechtigungsmodell konservativ bleiben. Die Dokumentation zu Claude Code permissions ist Pflichtlektüre, bevor Migrationen, Löschungen oder Deployments automatisiert werden. Agentengeschwindigkeit hilft nicht, wenn sie nötige Zustimmungspunkte überspringt.
Konkrete Fallen
Die erste Falle ist Refactoring ohne Tests. Saubererer Code ist trotzdem eine Regression, wenn Rundung, Rabattlogik oder Fehlermeldungen verändert werden.
Die zweite Falle ist, Vorschläge von Claude Code als Geschäftsregel zu akzeptieren. Eine idiomatischere Rückgabe ist nicht automatisch kompatibel mit bestehenden Clients.
Die dritte Falle ist der riesige PR. Typmigration, Logiktrennung, Dependency-Updates, Dateiverschiebungen und Formatierung sollten möglichst getrennt werden.
Die vierte Falle ist zu schnelle Verbesserung der Fehlerbehandlung. In alten Systemen können null, bestimmte Strings oder ungewöhnliche HTTP-Status Teil eines Vertrags sein.
Die fünfte Falle ist fehlende Dokumentation. Lass Claude Code Kompatibilitätsnotizen, manuelle Prüfschritte und Rollback-Plan in die PR-Beschreibung schreiben.
Review, Links und CTA
Kombiniere diesen Ablauf mit dem Guide zur Refactoring-Automatisierung, TDD mit Claude Code und automatischer Dokumentationsgenerierung. Für Teams gehören verbotene Bereiche, Testbefehle, Domänenbegriffe und Review-Regeln in CLAUDE.md Best Practices.
ClaudeCodeLab unterstützt Teams mit Claude Code Training, CLAUDE.md Templates und Beratung zur Modernisierung bestehender Produkte. Das Ziel ist keine KI-Neuschreibung, sondern ein wiederholbarer Prozess, in dem Tests, Berechtigungen und menschliche Reviews zusammenarbeiten.
Geprüftes Ergebnis
Ich habe den Ablauf als legacy-modernization-demo durchgespielt: alten JavaScript Order Processor erstellen, drei Verhalten mit Vitest sichern, nach TypeScript migrieren und anschließend npm test sowie npm run typecheck ausführen. Der größte Gewinn kam durch die Trennung von Read-only-Audit und Bearbeitungsprompt. Die Diffs waren kleiner, und Gesamtbetrag, VIP-Rabatt sowie Fehler bei leerer Bestellung waren leichter zu prüfen.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.