免费

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