免费

让 Claude 改主意

Claude 给的"更轻"推荐里藏着隐藏代价,一句反问就够它把代价摊开。


我在改 smarts —— 一个把以太坊智能合约渲染成"开发者文档"的 Rails 应用。前几天遇到一个老问题:合约会被升级。如果系统拿一份过期的 ABI 去渲染一个已经升级过的代理合约,等于在向用户撒谎。

我让 Claude 一起设计 proxy upgrade 的检测机制。它给了一份完整的技术方案,包括 EIP-1967 / Beacon proxy / ZeppelinOS 几种代理标准的兼容办法。到"什么时候触发检查"这一步,它列了两个选项:

# 选项 A: DB 上加 last_proxy_check_at,5min 节流
if @contract.last_proxy_check_at.nil? || @contract.last_proxy_check_at < 5.minutes.ago
  CheckProxyUpgradeJob.perform_later(@contract)
end

# 选项 B: Solid Cache 节流,更轻
unless Rails.cache.exist?("proxy_check:#{@contract.id}")
  Rails.cache.write("proxy_check:#{@contract.id}", true, expires_in: 5.minutes)
  CheckProxyUpgradeJob.perform_later(@contract)
end

它最后一句是:

选 B 更简单,不需要加列。

我盯着这两段代码看了一会儿。B 确实简单,没有 migration,全是 cache 调用,Rails 风格也对路。但有种说不出的不放心。

我打了三个字,按了回车:

你建议 a 吗?

Claude 重新算了一遍

它的回复(删了几段代码):

会改主意——建议 A

理由:proxy 检测的整个目的是不信任 ABI 新鲜度。如果用 Solid Cache 做节流,我们等于又把"检查过没"挂在易失存储上:

  • 部署 / cache wipe 后,所有合约一起进入"未检查"状态 → 下一波访问触发检查风暴
  • 没办法回答"这个合约多久没复查过"这种问题
  • 没法做定时兜底:冷门合约可能几周没人访问,靠 lazy 永远不会触发

加一列 last_proxy_check_at 后能做的事:

  1. lazy 触发(同 B 的逻辑)
  2. 定时兜底,配合 recurring.yml 把超过 24h 没复查的合约批量入队,独立于用户访问
  3. 还能在页面上展示新鲜度:"Implementation verified 12 min ago"

代价:一次 migration + job 完成时一次 update_column(不走 callbacks,几乎零成本)。

短结论:B 看起来"更 Rails 原生、更轻",但功能层面其实少了一截。A 才是把"我们不信任 ABI 新鲜度"这件事真正落地的方案。

注意它两次推荐之间发生的事:

  • 第一次:列出方案 → 比代码量 → 推 B("更简单")
  • 第二次:拿"为什么要做这件事"反推方案 → 发现 B 把状态挂在易失存储上跟"不信任 ABI 新鲜度"这件事自相矛盾 → 推 A

中间没有新的事实进来。代码量、Rails 习惯、cache 行为,全没变。变的是它评估方案时站在了哪一层

第一次它在比较代码量。第二次它在比较"这个改动能不能解决一开始要解决的问题"。

"你建议 a 吗?" 这三个字的作用

我打这三个字的时候没想着要逼它什么。我只是在 B 的代码上停住了,本能地想再问一次它的偏好。

但效果是把它从"两套方案选一个"拉回到"为什么我们要做这件事"。这个落差是关键。

LLM 给推荐时几乎一定会从"局部最优"出发:哪个 diff 更小、哪段代码更像 idiomatic Rails、哪种实现读起来更顺。这些维度都是真的。但它们经常和"这个 PR 是为了解决什么问题"互不打通。

A 和 B 在"5 分钟节流"这件事上是等价的。但 A 顺手解锁了三件 B 做不到的事——定时兜底、可观测性、UI 上展示新鲜度。如果你只看节流层,B 赢;如果你在意"为什么我们要这个 job",A 赢。

Claude 第一次没看到这一层,不是因为它不会看。是因为列方案对比时,它的默认视角就是"语法层 / Rails 层"。一句反问把它拉到"问题层",它马上能自己算出 B 的代价。

怎么把这变成日常

我做了几个小调整:

Claude 用"更简单 / 更轻 / 更原生"这种词收尾时,多看一眼。 这些是审美词,不是判断词。它们说的是"读起来舒服",不是"能落地"。

反问的成本几乎是零。 "你建议 X 吗?" 三五个字,Claude 会被迫站在它没站过的角度重新估一遍。哪怕它最后还是推同一个方案,理由也会更扎实,足够你判断要不要照做。

让它把"代价"摊开,而不是只列推荐。 这次的好结果不是来自我硬推 A,是来自 Claude 把 B 的隐藏成本(cache wipe 风暴、可观测性缺失、定时任务死路)写出来。这些写出来之前,我自己也没意识到。

最后落地的是 A:加了 last_proxy_check_at 一列,lazy 触发 + 24h 定时兜底,UI 上多了一行 Implementation verified 12 min ago。三天后我又改了一处别的代码,这一列让我能写出 Contract.where("last_proxy_check_at < ?", 24.hours.ago).find_each 这种查询——如果当时选了 B,这种查询根本写不出来。

那三个字的反问,价值大约相当于一周以后的一次重构。