Sicheres Refactoring mit Claude Code automatisieren
Praktischer Workflow für Claude Code Refactoring: Vorher/Nachher, Tests, git diff, Review, Risiken und sofort nutzbare Prompts.
Zuerst die Sicherheitsgrenze festlegen
Der sicherste Weg, Refactoring mit Claude Code zu automatisieren, ist nicht der Prompt “räum den Code auf”. Das klingt produktiv, führt aber oft zu einem großen Diff, den niemand sauber prüfen kann. Refactoring bedeutet, die interne Struktur zu verbessern, ohne das Verhalten zu ändern, auf das Benutzer, APIs, Tests und andere Systeme angewiesen sind. Wenn diese Grenze unklar ist, wird aus Automatisierung schnell unbeabsichtigte Feature-Arbeit.
Dieser Artikel behandelt Claude Code als praktischen Partner: erst untersuchen, dann einen kleinen Plan erstellen, genau eine Änderung umsetzen, Tests ausführen und den git diff erklären. Die offiziellen common workflows zeigen diese Arbeitsweise gut. Für Befehlsrechte und Projektkonfiguration ist außerdem die Dokumentation zu settings wichtig.
Wenn dein Team gerade Regeln für den Einsatz erstellt, lies auch die Artikel zu Claude Code Berechtigungen und Claude Code Kontextmanagement. Hier geht es um den Alltag: was man zuerst fragt, welche Änderungen für Einsteiger sicher sind, wie man testet und wie man das Ergebnis prüft.
Masa-Notiz aus der Praxis: In kleinen Tests war Claude Code stark bei Umbenennungen, dem Extrahieren reiner Funktionen, dem Klären von TypeScript-Typen und dem Ergänzen von Regressionstests. Schwieriger wurde es bei breiten Prompts wie “modernisiere diesen Service”. Der langweilige Workflow ist der wirtschaftliche: enger Scope, sichtbare Tests, kleiner Diff.
Sicherer Workflow: untersuchen, planen, einen Diff ändern, verifizieren
Nutze diese Reihenfolge, bis das Team Vertrauen aufgebaut hat.
| Schritt | Was Claude Code tut | Was der Mensch prüft |
|---|---|---|
| 1. Untersuchen | Dateien, Abhängigkeiten und Tests lesen | Scope ist nicht zu groß |
| 2. Planen | Plan mit maximal drei Schritten vorschlagen | Keine versteckte Verhaltensänderung |
| 3. Ändern | Nur ein Thema bearbeiten | Diff ist reviewbar |
| 4. Verifizieren | Tests, typecheck und lint ausführen | Fehler sind klar erklärt |
| 5. Review | git diff und Risiken zusammenfassen | Before/after Verhalten bleibt gleich |
Beginne mit einem Prompt ohne Dateiedits.
Untersuche dieses Repository auf sichere Refactoring-Kandidaten.
Bearbeite noch keine Dateien.
Bedingungen:
- Externes Verhalten nicht ändern
- Ein Diff umfasst höchstens drei Dateien
- Bereiche mit vorhandenen Tests bevorzugen
- Gib eine Tabelle aus: Kandidat, Grund, Prüfbefehl, Risiko
“Bearbeite noch keine Dateien” ist wichtig. Claude Code kann schnell von Analyse zu Umsetzung wechseln. Wenn Untersuchung und Implementierung getrennt sind, sinkt das Risiko deutlich.
Lege vor der Änderung einen Branch an und sichere die Ausgangslage.
git status --short
git checkout -b refactor/safe-extract-order-total
npm test
npm run typecheck
npm run lint
Passe die Befehle an dein package.json an. Wenn Tests schon vor dem Refactoring fehlschlagen, dokumentiere das. Sonst ist später unklar, ob Claude Code etwas kaputt gemacht hat oder ob der Fehler bereits existierte.
Use case 1: Umbenennen und eine kleine reine Funktion extrahieren
Der sicherste Einstieg ist bessere Benennung plus Extraktion einer reinen Funktion. Eine reine Funktion liefert für dieselbe Eingabe dasselbe Ergebnis und verändert keine Datenbank, sendet keine E-Mail, ruft keine API auf und ändert keinen globalen Zustand. Das ist ideal für Claude Code, weil Erfolg leicht testbar ist.
// before: src/domain/order.ts
export function calc(o: { items: { p: number; q: number }[]; d?: number }) {
let t = 0;
for (const i of o.items) {
t += i.p * i.q;
}
if (o.d) {
t = t - o.d;
}
return Math.max(t, 0);
}
Der Code ist kurz, aber p, q und d erklären die Fachlogik nicht. Bitte Claude Code, zuerst Tests zu ergänzen und danach Namen zu verbessern.
Refactore die Funktion calc in src/domain/order.ts sicher.
Anforderungen:
- Vor der Implementierungsänderung Unit-Tests ergänzen, die aktuelles Verhalten fixieren
- Der exportierte Name calc bleibt in diesem Diff gleich
- Variablen- und Typnamen verbessern
- Die Regel, dass der Gesamtbetrag nie negativ wird, bleibt erhalten
- Danach npm test -- order ausführen
Ein gutes Ergebnis bleibt klein.
// after: src/domain/order.ts
type OrderLine = {
price: number;
quantity: number;
};
type OrderInput = {
items: OrderLine[];
discount?: number;
};
export function calc(order: OrderInput): number {
const subtotal = order.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return Math.max(subtotal - (order.discount ?? 0), 0);
}
Kopierbare Tests:
// src/domain/order.test.ts
import { describe, expect, it } from "vitest";
import { calc } from "./order";
describe("calc", () => {
it("multiplies price and quantity", () => {
expect(calc({ items: [{ price: 1200, quantity: 2 }] })).toBe(2400);
});
it("applies discount without returning a negative total", () => {
expect(calc({ items: [{ price: 500, quantity: 1 }], discount: 800 })).toBe(0);
});
});
Prüfe nur die geänderten Dateien.
git diff -- src/domain/order.ts src/domain/order.test.ts
npm test -- order
npm run typecheck
Die Review-Frage lautet nicht “sieht der Code schlau aus?”, sondern “behalten dieselben Eingaben dieselbe fachliche Bedeutung?”. Prüfe Berechnung, exportierten Namen und Testbeschreibungen.
Use case 2: any entfernen, indem zuerst die Grenze typisiert wird
any zu reduzieren ist wertvoll, aber projektweit auf einmal ist es riskant. Beginne an Grenzen: API-Antworten, Formulare, Konfiguration, Webhooks oder importierte CSV-Zeilen. Dort kommen unbekannte Daten ins System.
// before: src/lib/user-api.ts
export async function fetchUser(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
export function getDisplayName(user: any): string {
return user.profile.displayName || user.name;
}
Gib Claude Code ein enges Ziel und beschreibe fehlende Daten.
Reduziere any in src/lib/user-api.ts.
Anforderungen:
- Typ für die API-Antwort ergänzen
- Fetch-URL und Rückgabebedeutung bleiben gleich
- getDisplayName ist sicher, wenn profile fehlt
- Tests für aktuelles Anzeigeverhalten ergänzen
- npm test -- user-api und npm run typecheck ausführen
Ein akzeptabler erster Diff:
// after: src/lib/user-api.ts
export type UserResponse = {
id: string;
name: string;
profile?: {
displayName?: string;
};
};
export async function fetchUser(id: string): Promise<UserResponse> {
const response = await fetch(`/api/users/${id}`);
return response.json() as Promise<UserResponse>;
}
export function getDisplayName(user: UserResponse): string {
return user.profile?.displayName ?? user.name;
}
Dieser Cast validiert keine Laufzeitdaten. Wenn echte Eingabesicherheit nötig ist, folgt ein zweiter Diff mit zod oder einem vorhandenen Parser. Mische nicht “any entfernen” und “Validierungsbibliothek einführen” im ersten Einsteiger-Diff.
// src/lib/user-api.test.ts
import { describe, expect, it } from "vitest";
import { getDisplayName, type UserResponse } from "./user-api";
describe("getDisplayName", () => {
it("uses profile displayName when present", () => {
const user: UserResponse = {
id: "u1",
name: "Masa",
profile: { displayName: "Masa I." },
};
expect(getDisplayName(user)).toBe("Masa I.");
});
it("falls back to name when profile is missing", () => {
expect(getDisplayName({ id: "u2", name: "Guest" })).toBe("Guest");
});
});
Suche im Review nach gefährlichen Abkürzungen: as any, geschluckte Fehler, leere Strings als Fallback oder verändertes Verhalten optionaler Felder. Ein stärker typisierter Diff kann trotzdem Verhalten brechen.
Use case 3: Große Funktionen erst mit Test-Harness aufteilen
Große Service-Funktionen sind verlockend, verstecken aber oft Fachlogik. Bestellungen, Abrechnung, Berechtigungen, Benachrichtigungen und Importe mischen Validierung, Berechnung, Persistenz und Seiteneffekte. Lass Claude Code zunächst nur ein reines Stück extrahieren.
// before: src/services/order-service.ts
export async function createOrder(input: CreateOrderInput) {
if (input.items.length === 0) {
throw new Error("items required");
}
const subtotal = input.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const shippingFee = subtotal >= 10000 ? 0 : 800;
const total = subtotal + shippingFee;
const order = await db.order.create({
data: { userId: input.userId, subtotal, shippingFee, total },
});
await mailer.sendOrderCreated(order.id);
return order;
}
Der Prompt muss auch Nicht-Ziele festlegen.
Mache createOrder in src/services/order-service.ts kleiner.
In diesem Diff:
- Nur Versand- und Gesamtberechnung in eine reine Funktion extrahieren
- Name: calculateOrderTotals
- Unit-Tests für calculateOrderTotals ergänzen
- Reihenfolge von Datenbank-Schreibvorgang und E-Mail beibehalten
Nicht in diesem Diff:
- Datenbankschema ändern
- Fehlermeldungen ändern
- API-Antwortform ändern
- Unrelated Funktionen verschieben
- Ganze Datei formatieren
Danach:
// after: src/services/order-service.ts
export function calculateOrderTotals(items: OrderItem[]) {
const subtotal = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const shippingFee = subtotal >= 10000 ? 0 : 800;
return {
subtotal,
shippingFee,
total: subtotal + shippingFee,
};
}
Review-Befehle:
git diff --stat
git diff -- src/services/order-service.ts
git diff -- src/services/order-service.test.ts
npm test -- order-service
Wenn Claude Code nebenbei formatiert:
Dieser Diff ist zu groß.
Nimm reine Formatierungsänderungen zurück und behalte nur die Extraktion von calculateOrderTotals plus Tests.
Ändere kein externes Verhalten, keine Fehlermeldungen, keine DB-Schreibvorgänge und keine E-Mail-Reihenfolge.
Mit git diff reviewen, nicht nach Gefühl
Die Erklärung von Claude Code ist hilfreich, aber der Diff ist die Wahrheit.
git diff --check
git diff --stat
git diff --name-only
git diff --word-diff -- src/domain/order.ts
| Bereich | Was prüfen |
|---|---|
| Verhalten | Eingaben, Ausgaben, Exceptions, HTTP-Status und Persistenzreihenfolge |
| Größe | Änderung passt in eine menschliche Review |
| Tests | Bestehendes Verhalten ist abgesichert |
| Typen | Kein neues as any, keine unsicheren Casts, keine ignorierten Fehler |
| Seiteneffekte | API, E-Mail, Billing, Löschen und Rechte behalten Reihenfolge |
| Zusammenfassung | Claude Codes Erklärung passt zum echten Diff |
Review-Prompt:
Reviewe diesen git diff.
Prüfe:
- Überschreitet die Änderung den Refactoring-Scope?
- Welches Verhalten ist nicht durch Tests geschützt?
- Gibt es unsichere Casts oder geschluckte Fehler?
- Welche Dateien muss ein Mensch besonders prüfen?
Antworte mit:
- Sieht sicher aus
- Braucht menschliche Bestätigung
- Muss korrigiert werden
und nenne Dateien und Gründe.
Pitfall: typische Fehler und Risiken
Erster failure: ein zu breiter Prompt.
Mach diese Service-Schicht sauberer.
Das kann Extraktion, Namen, Fehlerdesign, Dateiverschiebung und Formatierung vermischen. Besser:
Extrahiere nur die Versandkostenberechnung aus createOrder in eine reine Funktion.
Ändere keine Prozessreihenfolge, Fehlermeldungen oder Rückgabewerte.
Zweiter risk: einen schön aussehenden Diff ohne Tests akzeptieren. Lesbarkeit kann steigen, während Grenzfälle kippen: Rabatte, kostenloser Versand, Rechte, Retry-Verhalten oder Nullwerte. Dritter Fehler: Formatierung und strukturelles Refactoring mischen. Wenn Prettier hunderte Zeilen ändert, ist die echte Änderung versteckt. Vierter Risk: Befehlsrechte zu früh zu weit öffnen. Beginne mit Lesen, Tests, typecheck und lint; erweitere später.
Wiederverwendbare Checklist und CTA
## Refactoring checklist
- [ ] Die Änderung hat genau ein Ziel
- [ ] Baseline-Tests liefen vor dem Editieren
- [ ] Before/after Verhalten ist gleich
- [ ] Bestehende oder neue Tests schützen das Verhalten
- [ ] git diff --stat ist klein genug für Review
- [ ] git diff --check läuft durch
- [ ] Keine neuen any, unsicheren Casts oder geschluckten Fehler
- [ ] DB, E-Mail, Billing, Löschen und Rechte behalten ihre Reihenfolge
Finaler Prompt:
Führe genau einen sicheren Refactoring-Diff aus.
Ziel:
- src/services/order-service.ts
- src/services/order-service.test.ts
Erfolgskriterien:
- Externes Verhalten ändert sich nicht
- calculateOrderTotals wird extrahiert
- Bestehende und neue Tests bestehen
- Berichte git diff --stat und ausgeführte Befehle
Verboten:
- DB-Schemaänderungen
- API-Response-Änderungen
- Änderungen an Fehlermeldungen
- Bearbeitung unrelated Dateien
In meiner Prüfung brachten zwei Gewohnheiten den größten Qualitätsgewinn: zuerst ein Plan ohne Edit und am Ende immer eine git diff-Zusammenfassung. Kombiniere das mit der Claude Code Review-Checkliste und den CLAUDE.md Best Practices, damit das Team den Prozess wiederholen kann.
Wenn dein Team Claude Code sicher einführen will, kann das Claude Code Training Berechtigungen, Prompts, Review-Gewohnheiten und Workflow-Design abdecken. Refactoring-Automation lohnt sich, wenn sie Wartungsrisiko senkt, nicht wenn sie spektakuläre, unprüfbare Diffs erzeugt.
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.