Przejdź do głównej zawartości

Refaktoryzacja na Dużą Skalę z Terminala

Odkładasz tę refaktoryzację od sześciu miesięcy. Katalog utils ma 47 plików. Połowa bazy kodu używa callbacks, podczas gdy druga połowa używa async/await. Są trzy różne sposoby obsługi błędów. Twój team lead w końcu powiedział “musimy to posprzątać przed następnym pushem funkcji”, a ty wyciągnąłeś krótszą zapałkę.

Ręczna refaktoryzacja na dużą skalę jest żmudna i podatna na błędy. Zmieniasz nazwę funkcji, pomijasz jedno miejsce wywołania, a raport o błędzie przybywa o 15:00 w piątek. Claude Code zmienia to równanie. Czyta całą twoją bazę kodu, rozumie graf zależności i stosuje zmiany systematycznie w setkach plików. W połączeniu z wzorcami fan-out dla równoległego wykonywania i hookami do automatycznej weryfikacji, refaktoryzacja na dużą skalę staje się zadaniem weekendowym zamiast miesięcznego maratonu.

  • Przepływ pracy dla bezpiecznej refaktoryzacji na dużą skalę z automatyczną weryfikacją testów
  • Wzorce fan-out używające trybu headless dla transformacji całej bazy kodu
  • Prompty dla typowych zadań refaktoryzacyjnych: zmiany nazw, migracje wzorców, ekstrakcja modułów
  • Podejście strangler fig dla przyrostowego zastępowania starych wzorców

Duża refaktoryzacja zawodzi, gdy zmieniasz wszystko naraz, psujesz testy w sposób, którego nie rozumiesz, i spędzasz więcej czasu na debugowaniu refaktoryzacji niż spędziłeś na samej refaktoryzacji. Bezpieczne podejście jest przyrostowe: jeden wzorzec na raz, weryfikowany na każdym kroku.

  1. Ustanów siatkę bezpieczeństwa

    Przed zmianą jakiegokolwiek kodu upewnij się, że testy przechodzą i masz czysty stan git.

    Uruchom pełny zestaw testów i potwierdź, że wszystko przechodzi.
    Następnie uruchom linter i type checker. Jeśli coś jest już zepsute,
    napraw to przed rozpoczęciem refaktoryzacji. Potrzebuję czystej
    linii bazowej do refaktoryzacji.
  2. Przeanalizuj, co wymaga zmiany

  3. Refaktoryzuj jedną partię na raz

    Zacznij od partii 1: funkcje użytkowe w src/lib/.
    Dla każdego pliku:
    1. Wykonaj zmianę
    2. Zaktualizuj wszystkich wywołujących zmienionego kodu
    3. Uruchom testy dla dotkniętych modułów
    Pokaż mi output testów po każdym pliku. Jeśli test nie przejdzie,
    napraw go przed przejściem do następnego pliku.
  4. Commituj każdą partię niezależnie

    Wszystkie testy przechodzą dla partii 1. Commituj z wiadomością:
    "Migruj narzędzia src/lib/ z callbacks do async/await"
  5. Powtórz dla następnej partii

    Kontynuuj, aż wszystkie partie będą ukończone. Każda partia jest commitowana niezależnie, więc możesz wykonać bisect, jeśli coś się później zepsuje.

Dla naprawdę dużych zmian — zmiany nazwy typu w 200 plikach, aktualizacji ścieżek importu po restrukturyzacji katalogów, lub migracji przestarzałego API — tryb headless pozwala równoległe wykonywanie pracy.

Okno terminala
# Znajdź wszystkie pliki używające starego wzorca
grep -rl "oldFunctionName" src/ | while read file; do
claude -p "W $file zmień nazwy wszystkich wystąpień oldFunctionName
na newFunctionName. Zaktualizuj wszelkie powiązane nazwy zmiennych i
komentarze. Nie zmieniaj logiki, tylko nazw." &
done
wait
# Uruchom testy do weryfikacji
npm test

Dla bardziej złożonych transformacji:

Wewnątrz sesji interaktywnej sub-agents mogą obsługiwać różne części refaktoryzacji jednocześnie:

Użyj sub-agents do refaktoryzacji tych trzech obszarów równolegle:
1. Migruj wszystkie pliki w src/services/ ze starego wzorca
obsługi błędów do nowego wzorca AppError
2. Migruj wszystkie pliki w src/routes/ z express callbacks
do async route handlers
3. Zaktualizuj wszystkie pliki w src/middleware/, aby używały
nowego loggera zamiast console.log
Każdy sub-agent powinien uruchomić odpowiednie testy po wykonaniu
zmian. Raportuj z wynikami.
Zmień nazwę pola modelu User "fname" na "firstName" wszędzie:
1. Zaktualizuj migrację bazy danych
2. Zaktualizuj schemat Prisma/Drizzle
3. Zaktualizuj wszystkie pliki serwisów odwołujące się do pola
4. Zaktualizuj wszystkie kształty odpowiedzi API
5. Zaktualizuj wszystkie pliki testowe
6. Zaktualizuj wszystkie komponenty frontendu wyświetlające pole
Szukaj zarówno "fname", jak i "user.fname" i "user['fname']",
aby złapać wszystkie wzorce dostępu. Uruchom pełny zestaw testów
po wszystkich zmianach.
Logika uwierzytelniania jest rozproszona w sześciu plikach. Wyodrębnij
ją do samodzielnego katalogu src/modules/auth/:
1. Najpierw zidentyfikuj każdą funkcję, typ i stałą związaną z auth
w całej bazie kodu
2. Stwórz nową strukturę katalogów:
- src/modules/auth/index.ts (publiczne API)
- src/modules/auth/service.ts
- src/modules/auth/middleware.ts
- src/modules/auth/types.ts
- src/modules/auth/constants.ts
3. Przenieś kod, aktualizując wszystkie importy w bazie kodu
4. Upewnij się, że publiczne API modułu jest wyraźne -- eksportuj
tylko to, czego zewnętrzny kod faktycznie używa
Uruchom testy po każdym przeniesieniu, aby natychmiast złapać zepsute importy.
Konwertuj src/legacy/ z JavaScript na TypeScript przyrostowo.
Zacznij od plików liściastych (bez zależności od innych plików legacy),
następnie pracuj do wewnątrz.
Dla każdego pliku:
1. Zmień nazwę .js na .ts
2. Dodaj adnotacje typów dla wszystkich parametrów i zwrotów funkcji
3. Zastąp any właściwymi typami
4. Dodaj interfejsy dla kształtów obiektów
5. Napraw wszelkie błędy typów znalezione przez kompilator
Nie zmieniaj żadnej logiki. To jest migracja tylko typów.
Uruchom tsc --noEmit po każdym pliku, aby natychmiast złapać błędy typów.
Nasza zależność @acme/sdk właśnie wydała v3, która deprecjonuje
metodę query() na rzecz execute(). Przewodnik migracji mówi:
- query(sql) staje się execute({ sql })
- query(sql, params) staje się execute({ sql, params })
- Typ zwracany zmienił się z rows[] na { rows, metadata }
Znajdź każdy plik, który importuje z @acme/sdk i używa query().
Zastosuj migrację. Zaktualizuj obsługę typu zwracanego.
Uruchom testy po każdym pliku.

Dla systemów legacy, gdzie nie możesz refaktoryzować wszystkiego na raz, użyj wzorca strangler fig: zbuduj nową wersję obok starej, stopniowo kieruj ruch do niej i usuń stary kod, gdy wszystko zostanie zmigrowane.

Musimy zastąpić naszą własną walidację Zod.
Obecna walidacja jest rozproszona w ponad 30 plikach używających
własnych funkcji validate().
Faza 1 (ten PR):
- Stwórz schematy Zod, które pasują do obecnych reguł walidacji
- Dodaj wrapper kompatybilności, który uruchamia zarówno starą, jak i nową
walidację i loguje rozbieżności
- Wdróż i monitoruj niedopasowania
Faza 2 (następny PR):
- Przełącz na Zod jako główny walidator
- Zachowaj stary kod jako fallback za feature flag
Faza 3 (ostatni PR):
- Usuń stary kod walidacji i feature flag
- Usuń wrapper kompatybilności
Zacznij od Fazy 1. Przeczytaj trzy istniejące funkcje validate(),
aby zrozumieć obecne reguły, następnie stwórz równoważne schematy Zod.

Skonfiguruj hooki, które uruchamiają się po każdej edycji, aby natychmiast złapać problemy:

.claude/settings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "npx tsc --noEmit 2>&1 | head -20"
}
]
}
}

Dla refaktoryzacji dotyczącej wielu plików szersze sprawdzenie po każdej edycji zapobiega kaskadowym błędom:

Po każdym zmienionym pliku uruchom:
1. npx tsc --noEmit (złap błędy typów natychmiast)
2. npm test -- --run --reporter=dot (szybkie uruchomienie testów)
Jeśli któryś nie przejdzie, zatrzymaj się i napraw przed przejściem do następnego pliku.
Nie akumuluj zepsutych plików.

Po zakończeniu refaktoryzacji skwantyfikuj, co się poprawiło:

Porównaj bazę kodu przed i po tej refaktoryzacji:
1. Uruchom linter i porównaj liczby ostrzeżeń
2. Uruchom type checker i porównaj liczby błędów
3. Uruchom zestaw testów i porównaj pass/fail/czas trwania
4. Policz liczbę plików, funkcji i linii kodu
5. Zmierz złożoność cyklomatyczną dla zrefaktoryzowanych modułów
Pokaż mi podsumowanie przed/po, abym mógł je uwzględnić w opisie PR.

Refaktoryzacja psuje test, którego się nie spodziewałeś. Zmiana miała ukrytą zależność. Wycofaj się do ostatniego commita, zbadaj zależność i zaktualizuj plan, aby ją uwzględnić. Dlatego commitowanie po każdej partii ma znaczenie — wycofania są czyste.

Zmiana nazwy pominęła niektóre miejsca wywołań. Wyszukiwanie Claude pominęło dynamiczne wzorce dostępu jak obj[fieldName] lub interpolację stringów. Po zmianie nazwy uruchom szersze wyszukiwanie: “Szukaj zarówno starej nazwy, jak i jakiegokolwiek stringa zawierającego starą nazwę. Sprawdź pliki konfiguracyjne, zapytania SQL i stringi szablonów.”

Refaktoryzacja jest zbyt duża na jedną sesję. Użyj trybu headless do części mechanicznych i zachowaj sesję interaktywną dla złożonych transformacji. Lub podziel refaktoryzację na wiele PR: “Daj mi plan, który dzieli tę refaktoryzację na trzy PR, każdy niezależnie wdrażalny i możliwy do przeglądu.”

Błędy typów kaskadują po zmianie. Zmieniłeś typ, który jest używany wszędzie. Zacznij od definicji typu i pracuj na zewnątrz: “Napraw błędy typów zaczynając od plików najbliższych zmienionemu typowi, następnie przenieś się do plików, które zależą od tych plików. Pokaż mi licznik błędów po każdym pliku.”

Zespół nadal merguje kod do plików, które refaktoryzujesz. Koordynuj z zespołem. Dla dużych refaktoryzacji rozważ krótkie zamrożenie kodu na dotkniętych plikach, lub użyj git worktrees, aby móc często rebase’ować bez utraty postępu.

Z czystym, dobrze zorganizowanym kodem i kompletnymi testami jesteś w silnej pozycji do generowania dokumentacji, która pozostaje aktualna z bazą kodu.