Przejdź do głównej zawartości

Wzorce refaktoryzacji w Claude Code to nazwane ruchy zachowujące działanie kodu (Extract Function, Replace Conditional with Polymorphism, podział klasy za fasadą, callbacki na async/await), wykonywane po jednym na turę wobec testów, których agent nie może edytować. Każdy ruch przyjmuje się na podstawie dowodów: niezmienionego diffu testów, zielonego zestawu, bramek typów i lintera oraz commita na ruch.

Otwierasz src/services/checkout.service.ts, żeby dodać jedną regułę rabatu, i trafiasz na processOrder: 340 linii walidacji inline, trzy zagnieżdżone gałęzie if dla typu płatności, liczenie ceny, zapis stanu magazynu i wysyłkę e-maila, a wszystko to na wspólnych zmiennych lokalnych. Zestaw testów Vitest jest zielony, ale nikt tej funkcji nie rusza, bo każda zmiana może zepsuć ścieżkę, która odpala się tylko na produkcji.

Ten przewodnik jest dla programistów, którzy chcą, żeby Claude Code przeprowadził taką przebudowę w obrębie jednego modułu albo kilku plików, i którzy chcą przyjąć wynik bez ponownego czytania każdej przeniesionej linii. Do zmian nazw i migracji wzorców w setkach plików służy refaktoryzacja na dużą skalę z terminala. Tę samą pętlę w pozostałych narzędziach opisują strony o refaktoryzacji z Codeksem i strategiach refaktoryzacji w Cursorze.

  • Pętlę w siedmiu krokach, w której zestaw testów jest zielony, a same testy nietknięte na każdym etapie
  • Gotowe prompty do utrwalenia zachowania, Extract Function, wzorca Strategy, podziału serwisu i przejścia z callbacków na async/await
  • Konfigurację sesji, która blokuje edycję testów, oraz jednolinijkową bramkę dowodzącą, że blokada zadziałała
  • Prompt recenzenta, który szuka zmian zachowania, więc przeglądasz tabelę ustaleń zamiast diffu
  • Tryby awarii typowe dla refaktoryzacji z agentem i sposób wyjścia z każdego z nich

Jak działa pętla refaktoryzacji przy zablokowanych testach?

Dział zatytułowany „Jak działa pętla refaktoryzacji przy zablokowanych testach?”

Refaktoryzacja jest poprawna, gdy obserwowalne zachowanie się nie zmienia. Jedynym tanim dowodem są testy, więc pętla najpierw chroni testy, a dopiero potem przenosi kod. Dbanie o strukturę kodu i jego testy, gdy pisze agent, to właśnie kodowanie rozszerzone.

  1. Utrwal zachowanie. Poproś Claude o testy charakteryzacyjne dla kodu w obecnej postaci i zacommituj je. Pełną technikę opisuje strona o testach charakteryzacyjnych.

  2. Udowodnij, że testy potrafią zawieść. Uruchom testy mutacyjne na pliku, który zamierzasz przebudować. Mutanty, które przeżyły, wskazują zachowanie, którego testy jeszcze nie pilnują.

  3. Oznacz punkt wyjścia i zablokuj testy. Wykonaj git tag refactor-base, a potem uruchom nową sesję, która nie może edytować plików testów.

  4. Zaplanuj ruchy. W trybie planowania (/plan albo Shift+Tab, który przełącza tryby) poproś o uporządkowaną listę nazwanych ruchów, od najmniej ryzykownego.

  5. Jeden nazwany ruch na turę. Wyodrębnij funkcję, wprowadź obiekt parametrów albo zastąp warunek. Jeden ruch nigdy nie jest przepisaniem od nowa.

  6. Uruchom bramkę. Pliki testów są takie same jak w refactor-base, zestaw przechodzi, a kontroler typów i linter nie zgłaszają błędów.

  7. Commit i powtórka. Jeden ruch na commit, więc zły krok cofa jeden git revert. Niezacommitowany zły krok cofniesz przez /rewind (albo dwukrotne Esc przy pustym prompcie). Checkpointy obejmują edycje plików wykonane przez Claude, a nie polecenia powłoki; zmiany z powłoki cofniesz przez git checkout -- . albo git stash.

Zespoły najczęściej pomijają kroki 2 i 3, a to właśnie one pozwalają zaufać wynikowi bez czytania go. Krok 5 ma znaczenie, bo mały ruch daje diff na tyle mały, że bramka i recenzent potrafią go ocenić.

Utrwal obecne zachowanie, zanim Claude przeniesie jakikolwiek kod

Dział zatytułowany „Utrwal obecne zachowanie, zanim Claude przeniesie jakikolwiek kod”

Poproś Claude, żeby najpierw przeczytał prawdziwy plik. Wtedy testuje kod, który istnieje, a nie kod, który sobie wyobraża. Testy charakteryzacyjne utrwalają to, co kod robi, łącznie z błędami, więc każ Claude zapisywać podejrzane błędy zamiast je poprawiać.

Zacommituj testy osobno, a potem sprawdź, czy mają zęby. Test, który przechodzi niezależnie od tego, co robi kod, niczego nie dowodzi przy refaktoryzacji. StrykerJS mutuje plik produkcyjny i raportuje, których mutantów testy nie zabiły. Uruchom go w terminalu:

Okno terminala
npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runner
npx stryker run --testRunner vitest --mutate src/services/checkout.service.ts

Wklej ocalałe mutanty z powrotem do Claude i poproś o testy, które je zabiją, a potem uruchom Strykera ponownie. Zapisz wynik mutacyjny: to część dowodów, które dołączasz do pull requesta. Strona jak silna jest twoja wyrocznia testowa opisuje testy mutacyjne dla Pythona i innych stosów.

Najczęstsza awaria refaktoryzacji z agentem to test poprawiany tak długo, aż przejdzie. Zamiast o to prosić, odbierz agentowi tę możliwość. Oznacz punkt wyjścia i uruchom sesję refaktoryzacji z zakazem edycji testów:

Okno terminala
git tag refactor-base
claude --disallowedTools "Edit(**/*.test.ts)"

Reguła zakazu zatrzymuje narzędzie edycji Claude na plikach testów. Nie zatrzyma polecenia powłoki w rodzaju sed -i, dlatego bramka sprawdza wynik, zamiast ufać regule. Tryb auto, od v2.1.283 tryb startowy interaktywnych sesji w terminalu i VS Code, pozwala większej liczbie poleceń powłoki ruszyć bez pytania cię o zgodę, co jest kolejnym powodem, by sprawdzać pliki, a nie regułę. Uruchamiaj ją w terminalu po każdym ruchu:

Okno terminala
git diff --exit-code refactor-base -- '*.test.ts' \
&& test -z "$(git ls-files --others --exclude-standard -- '*.test.ts')" \
&& npx vitest run \
&& npm run type-check \
&& npm run lint

git diff --exit-code kończy się błędem, jeśli jakikolwiek śledzony plik testu różni się od punktu wyjścia, zacommitowany czy nie. Pomija nowe, nieśledzone pliki, dlatego linia z git ls-files kończy się błędem, gdy pojawi się nowy plik testu. Żeby te same sprawdzenia uruchamiały się automatycznie po każdej edycji, podepnij je pod hook, jak opisuje strona o automatyzacji hookami. Ochrona wyroczni testowej przenosi ten pomysł do CI: pull request, który zmienia jednocześnie test i kod przez niego pokryty, zostaje oznaczony.

Który ruch refaktoryzacyjny pasuje do danego zapachu kodu?

Dział zatytułowany „Który ruch refaktoryzacyjny pasuje do danego zapachu kodu?”

Nazwij ruch w prompcie. „Posprzątaj to” zaprasza do przepisania całości, a nazwany ruch ma znany kształt, który sprawdzą i Claude, i bramka.

Zapach koduNazwany ruchDodatkowy dowód poza bramką
Jedna funkcja robi pięć rzeczyExtract FunctionWyodrębnione funkcje są czyste tam, gdzie czysty był oryginalny blok
Sześć parametrów zawsze wędruje razemIntroduce Parameter ObjectKażde miejsce wywołania się kompiluje, bez nowego any
switch po typie rośnie co kwartałReplace Conditional with Polymorphism (Strategy)Istniejące testy przechodzą bez zmian; publiczna sygnatura bez zmian
Klasa miesza niezwiązane odpowiedzialnościExtract Class za fasadąZewnętrzne importy bez zmian; metody fasady tylko delegują
Łańcuchy callbacków albo .then() w jednym moduleCallbacki na async/awaitKażde wywołanie ma await; ścieżka błędu ma test
Myląca nazwa użyta w 40 plikachRename Symbol przez serwer językaCzysty kontroler typów; żadnych pozostałych dopasowań w literałach tekstowych

Extract Function rozwiązuje większość problemów w stylu „ta funkcja jest za długa”. Claude bierze na siebie żmudną część: przekazanie współdzielonych zmiennych lokalnych jako parametrów i przepięcie oryginału na wywołania nowych funkcji. Kształt promptu jest ten sam w każdym języku, zmieniają się tylko idiomy.

In src/services/checkout.service.ts, processOrder does five jobs. Extract three
functions in the same file: validateOrder(input), calculatePricing(cart), and
reserveInventory(items). Leave the email send and the DB write in processOrder
as the orchestrator. Keep every existing type; do not widen any type to `any`.
Run `npx vitest run checkout` and `npm run type-check` and report both results.

W projekcie w Pythonie albo Elixirze dopasuj regułę zakazu i ścieżkę w bramce do układu testów, na przykład Edit(tests/**) i 'tests/'.

Sednem tego promptu jest warunek stopu w ostatnim akapicie. Test, który nie przechodzi po refaktoryzacji, oznacza zmianę zachowania. Właściwa reakcja to wycofanie zmiany i raport, a nie poprawianie asercji.

switch po typie płatności, który co kwartał dostaje nową gałąź, to podatek od utrzymania. Wzorzec Strategy przenosi każdy przypadek za wspólny interfejs. Ten ruch dotyka kilku nowych plików, a kolejność ma znaczenie, więc najpierw go zaplanuj: wejdź w tryb planowania, zatwierdź plan i dopiero wtedy pozwól Claude go wdrożyć. Przy takim wieloplikowym ruchu podnieś wysiłek przez /effort high, zanim rozważysz inny model; przegląd modeli wyjaśnia tę kolejność.

Test akceptacyjny refaktoryzacji do Strategy jest krótki: istniejący zestaw payments przechodzi bez żadnej zmiany. Najczęściej gubi się gałąź default. Fabryka, która dla nieznanego typu zwraca undefined tam, gdzie switch rzucał wyjątek, zmienia zachowanie, a wyłapie to test charakteryzacyjny dla nieznanego typu.

Gdy klasa wchłonęła niezwiązane ze sobą odpowiedzialności, podziel ją, ale zostaw cienką fasadę, żeby w tym samym pull requeście nie zmienił się żaden zewnętrzny import. Szwy weryfikuje kontroler typów, nie twoje oczy.

Załóż zgłoszenie na usunięcie fasady w kolejnym pull requeście. Fasada, której nikt nie śledzi, zwykle zostaje na zawsze.

Zmodernizuj jeden moduł legacy: callbacki na async/await

Dział zatytułowany „Zmodernizuj jeden moduł legacy: callbacki na async/await”

Pułapka modernizacji polega na tym, że implementacja zmienia kształt, a miejsca wywołań albo obsługa błędów zostają przy starym. Każ Claude znaleźć wszystkie wywołania, zanim cokolwiek zmieni. Ten prompt obejmuje jeden moduł i jego wywołania; tę samą migrację w całym kodzie przeprowadzisz podejściem fan-out opisanym w refaktoryzacji na dużą skalę.

Test, który wywołuje metodę z callbackiem, jest częścią kontraktu zmienianego przez ten ruch. Zmiana takiego testu to twoja decyzja, w osobnym commicie, a nie decyzja agenta.

Grep znajduje ciągi znaków, a nie symbole, więc tekstowa zmiana nazwy może pominąć ponowny eksport albo zmienić niezwiązane process w komentarzu. Serwer języka rozwiązuje referencje precyzyjnie. Oficjalny marketplace Anthropic zawiera wtyczki LSP; binarka serwera języka nie jest w nich dołączona, więc zainstaluj ją najpierw:

Okno terminala
npm install -g typescript-language-server typescript
claude plugin install typescript-lsp@claude-plugins-official

Dla Pythona tę parę tworzą pyright i pyright-lsp@claude-plugins-official. Z zainstalowaną wtyczką prompt w rodzaju „Find every reference to OrderService.applyDiscount and rename it to applyPromotion” rozwiązuje referencje przez serwer języka zamiast przez wyszukiwanie tekstu. Najlepsze wtyczki wymieniają wtyczki dla innych języków, a serwery MCP do analizy kodu opisują Serenę, która daje edycję na poziomie symboli w Claude Code, Codeksie i Cursorze.

Jak zweryfikować refaktoryzację bez czytania każdej linii?

Dział zatytułowany „Jak zweryfikować refaktoryzację bez czytania każdej linii?”

Weryfikujesz dowody, a nie diff. Refaktoryzacja, która przechodzi poniższe sprawdzenia, nie zmieniła żadnego zachowania widocznego dla testów i typów. Wynik mutacyjny mówi, ile zachowania to obejmuje, a przegląd zachowania pokrywa to, czego testy rzadko pilnują.

SprawdzeniePolecenie albo źródłoCzego dowodzi
Testy nietkniętegit diff --exit-code refactor-base -- '*.test.ts'Wyrocznia jest ta sama, która przechodziła przed refaktoryzacją
Zestaw zielonynpx vitest runUtrwalone zachowanie pozostało bez zmian
Typy i lint czystenpm run type-check i npm run lintBrak zerwanego szwu, poszerzonego typu czy martwego importu
Siła wyroczniWynik mutacyjny z kroku 2Jaką część przeniesionego kodu testy złapałyby na błędzie
Przegląd zachowaniaPrompt recenzenta poniżejZmiany, których testy rzadko pilnują: treść błędu, kolejność efektów ubocznych, brakujący await

Piąte sprawdzenie to drugi agent, który czyta diff z jednym pytaniem, więc ty czytasz jego tabelę ustaleń zamiast diffu. Świeży subagent nie widział rozumowania, które doprowadziło do zmiany, dlatego jest lepszym recenzentem niż sesja, która ją napisała. Żeby recenzent był wielokrotnego użytku, zapisz go jako własnego subagenta.

Gdy przegląd jest czysty, wbudowana umiejętność /simplify może uporządkować zmieniony kod. To przegląd jakości, a nie polowanie na błędy, więc po nim uruchom bramkę jeszcze raz. Do pull requesta dołącz wynik bramki, wynik mutacyjny i tabelę recenzenta; pakiet dowodów nadaje temu standardowy kształt. Programista, który otwiera pull request, podpisuje się pod dowodami, a recenzent-człowiek sprawdza dowody zamiast każdej przeniesionej linii. Dowody zamiast diffów wyjaśnia, dlaczego to jest teraz twoja praca.

Test został poprawiony, żeby przeszedł. git diff w bramce kończy się błędem. Przywróć test przez git checkout refactor-base -- path/to/file.test.ts, odrzuć ruch przez /rewind albo git revert i zacznij go od nowa w sesji uruchomionej z --disallowedTools.

Refaktoryzacja przeszła, ale zepsuła się ścieżka widoczna tylko na produkcji. Testy charakteryzacyjne nie pokrywały tej ścieżki, a przebieg mutacyjny powinien był pokazać tam ocalałe mutanty. Cofnij commit, dodaj test dla zepsutej ścieżki, potwierdź, że nie przechodzi na zepsutej wersji, i powtórz ruch.

Jedna tura dała diff typu big bang. Claude przepisał moduł zamiast wykonać jeden ruch. Cofnij to przez /rewind przed commitem albo git revert po nim. Ponów prompt z jednym nazwanym ruchem i poleceniem „stop after this move and wait for me”.

Testy charakteryzacyjne utrwaliły błąd. Tak ma być: refaktoryzacja nie zmienia działania kodu, łącznie z błędami. Popraw błąd w osobnym commicie po refaktoryzacji, ze zmianą testu i poprawką w tym samym commicie, żeby historia pokazywała świadomą zmianę zachowania.

Claude odpływa od konwencji projektu. W połowie refaktoryzacji wymyśla nowy format błędów albo układ katalogów. Wskaż mu wzorzec („match the error handling in src/api/routes/orders.ts exactly”) i zapisz konwencję w CLAUDE.md, żeby kolejne sesje zaczynały od niej.

Sesja gubi wątek po wielu ruchach. Długie refaktoryzacje zapełniają okno kontekstu i wczesne decyzje się rozmywają. Każdy ruch jest zacommitowany, więc między ruchami możesz uruchomić /clear i zacząć następny od planu i git log, bez żadnej straty.

„Refaktoryzacja” wydajnościowa zmienia wyniki. Zastąpienie pętli O(n²) to ryzyko zmiany zachowania, a nie czysty ruch. Najpierw utrwal wynik testem, podmień algorytm, potwierdź, że test przechodzi, i zmierz wydajność swoim narzędziem do benchmarków, zamiast ufać argumentowi asymptotycznemu.