昨天的結論是:Claude 讀規格寫實作,也讀同一份規格寫測試,那 code review 呢?今天就拿系統裡那個已知的問題來測試。
昨天講掃描型檢查的時候提過「要清單不要結論」。放到 review 上更明顯,我把三種問法排開來看:
| 問法 | 拿到什麼 | 為什麼 |
|---|---|---|
| 「幫我 review 有沒有安全問題」 | 一篇作文:建議加 rate limit、建議用 HTTPS、建議檢查輸入 | 它沒有判準,只能列出通用清單 |
| 「列出所有端點與它宣告的 dependency」 | 一張表,我自己看得出哪幾列不對 | 判準在我腦子裡,它只負責蒐集 |
「拿 contracts/api.md §4 的權限欄,逐列對照實作」 |
直接指出不一致的那幾列 | 判準被交出去了 |
第三種才是 review 該有的樣子。差別不在它多聰明,而是我有沒有把判準給它。
這跟昨天那條分界線是同一件事:判準寫在文件裡的,它做得比我好;判準還在我腦子裡的,它只能猜。差別是 review 這裡多了一個選項——我可以在提問的當下把判準交出去,不一定要事先寫進規格。
Review 會產出「發現」。發現這個東西的麻煩在於,它讀起來都很有說服力——有檔名、有行號、有推論鏈,看起來像結論其實是假設。
今天我自己讀 lib/api.ts 的時候就抓到一個。這段是 HTTP 那一層處理錯誤的地方:
// 一張不再有效的 token 留著只會讓每個請求都 401。丟掉它,App 就會回到
// 登入畫面——但**只丟 token,不碰使用者正在編輯的內容**。
if (error.status === 401) clearToken()
然後 App.tsx 的第一行狀態是這樣:
const [token, setTokenState] = useState(currentToken)
useState 只讀一次。全 repo 沒有任何地方監聽 storage 變化。所以註解裡那句「丟掉它,App 就會回到登入畫面」,看起來是假的。
如果我就這樣寫進 review 報告,它會是一個錯的發現。
因為那句話裡其實藏了兩個獨立的主張,而它們不一定同真同假:
localStorage 裡的 token 真的被丟掉了所以我一條斷言寫一條測試:
it('後端回 401,localStorage 的 token 確實被丟掉', async () => { ... })
it('token 丟掉之後,畫面回到登入表單', async () => { ... })
跑出來:
× token 丟掉之後,畫面回到登入表單
TestingLibraryElementError: Unable to find role="button" and name "登入"
Tests 1 failed | 1 passed (2)
第一條綠、第二條紅。
所以真正的缺陷不是「token 沒被清掉」——它清得好好的。真正的缺陷是清掉了,但沒有人知道。React 的狀態沒有跟著變,使用者會坐在一個每個請求都失敗的畫面上,直到自己按重新整理。
這兩種說法會導向完全不同的修法。前者要去改 clearToken,後者要去改狀態怎麼傳。只差在有沒有拆開來驗。
整理一下今天實際撞到的:
這三類的共同點是:它們都不在「這次改了什麼」裡面。
明天:把這些檢查搬進 CI。