リファクタには症状がない。Claude が外しても見えない。テスト、原子コミット、手動クリックの 3 本のガードレールで押さえる。
リファクタリングは Claude にとって一番危険なタスクだ。バグには症状がある——ボタンが反応しない、値が undefined、スタックトレース——だから Claude の修正が正しいかどうか判断できる。リファクタリングには症状がない。「まだ動く」は「テストが通る」と同じ意味になりうる、裏で挙動が変わっていて、気づかず、一週間後に本番が炎上する。
最近 how2claude で Claude に大きめのリファクタリングをやらせた:x402 暗号決済を、自作の PaymentHandler + FacilitatorClient(139 行)から x402-rails gem に移行、同時に Purchase.create! / Subscription.create! の——二つのコントローラに散らばって重複していた——フィールドマッピングを model のクラスメソッドに抽出。1 コミット、4 ファイル変更、2 削除、2 追加。
プロンプトは一語だった:「リファクタリング」。
こんなに短くて済んだのは、周囲にガードレールがあったから。
そこまで来た時点でこのブランチにはテストが 221 本。決済フローのクリティカルパスは全部カバー済みだった。
Claude のデフォルト動作は「まずテストを見る」ではない。だから先に bin/rails test を走らせてグリーンを確認してから手を付けろと指示する。
リファクタリング後にもう一回走らせる。まだグリーン。これは「リグレッションがない」を意味しない——「既知の挙動」が壊れていない、としか言えない。
コードパスにテストがないなら、Claude に最小のテストを書かせて現挙動をロックし、通して、コミットする。それからリファクタリング。そうしないと Claude がやっているのはリファクタリングじゃなくて書き直しだ——前後の等価性を検証する手段がない。
今回のリファクタリングは実質 2 件:
Purchase / Subscription のフィールドマッピング:controller → model のクラスメソッドフロントエンドには 3 件目:viem + x402-fetch で JS 側の署名フローを書き直す。
Claude には自然な境界で分けさせた:バックエンド + モデル抽出で 1 コミット(9f3e239)、フロントエンドは別コミット(93746d8)。各コミットに完全な説明、変更ファイル一覧、なぜ変えたかを載せる。
メリット:
- diff が読める。1 コミット 1 件。
- ロールバックの粒度が制御できる。本番でフロントエンドのバグが出たら git revert 93746d8 でフロントだけ戻せる、バックエンドは残る。
- Claude 自身の注意も集中する。1 コミット 1 件だと、Claude の注意もそのひとつだけに向く。
リファクタリング完了後、Claude を止めて git diff --staged を出させる。テストは走らせない、アプリも起動しない、まず diff を読む。
チェックするシグナル:
app/services/x402/payment_handler.rb が丸ごと消えた——OK、gem 移行の本旨。だが触れと言っていないものを消していたら即座に問い詰める。Purchase.create!(wallet_address: verify_result["payer"], ...) → Purchase.record_x402!(payment:, settlement:) では payment[:payer]。ソースは変わった(gem の request.env vs 旧クライアントの戻り値)、でもフィールドは 1:1 で対応しないといけない。落とし穴 1:gem の Stimulus コントローラがサイレントにロードされなかった
x402-rails gem は自前の Stimulus コントローラを同梱する。Claude がコードを書いて、テストは全部グリーン。私が支払いボタンを手動でクリックしたら——無反応。
原因:config/importmap.rb にあった @hotwired/stimulus の pin が存在しない vendor ファイルを指していて、importmap がその pin を黙って破棄していた。結果、gem のコントローラは読み込まれない。テストでは捕まえられない、bin/rails test は JS を実行しないから。
落とし穴 2:YAML が 0x... を integer としてパースした
wallet_address: 0x833589...——クォートなし。YAML は 0x で始まるので 16 進数の整数と解釈。読み出すと integer。Facilitator は非文字列を受け取って拒否。Claude は config を書く時に YAML のパース規則を意識しなかった。
どちらの落とし穴も実際にボタンをクリックして初めて発覚した。テスト通過 ≠ 機能が動く。リファクタリング後の手動検証はサボれない。
リファクタリングは「Claude に運転させる」中でも最もリスクが高いシナリオだ。ガードレールは Claude のためにあるんじゃない、あなたのためにある——Claude が外したときに、5 分で気づけるように。本番が燃えてから、ではなく。