診斷錯誤
處理棘手錯誤的學科。只有在有明確理由時才可以跳過階段。
在探索程式碼庫時,請先閱讀 CONTEXT.md(如果存在),以建立相關模組的清晰心理模型,並檢查您正在觸及的區域中的 ADR。
紅化
此技能要求您顯示指令、輸出和捕獲的工件。首先對每個機密進行紅化處理:以 <REDACTED> 取代。針對環境變數建立迴圈,讓憑證保留在環境中,而不是顯示在您展示的內容中。捕獲的工件帶有認證標頭:僅引用包含所需訊號的行。
如果紅化後的輸出不足以診斷錯誤,請明確說明並詢問使用者。
階段 1:建立回饋迴路
這就是此技能的核心。 其他一切都是機械性的。如果您有一個緊湊的通過/失敗訊號來指示此錯誤(一個能對_這個_特定錯誤顯示紅色失敗的訊號),您將能找到原因;二分搜尋、假設測試和儀器化都只是利用這個訊號。如果您沒有這樣的訊號,那麼再怎麼盯著程式碼看也無濟於事。
在此投入不成比例的努力。要積極。要有創意。拒絕放棄。
構建回饋迴路的方法,大致按此順序
- 失敗的測試,在能到達該錯誤的任何接合處:單元測試、整合測試、端到端測試。
- Curl / HTTP 腳本,針對正在執行的開發伺服器。
- CLI 調用,使用固定輸入,並將 stdout 與已知正確的快照進行比對。
- 無頭瀏覽器腳本(Playwright / Puppeteer),驅動 UI 並對 DOM / Console / 網路進行斷言。
- 重放捕獲的軌跡。 將真實的網路請求 / 負載 / 事件日誌儲存到磁碟;在隔離環境中透過程式碼路徑重放它。
- 一次性測試用框架。 啟動系統的最小子集(一個服務,模擬依賴項),透過單個函數呼叫來執行錯誤程式碼路徑。
- 屬性 / 模糊測試迴圈。 如果錯誤是「有時輸出錯誤」,則執行 1000 次隨機輸入並尋找失敗模式。
- 二分搜尋框架。 如果錯誤出現在兩個已知狀態(提交、資料集、版本)之間,自動化「在狀態 X 啟動、檢查、重複」的流程,以便您可以使用
git bisect run。 - 差異化迴圈。 對相同輸入執行舊版 vs 新版(或兩種設定),並比對輸出。
- 人機協作 Bash 腳本。 最後手段。如果需要人類點擊,則使用
scripts/hitl-loop.template.sh來_驅動_他們,使迴圈仍然結構化。捕獲的輸出會回饋給您。
建立正確的回饋迴路,錯誤就解決了 90%。
緊湊化迴圈
將迴圈視為一個產品。一旦有了一個_迴圈_,請緊湊化它:
- 我能讓它更快嗎?(快取設定、跳過無關的初始化、縮小測試範圍。)
- 我能讓訊號更清晰嗎?(針對特定症狀進行斷言,而不是「沒有崩潰」。)
- 我能讓它更具確定性嗎?(固定時間、設定隨機數種子、隔離檔案系統、凍結網路。)
一個 30 秒的不穩定迴圈比沒有迴圈好不了多少;一個 2 秒的確定性迴圈則是緊湊的,是除錯的超能力。
非確定性錯誤
目標不是一個乾淨的重現,而是一個更高的重現率。將觸發條件循環 100 次、平行化、增加壓力、縮小時間視窗、注入睡眠。一個 50% 發生率的錯誤是可除錯的;1% 則不是,所以持續提高重現率直到它可以被除錯。
當您真的無法建立迴圈時
停止並明確說明。列出您嘗試過的方法。詢問使用者: (a) 存取能重現錯誤的環境權限,(b) 一個已紅化的捕獲工件(HAR 檔案、日誌轉儲、核心轉儲、帶有時間戳的螢幕錄影),或 (c) 允許添加暫時的生產環境儀器。在沒有迴圈的情況下,不得繼續進行假設。
完成標準:一個能顯示紅色的緊湊迴圈
階段 1 完成時,迴圈必須是緊湊的且能夠顯示紅色:您能指定一個指令(腳本路徑、測試調用、curl 命令),並且您已經至少執行過一次(請顯示該調用及其輸出,已紅化),而且該指令滿足:
- 能夠顯示紅色:它驅動了實際的錯誤程式碼路徑,並斷言了使用者描述的明確症狀,因此它能在這個錯誤上顯示紅色失敗,並在修復後顯示綠色成功。不只是「執行沒有錯誤」;它必須能夠_捕捉到這個特定的錯誤_。
- 確定性:每次執行結果相同(對於不穩定錯誤:根據上述,需有一個固定且高的重現率)。
- 快速:幾秒鐘,而非幾分鐘。
- 可被代理程式執行:您可以無人值守地執行它;只有透過
scripts/hitl-loop.template.sh才需要人類介入。
如果您發現自己在建立此指令之前就開始閱讀程式碼來構建理論,請停止:直接跳到假設正是此技能旨在防止的失敗模式。 在沒有能夠顯示紅色的指令之前,不進行階段 2。
階段 2:重現 + 最小化
執行迴圈。觀察它顯示紅色,表示錯誤出現。
確認:
- 該迴圈產生了使用者描述的失敗模式,而不是恰好發生在附近的其他失敗。錯誤的 bug = 錯誤的修復。
- 此失敗在多次執行中可重複(或者,對於非確定性錯誤,重現率高到足以在此基礎上進行除錯)。
- 您已捕獲了確切的症狀(錯誤訊息、錯誤輸出、緩慢的時間),以便後續階段能驗證修復是否真正解決了問題。
最小化
一旦迴圈變成紅色,將重現案例縮小到仍能顯示紅色的最小場景。一次只減少一個輸入、呼叫者、設定、資料或步驟,每次減少後重新執行迴圈,只保留對失敗至關重要的部分。
為什麼要這麼做:最小化的重現案例能縮小階段 3 的假設空間(需要懷疑的移動部件更少),並在階段 5 成為一個乾淨的回歸測試。
當每個剩餘元素都是至關重要的時,即完成:移除其中任何一個都會使迴圈變成綠色。
在您成功重現並最小化之前,不得繼續進行。
階段 3:假設
在測試任何假設之前,先產生 3–5 個排序後的假設。單一假設生成會錨定在第一個看似合理的想法上。
每個假設必須是可證偽的:陳述它所做出的預測。
格式:「如果 是原因,那麼 <改變 Y> 將使錯誤消失 / <改變 Z> 將使錯誤惡化。」
如果您無法陳述預測,那麼這個假設只是一種感覺:丟棄它或讓它更明確。
在測試之前,向使用者顯示排序後的清單。 使用者通常擁有領域知識,能立即重新排序(「我們剛剛部署了一個對第 3 項的更改」),或者知道他們已經排除了哪些假設。這是一個便宜的檢查點,能節省大量時間。但如果使用者不在,請不要阻塞進度;按您的排序繼續進行。
階段 4:儀器化
每次探查必須對應到階段 3 中的一個特定預測。一次只改變一個變數。
工具偏好:
- 如果環境支援,使用除錯器 / REPL 檢查。一個中斷點勝過十個日誌。
- 有針對性的日誌,放在能區分假設的邊界上。
- 永遠不要「記錄所有內容然後用 grep」。
為每個除錯日誌加上唯一的標籤,例如 [DEBUG-a4f2]。最後清理時只需一個 grep 即可完成。未標記的日誌保留;標記的日誌刪除。
效能分支。 對於效能回歸,日誌通常是錯誤的。相反:先建立基準測量(計時框架、performance.now()、分析器、查詢計畫),然後進行二分搜尋。先測量,後修復。
階段 5:修復 + 回歸測試
在修復之前撰寫回歸測試,但僅當存在一個正確的接合處時才這樣做。
正確的接合處是指測試能在呼叫點觸發真實的錯誤模式。如果唯一可用的接合處太淺(當錯誤需要多個呼叫者時卻只有單一呼叫者的測試,無法複製觸發錯誤的鏈條的單元測試),那麼在那裡的回歸測試會提供虛假的安全感。
如果不存在正確的接合處,那麼這本身就是一個發現。 記錄下來。程式碼庫的架構阻止了對這個錯誤的鎖定。將此標記出來供下一階段參考。
如果存在正確的接合處:
- 將最小化的重現案例轉換為該接合處的一個失敗測試。
- 觀察它失敗。
- 套用修復。
- 觀察它通過。
- 針對原始(未最小化的)場景重新執行階段 1 的回饋迴路。
階段 6:清理
在宣告完成之前必須滿足以下條件:
- 原始重現案例不再重現(重新執行階段 1 的迴圈)
- 回歸測試通過(或記錄了缺乏接合處的事實)
- 所有
[DEBUG-...]儀器已被移除(使用 grep 檢查該前綴) - 一次性原型已刪除(或移動到明確標記的除錯位置)
- 最終被證明正確的假設已在提交 / PR 訊息中說明,以便下一個除錯者學習