為什麼 Windsurf 程式碼 Bug 會躲過審查
人工審查的規模上限,取決於一個人腦中能同時裝下多少行程式碼。範圍大而機械化的變更會以一種很特定的方式擊垮它:每一段 diff 單獨看都說得通,注意力於是開始滑掉。你審第十二個檔案時的仔細程度遠不如第一個,而問題幾乎總是藏在後段。
問題也很少出在「改了什麼」,而是出在「沒改到什麼」。一次漏掉某個呼叫點的重新命名、一個有三個建構子沒傳入的新必填欄位、一個到處都更新了卻獨漏錯誤處理路徑的 handler。
大範圍變更出錯的三種形態
不完整的全面替換
十一個呼叫點都更新了,漏掉一個,而它剛好在沒人每天會走到的路徑上。
型別通常抓不到,因為漏掉的那個位置本來型別就寫得很鬆。
假設漂移
兩個檔案對同一個值的意義產生了分歧,例如時間戳記到底是秒還是毫秒。
每個檔案內部都自洽,所以單獨看都不覺得有問題。
無聲的行為改變
一次重構保住了介面,卻悄悄改掉了順序、預設值或某條錯誤處理路徑。
針對舊實作寫的測試照樣通過,因為它們驗證的是介面。
這三種有個共同點:你只能靠實際操作應用程式才會發現,而不是靠讀它。這正是它們代價高昂的原因。
驗證行為,而不是驗證 diff
面對大範圍變更,真正有用的問題不是「每一處編輯對不對」,而是「產品原本會做的事還做得到嗎,再加上新的那件事」。這是一個關於行為的問題,而答案來自實際驅動已部署的應用程式,走一遍這次變更有可能影響到的流程。
覆蓋範圍不必窮盡也一樣有用。一次把結帳路徑上某個欄位全面改名的變更,只需要把結帳端到端跑過一次,價值就勝過仔細讀完十二份 diff。
把驗證用的 skill 安裝到編輯器裡,讓 agent 可以自己執行這道檢查,而不是等你動手。
終端機
npm install -g @testsprite/testsprite-cli
testsprite setup
如果你不想安裝任何東西,在儀表板上也能完成同樣的設定。CLI 的其餘功能都在 CLI 儲存庫。
把失敗本身交給 agent,而不是你的摘要
東西真的壞掉時,修復的循環只有在證據可用時才閉得起來:嘗試了什麼、應用程式實際做了什麼、兩者在哪裡分岔。拿到這些,agent 就能帶著明確目標回到同樣那十二個檔案裡。只拿到一張截圖,它就只能用猜的,而你又得回頭自己描述一遍。
讀一份大範圍 diff,而不自欺
審查一份動到十二個檔案的變更,有一種誠實的做法,而它不是逐段讀完每一塊 diff。做法是:在你打開 diff 之前,先決定「這次變更要正確,有哪些前提必須成立」,然後只檢查那幾件事。
以重新命名來說,前提是每個呼叫點都跟著改了——這件事編譯器大致能回答——以及有沒有任何地方是動態組出這個名稱的,而這件事編譯器答不了。以新增必填欄位來說,前提是每一條會建構出該物件的路徑,包括那些會默默繼續傳入不完整版本的測試與 fixture。以保留介面的重構來說,前提是順序與錯誤處理路徑,因為重構悄悄改掉的正是這些。
每次變更問三個問題,勝過掃過四百行程式碼,而且它會留給你一份誠實的清單:還有哪些事非得把東西跑起來才驗得了。
依影響範圍挑流程,而不是依重要性
人的直覺是先覆蓋最重要的功能。但面對大範圍變更,更好的問題是:這次變更有可能影響到哪些流程,並依「它們壞掉時有多無聲無息」來排序。
付款流程很重要,而它也一直被盯著。它壞掉的話,你一小時內就會知道。危險的是那些同樣要緊、卻是以週為單位而非以小時為單位在使用的流程:一次匯出、一封邀請、一個設定頁、一份排程寄送的摘要。一次沒做完的全面替換,就是躺在這些地方一整個月都沒人驚動。
所以在大範圍變更之後,先去跑那些安靜的路徑。它們覆蓋起來成本很低,而且沒有其他人在檢查它們。
讓它在每一個 pull request 上都執行
大範圍變更正好是人們最想趕快合併的那一種,因為 diff 看起來很無聊。這正是這道檢查應該自動執行、而不是靠人記得的理由。
有兩條路,而怎麼選其實取決於 pipeline 由誰掌管。從儀表板串接 GitHub App 完全不需要更動你的儲存庫,因為它是對你的建置本來就會產生的部署做出反應。而加上一個 GitHub Actions 步驟,則是把這道檢查放進儲存庫裡,讓它像建置流程的其他部分一樣接受審查。
不用讀,也能驗證大範圍變更
TestSprite 回答的正是大範圍 diff 回答不了的問題:產品原本會做的事還做得到嗎,再加上新的那件事。它會開啟已部署的應用程式,走過這次變更有可能影響到的流程,並回報行為而非結構。
這正是這個問題該有的解法形狀。不完整的全面替換、漂移的假設、悄悄改變的行為,在原始碼裡都看不見,一實際操作就無所遁形。測試覆蓋是從你的產品生成的,而不是人工撰寫的,所以那些安靜的流程——匯出、邀請、設定頁——不需要任何人記得,就會被包含進來。
真的出問題時,失敗會以一整包 agent 能直接動手處理的形式回傳,而這一點在這裡最關鍵:把它指向那個具體的分歧點,勝過要它重新檢查十二個檔案。
型別為什麼抓不到這些問題?
型別能抓到相當一部分不完整的全面替換,卻幾乎抓不到假設漂移或無聲的行為改變,因為這兩者在型別上本來就是正確的。
那我直接要求比較小的變更就好了嗎?
這確實有幫助,但也等於放棄了使用代理式編輯器的大半理由。加上一道驗證步驟,你就能保留這種幅度,同時仍然知道結果到底能不能用。
如果變更只發生在後端,這套做法還適用嗎?
適用,只是改以 API 層級的覆蓋,而不是透過介面。原則是一樣的:去跑行為,而不是去讀變更。
這樣一道檢查要花多久?
久到你不會每敲一次鍵盤就跑一次,短到足以放進一個 pull request 裡。那就是它該待的位置。
如果 agent 已經跑過單元測試了呢?
有用,但單元測試通常是照著這次變更剛剛改掉的那些假設寫出來的。它們確認的是內部一致性,而不是對外的正確性。