Wzorce refaktoryzacji w Claude Code
ENWzorce 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.
Co daje refaktoryzacja przy zablokowanych testach
Dział zatytułowany „Co daje refaktoryzacja przy zablokowanych testach”- 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.
-
Utrwal zachowanie. Poproś Claude o testy charakteryzacyjne dla kodu w obecnej postaci i zacommituj je. Pełną technikę opisuje strona o testach charakteryzacyjnych.
-
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ą.
-
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. -
Zaplanuj ruchy. W trybie planowania (
/planalbo Shift+Tab, który przełącza tryby) poproś o uporządkowaną listę nazwanych ruchów, od najmniej ryzykownego. -
Jeden nazwany ruch na turę. Wyodrębnij funkcję, wprowadź obiekt parametrów albo zastąp warunek. Jeden ruch nigdy nie jest przepisaniem od nowa.
-
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. -
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 przezgit checkout -- .albogit 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:
npm install --save-dev @stryker-mutator/core @stryker-mutator/vitest-runnernpx stryker run --testRunner vitest --mutate src/services/checkout.service.tsWklej 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.
Zablokuj testy, żeby Claude nie mógł ich edytować
Dział zatytułowany „Zablokuj testy, żeby Claude nie mógł ich edytować”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:
git tag refactor-baseclaude --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:
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 lintgit 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 kodu | Nazwany ruch | Dodatkowy dowód poza bramką |
|---|---|---|
| Jedna funkcja robi pięć rzeczy | Extract Function | Wyodrębnione funkcje są czyste tam, gdzie czysty był oryginalny blok |
| Sześć parametrów zawsze wędruje razem | Introduce Parameter Object | Każ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ści | Extract Class za fasadą | Zewnętrzne importy bez zmian; metody fasady tylko delegują |
Łańcuchy callbacków albo .then() w jednym module | Callbacki na async/await | Każde wywołanie ma await; ścieżka błędu ma test |
| Myląca nazwa użyta w 40 plikach | Rename Symbol przez serwer języka | Czysty kontroler typów; żadnych pozostałych dopasowań w literałach tekstowych |
Wyodrębnij funkcję bez zmiany zachowania
Dział zatytułowany „Wyodrębnij funkcję bez zmiany zachowania”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 threefunctions in the same file: validateOrder(input), calculatePricing(cart), andreserveInventory(items). Leave the email send and the DB write in processOrderas 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.In app/services/checkout.py, refactor process_order by extractingvalidate_order(payload), calculate_pricing(cart), and reserve_inventory(items)as module-level functions with type hints. Keep process_order as theorchestrator. Do not edit anything under tests/. Run`pytest tests/test_checkout.py -q` and `mypy app/services` and show the output.In lib/shop/checkout.ex, process_order/1 does too much. Extractvalidate_order/1, calculate_pricing/1, and reserve_inventory/1 as privatefunctions and pipe them together in process_order/1. Keep the existingtypespecs. Do not edit anything under test/. Run`mix test test/shop/checkout_test.exs` and report the result.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.
Zastąp switch po typie polimorfizmem
Dział zatytułowany „Zastąp switch po typie polimorfizmem”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.
Podziel rozrośnięty serwis za fasadą
Dział zatytułowany „Podziel rozrośnięty serwis za fasadą”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.
Daj Claude nawigację po symbolach przy zmianie nazw
Dział zatytułowany „Daj Claude nawigację po symbolach przy zmianie nazw”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:
npm install -g typescript-language-server typescriptclaude plugin install typescript-lsp@claude-plugins-officialDla 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ą.
| Sprawdzenie | Polecenie albo źródło | Czego dowodzi |
|---|---|---|
| Testy nietknięte | git diff --exit-code refactor-base -- '*.test.ts' | Wyrocznia jest ta sama, która przechodziła przed refaktoryzacją |
| Zestaw zielony | npx vitest run | Utrwalone zachowanie pozostało bez zmian |
| Typy i lint czyste | npm run type-check i npm run lint | Brak zerwanego szwu, poszerzonego typu czy martwego importu |
| Siła wyroczni | Wynik mutacyjny z kroku 2 | Jaką część przeniesionego kodu testy złapałyby na błędzie |
| Przegląd zachowania | Prompt recenzenta poniżej | Zmiany, 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.
Kiedy refaktoryzacja z Claude Code idzie źle
Dział zatytułowany „Kiedy refaktoryzacja z Claude Code idzie źle”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.