Free

Отдаём рефакторинг Claude

У рефакторинга нет симптомов — ошибок Claude не видно. Три ограждения: тесты, атомарные коммиты, ручной клик.


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

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

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

Такой короткий промпт получался, потому что вокруг стояли ограждения.

Ограждение #1: без тестов рефакторинг не делать

К моменту, когда ветка дошла до этого места, в ней было 222 теста. Все критические пути флоу оплаты покрыты.

Дефолтное действие 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 минут, а не на пожаре в проде.