Free

Віддаємо рефакторинг Claude

Рефакторинг не має симптомів — помилок Claude не видно. Три огорожі: тести, атомарні коміти, ручний клік.


Рефакторинг — задача, у якій Claude найнебезпечніший. Баг має симптоми — кнопка не реагує, значення undefined, стек-трейс — тому ти можеш сказати, чи спрацював фікс Claude. У рефакторингу симптомів немає. «Все ще працює» може означати «тести все ще проходять», а поведінка тихо змінилася, ти не помітив і через тиждень продакшен палає.

Нещодавно я зробив з Claude доволі великий рефакторинг у how2claude: переніс крипто-платежі x402 з власноруч написаних PaymentHandler + FacilitatorClient (139 рядків) на gem x402-rails і водночас виніс мапінг полів Purchase.create! / Subscription.create! — продубльований у двох контролерах — у класові методи моделі. Один коміт, 4 файли змінено, 2 видалено, 2 додано.

Мій промпт був одним словом: «refactor».

Таким коротким він міг бути тому, що навколо стояли огорожі.

Огорожа #1: без тестів рефакторинг не робимо

До моменту, коли гілка дійшла до цього місця, у ній було 221 тест. Усі критичні шляхи потоку оплати покриті.

Стандартна дія Claude перед рефакторингом — не «спершу подивитися тести». Тому я сказав йому спершу запустити bin/rails test, підтвердити зелене, і лише потім щось чіпати.

Після рефакторингу прогнати ще раз. Все ще зелене. Це не означає «регресій немає» — це означає, що відома поведінка не зламана.

Якщо твій кодпоч не вкритий тестами, нехай Claude напише мінімальний тест, що фіксує поточну поведінку. Прогони, закомміть. Аж потім рефакторинг. Інакше те, що він робить, — не рефакторинг, а переписування, і ти не маєш способу перевірити еквівалентність.

Огорожа #2: змусити поділити зміни на атомарні коміти

Цей рефакторинг насправді був двома речами:

  1. Бекенд x402: власноруч → gem
  2. Мапінг полів Purchase / Subscription: контролер → класові методи моделі

У фронтенді була третя: переписати підпис на JS-боці через viem + x402-fetch.

Я примусив Claude поділити за природними межами: бекенд + витяг моделі — один коміт (9f3e239), фронтенд — окремий (93746d8). Кожен коміт несе повний опис, список файлів і причину зміни.

Переваги:
- Дифи залишаються читабельними. Один коміт, одна річ.
- Гранулярність відкату. Якщо в проді знайшли баг фронтенду, git revert 93746d8 відкочує лише фронт, бекенд залишається.
- Увага самого Claude залишається сфокусованою. Один коміт, одна річ — і його увага накриває лише цю річ.

Огорожа #3: прочитай диф, перш ніж сказати «готово»

Коли рефакторинг завершений, я зупиняю Claude і прошу показати git diff --staged. Тести не запускати, додаток не запускати — спершу читати диф.

Сигнали, які я сканую:

  • Що він видалив? app/services/x402/payment_handler.rb видалений цілком — ок, це сенс міграції на gem. Але якщо видалив те, що я не просив чіпати, я зупиняюся і запитую.
  • Чи змінився мапінг полів? Purchase.create!(wallet_address: verify_result["payer"], ...)Purchase.record_x402!(payment:, settlement:) тепер читає payment[:payer]. Джерело змінилося (request.env у gem vs значення, яке повертав старий клієнт), але поля мають відображатися один до одного.
  • Зміни «по дорозі». Claude обожнює виправляти те, що «виглядає неправильно», під час рефакторингу — переформульовувати повідомлення про помилки, перейменовувати змінні, виділяти метод, який, на його думку, має існувати. За цим треба стежити. Обіцянка рефакторингу — «еквівалентна поведінка». Зміна мимохідь цю обіцянку ламає.

Дві помилки Claude цього разу

Пастка 1: Stimulus-контролери gem мовчки не завантажилися

gem x402-rails постачається з власними Stimulus-контролерами. Claude написав код, тести пройшли зеленими. Я вручну клацнув кнопку оплати — нічого.

Причина: у config/importmap.rb pin для @hotwired/stimulus указував на неіснуючий vendor-файл, і importmap мовчки викинув цей pin. Контролери gem так і не завантажилися. Тести цього не ловлять, бо bin/rails test не виконує JS.

Пастка 2: YAML розпарсив 0x... як integer

wallet_address: 0x833589... — без лапок. YAML побачив префікс 0x, прочитав як шістнадцятковий integer. Facilitator отримав не-рядок і відхилив. Claude, пишучи конфіг, не зупинився подумати про правила парсингу YAML.

Обидві пастки спіймалися тому, що я клацнув реальну кнопку. Зелені тести — не те саме, що робоча фіча. Ручна перевірка після рефакторингу не опціональна.

Повний флоу рефакторингу з Claude

  1. Проганяєш увесь тест-сьют. Підтверджуєш зелене. Якщо цільовий код не має покриття, примушуєш Claude написати мінімальний тест, що фіксує поточну поведінку, і комітиш.
  2. Кажеш, яку поверхню хочеш витягти. «Refactor» вистачає, коли дублювання очевидне; коли ні — «винеси мапінг полів з X у класові методи Y».
  3. Ділиш на атомарні коміти. Один коміт, одна річ.
  4. Сам читаєш диф. Що видалено, чи тримається мапінг полів, чи не змінилося щось мимохідь.
  5. Прогоняєш тести ще раз. Зелене.
  6. Якщо зачеплено видимий користувачеві шлях — клацни по фічі вручну. Шари, куди тести не дістають — JS, importmap, CDN, парсинг YAML — видно лише оком.

Рефакторинг — сценарій «пустити Claude за кермо» з найвищим ризиком. Огорожі не для Claude. Вони для тебе — щоб, коли Claude помилиться, ти зловив це за 5 хвилин, а не під час пожежі в проді.