免費

讓 Claude 做 refactor

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 就一個字:「重構」。

能這麼短,是因為周圍有護欄。

護欄 #1:refactor 之前必須有測試

這個 branch 跑到那一步,221 個測試。Payment flow 的關鍵路徑全覆蓋。

Claude 做 refactor 前的預設動作不是「先看測試」。所以我要求它先跑一次 bin/rails test,確認全綠,再動。

跑完 refactor 再跑一次,還是全綠。這不代表沒回歸——只代表已知行為沒壞。

如果你的 codepath 沒有測試覆蓋,先讓 Claude 補一個最小測試把當前行為鎖住,跑通、commit。然後才 refactor。否則它做的不是 refactor,是 rewrite——你沒有辦法驗證前後等價。

護欄 #2:讓它把變動拆成原子 commit

這次 refactor 其實是兩件事:

  1. x402 後端從 hand-rolled 換成 gem
  2. 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 做一件事,它的注意力也只覆蓋一塊。

護欄 #3:diff 看一遍再說好

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 的回傳),但映射的欄位必須一一對應。
  • 「順手」的異動。Claude 很愛在 refactor 裡順手修一些「看起來不對」的地方——error message 措辭、變數重命名、抽一個它覺得該抽的方法。這些要警惕。refactor 的承諾是「行為等價」,順手改一改就不等價了。

Claude 這次踩的兩個坑

坑 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 後的人工驗證不能省。

給 Claude 做 refactor 的完整流程

  1. 跑一次全套測試,確認全綠。目標程式碼沒測試覆蓋的,先補最小測試鎖住行為。
  2. 說清楚你想抽的 surface。「重構」能用是因為重複太明顯;不明顯時就說「把 X 的欄位映射抽到 Y 的類別方法」。
  3. 原子 commit 拆分,一個 commit 一件事。
  4. diff 自己看一遍。刪了什麼、欄位映射對不對、有沒有順手改的。
  5. 再跑一次測試,全綠。
  6. 改的是使用者可見路徑,手動點一遍。JS、importmap、CDN、YAML 解析這類測試覆蓋不到的層,只能靠眼睛。

Refactor 是「讓 Claude 自主」風險最高的場景。護欄不是給 Claude 的,是給你的——讓你在它出錯時能在 5 分鐘內發現,而不是正式環境爆炸時才發現。