У рефакторинга нет симптомов — ошибок Claude не видно. Три ограждения: тесты, атомарные коммиты, ручной клик.
Рефакторинг — задача, в которой Claude опаснее всего. У бага есть симптомы — кнопка не реагирует, значение undefined, стек-трейс — поэтому ты можешь сказать, сработал ли фикс Claude. У рефакторинга симптомов нет. «Всё ещё работает» может означать «тесты всё ещё проходят», пока поведение молча изменилось, ты не заметил, и через неделю продакшен горит.
Недавно я сделал с Claude довольно большой рефакторинг в how2claude: перенёс крипто-платежи x402 с самописных PaymentHandler + FacilitatorClient (139 строк) на gem x402-rails и одновременно вынес маппинг полей у Purchase.create! / Subscription.create! — дублировавшийся в двух контроллерах — в классовые методы модели. Один коммит, 4 файла изменены, 2 удалены, 2 добавлены.
Мой промпт был одним словом: «refactor».
Такой короткий промпт получался, потому что вокруг стояли ограждения.
К моменту, когда ветка дошла до этого места, в ней было 222 теста. Все критические пути флоу оплаты покрыты.
Дефолтное действие Claude перед рефакторингом — не «сначала посмотреть тесты». Поэтому я сказал ему сначала запустить bin/rails test, убедиться, что зелёно, и только потом что-то трогать.
После рефакторинга прогнать снова. Всё ещё зелёно. Это не значит «регрессий нет» — это значит, что известное поведение не сломано.
Если у твоего кодпути нет покрытия тестами, попроси Claude написать минимальный тест, фиксирующий текущее поведение. Прогони, закоммить. И только потом рефакторинг. Иначе то, что он делает, — не рефакторинг, а переписывание, и у тебя нет способа проверить эквивалентность.
Этот рефакторинг на самом деле был двумя вещами:
Purchase / Subscription: из контроллера → в классовые методы моделиВо фронтенде была третья: переписать подпись на стороне JS через viem + x402-fetch.
Я заставил Claude разделить по естественным границам: бэкенд + извлечение модели — один коммит (9f3e239), фронтенд — отдельный (93746d8). Каждый коммит несёт полное описание, список файлов и причину.
Плюсы:
- Диффы остаются читаемыми. Один коммит, одна вещь.
- Гранулярность отката. Если в проде поймали баг фронтенда, git revert 93746d8 откатывает только фронт, бэкенд остаётся.
- Собственное внимание Claude остаётся сфокусированным. Один коммит, одна вещь — и его внимание накрывает только эту вещь.
Когда рефакторинг завершён, я останавливаю 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 возвращаемое значение старого клиента), но поля должны отображаться один к одному.Ловушка 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 за руль» с самым высоким риском. Ограждения не для Claude. Они для тебя — чтобы, когда Claude что-то напортачит, ты поймал это за 5 минут, а не на пожаре в проде.