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 分钟内发现,而不是生产环境爆炸时才发现。