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 風格也對路。但有種說不出的不放心。
我打了三個字,按了 Enter:
你建議 a 嗎?
它的回覆(刪了幾段程式碼):
會改主意——建議 A。
理由:proxy 檢測的整個目的是不信任 ABI 新鮮度。如果用 Solid Cache 做節流,我們等於又把「檢查過沒」掛在易失儲存上:
- 部署 / cache wipe 後,所有合約一起進入「未檢查」狀態 → 下一波存取觸發檢查風暴
- 沒辦法回答「這個合約多久沒複查過」這種問題
- 沒辦法做定時兜底:冷門合約可能幾週沒人存取,靠 lazy 永遠不會觸發
加一欄
last_proxy_check_at後能做的事:
- lazy 觸發(同 B 的邏輯)
- 定時兜底,搭配 recurring.yml 把超過 24h 沒複查的合約批次入隊,獨立於使用者存取
- 還能在頁面上顯示新鮮度:「Implementation verified 12 min ago」
代價:一次 migration + job 完成時一次
update_column(不走 callbacks,幾乎零成本)。短結論:B 看起來「更 Rails 原生、更輕」,但功能層面其實少了一截。A 才是把「我們不信任 ABI 新鮮度」這件事真正落地的方案。
注意它兩次推薦之間發生的事:
中間沒有新的事實進來。程式碼量、Rails 慣例、cache 行為,全沒變。變的是它評估方案時站在了哪一層。
第一次它在比較程式碼量。第二次它在比較「這個改動能不能解決一開始要解決的問題」。
我打這三個字的時候沒想著要逼它什麼。我只是在 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,這種查詢根本寫不出來。
那三個字的反問,價值大約相當於一週以後的一次重構。