Refactor 沒症狀,Claude 出錯你看不見。用測試、原子 commit、手動點擊三道護欄鎖住它。
Refactor 是 Claude 最危險的任務。Bug 有症狀——點了沒反應、值是 undefined、報錯——你能看出 Claude 的修復對不對。Refactor 沒有。「它還能跑」可能意味著「測試還過」,但行為變了、你沒發現、一週後正式環境爆炸。
最近我用 Claude 做了一次比較大的 refactor:how2claude 的 x402 加密支付,從自己手寫的 PaymentHandler + FacilitatorClient(139 行)換到 x402-rails gem,同時把 Purchase.create! / Subscription.create! 重複散落在兩個 controller 裡的欄位映射抽到 model 類別方法。一個 commit,4 個檔案改、2 個檔案刪、2 個檔案新增。
我當時給的 prompt 就一個字:「重構」。
能這麼短,是因為周圍有護欄。
這個 branch 跑到那一步,221 個測試。Payment flow 的關鍵路徑全覆蓋。
Claude 做 refactor 前的預設動作不是「先看測試」。所以我要求它先跑一次 bin/rails test,確認全綠,再動。
跑完 refactor 再跑一次,還是全綠。這不代表沒回歸——只代表已知行為沒壞。
如果你的 codepath 沒有測試覆蓋,先讓 Claude 補一個最小測試把當前行為鎖住,跑通、commit。然後才 refactor。否則它做的不是 refactor,是 rewrite——你沒有辦法驗證前後等價。
這次 refactor 其實是兩件事:
Purchase / Subscription 的欄位映射從 controller 抽到 model 類別方法前端還有第三件:用 viem + x402-fetch 重寫 JS 端的簽名流程。
我讓 Claude 按自然邊界拆:backend + model extraction 一個 commit(9f3e239),frontend 單獨一個(93746d8)。每個 commit 自帶完整描述、異動檔案清單、為什麼這麼改。
好處:
- diff 可讀。看一次 commit 就懂一件事。
- 回滾粒度可控。如果正式環境發現前端 bug,git revert 93746d8 只回前端,後端保留。
- Claude 自己注意力更集中。一個 commit 做一件事,它的注意力也只覆蓋一塊。
refactor 做完,我讓 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 的 env vs 手寫 client 的回傳),但映射的欄位必須一一對應。坑 1:gem 的 Stimulus controllers 靜默沒載入
x402-rails gem 帶了自己的 Stimulus controllers。Claude 寫完程式碼,跑測試全綠。我手動點了一下支付按鈕——沒反應。
原因:config/importmap.rb 裡 @hotwired/stimulus 的 pin 指向一個不存在的 vendor 檔案,importmap 靜默丟掉了這個 pin。結果 gem 的 controllers 載入不了。測試抓不到,因為 bin/rails test 不執行 JS。
坑 2:credentials 裡 0x... 被 YAML 解析成整數
wallet_address: 0x833589...——沒引號,YAML 看到 0x 開頭以為是十六進位數字,讀出來是 integer。Facilitator 收到非 string,拒絕。Claude 寫設定時沒特意想 YAML 解析規則。
兩個坑都是我手動點了支付按鈕才發現的。測試過不代表功能對,refactor 後的人工驗證不能省。
Refactor 是「讓 Claude 自主」風險最高的場景。護欄不是給 Claude 的,是給你的——讓你在它出錯時能在 5 分鐘內發現,而不是正式環境爆炸時才發現。