# Diagnosing Bugs

> 硬 bug 與效能回歸的診斷迴圈。當使用者說「diagnose」/「debug this」，或回報東西壞了/拋錯/失敗/很慢時使用。

- Skill: `shumingyang-opencode/diagnosing-bugs` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add shumingyang-opencode/diagnosing-bugs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/shumingyang-opencode/diagnosing-bugs/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: shumingyang-opencode (https://skillmd.com/u/shumingyang-opencode)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/shumingyang-opencode/diagnosing-bugs

---


# 診斷 Bug

一套應付硬 bug 的紀律。只有明確證成時才可跳過階段。

探索程式碼時，先讀 `CONTEXT.md`（如果存在）以取得相關模組的清晰心智模型，並檢查你要觸及的區域中的 ADR。

## 第一階段——建立回饋迴圈

**這就是本技能的核心。** 其他一切都是機械性作業。如果你對這個 bug 有一個**緊密**的通過/失敗訊號——一個會因為_這個_ bug 而變紅的訊號——你就會找到原因；二分、假設檢驗與插樁都只是在消耗它。如果你沒有這樣的訊號，再怎麼瞪著程式碼看也救不了你。

在這裡投入不成比例的精力。**要積極。要有創意。拒絕放棄。**

### 建立回饋迴圈的方法——大致依此順序嘗試

1. **失敗測試**，在任何觸及 bug 的接縫——單元、整合、e2e。
2. **curl / HTTP 腳本**，對著正在執行的開發伺服器。
3. **CLI 呼叫**，搭配固定裝置輸入，把 stdout 跟已知良好快照做 diff。
4. **無頭瀏覽器腳本**（Playwright / Puppeteer）——驅動 UI，對 DOM / 主控台 / 網路做斷言。
5. **重播擷取的追蹤**。把真實的網路請求 / payload / 事件日誌存到磁碟；在隔離環境中透過程式碼路徑重播。
6. **一次性測試架**。啟動系統的最小子集合（一個服務、模擬過的相依），用單一函式呼叫去觸發 bug 的程式碼路徑。
7. **屬性 / 模糊測試迴圈**。如果 bug 是「輸出偶爾錯誤」，跑 1000 個隨機輸入，找出失敗模式。
8. **二分測試架**。如果 bug 出現在兩個已知狀態之間（commit、資料集、版本），自動化「在狀態 X 啟動、檢查、重複」，讓你能 `git bisect run`。
9. **差異迴圈**。把相同輸入跑過舊版 vs 新版（或兩種設定），diff 輸出。
10. **HITL bash 腳本**。最後手段。如果必須由人點擊，用 `scripts/hitl-loop.template.sh` 驅動_他們_，讓迴圈仍然結構化。擷取的輸出會回饋給你。

建立正確的回饋迴圈，bug 就完成九成了。

### 收緊迴圈

把迴圈當產品來經營。一旦有了_一個_迴圈，就**收緊**它：

- 能不能讓它更快？（快取設定、跳過無關的初始化、縮小測試範圍。）
- 能不能讓訊號更銳利？（斷言具體症狀，而不是「沒有崩潰」。）
- 能不能讓它更確定？（釘住時間、播種 RNG、隔離檔案系統、凍結網路。）

一個要花 30 秒的搖擺迴圈只比沒有迴圈好一點點；一個 2 秒且確定的是緊密的——這是除錯的超能力。

### 非確定性 bug

目標不是乾淨的重現，而是**更高的重現率**。把觸發器重複 100 次、平行化、加壓力、收窄時間窗、注入 sleep。50% 會搖擺的 bug 可以除錯；1% 的不能——持續拉高重現率，直到它能被除錯。

### 當你真的無法建立迴圈

停下來，明確說出來。列出你試過什麼。向使用者要求：(a) 存取能重現它的任何環境，(b) 一個擷取的產物（HAR 檔、日誌傾倒、核心傾倒、帶時間戳的螢幕錄影），或 (c) 加入暫時正式環境插樁的權限。**不要**在沒有迴圈的情況下繼續做假設。

### 完成標準——一個會變紅的緊密迴圈

第一階段完成的條件是迴圈**緊密**且**能變紅**：你能指認出**一個指令**——一個腳本路徑、一次測試呼叫、一個 curl——你**至少已經跑過一次**（貼出該呼叫及其輸出），而且它：

- [ ] **能變紅**——它驅動真正的 bug 程式碼路徑，並斷言**使用者的確切症狀**，所以它會因為這個 bug 而變紅、修好後變綠。不是「跑起來沒有報錯」——它必須能_抓到這個特定 bug_。
- [ ] **確定**——每次執行結果相同（搖擺 bug：釘住的高重現率，如上述）。
- [ ] **快速**——幾秒，不是幾分鐘。
- [ ] **代理可執行**——你可以無人看管地跑它；只有透過 `scripts/hitl-loop.template.sh` 才需要人在迴圈中。

如果你發現自己在這個指令存在之前就讀程式碼建理論，**停下來——直接跳到假設正是本技能要防止的失敗模式。** 沒有能變紅的指令，就沒有第二階段。

## 第二階段——重現 + 最小化

執行迴圈。看它變紅——bug 出現了。

確認：

- [ ] 迴圈產生的是**使用者**描述的那個失敗模式——不是碰巧在附近的其他失敗。錯誤的 bug = 錯誤的修復。
- [ ] 失敗在多次執行間可重現（或對非確定性 bug，重現率高到足以針對它除錯）。
- [ ] 你已擷取確切的症狀（錯誤訊息、錯誤輸出、緩慢的時序），讓後續階段可以驗證修復確實對症下藥。

### 最小化

一旦它變紅，把重現縮到**仍會變紅的最小情境**。**一次一個**地砍掉輸入、呼叫者、設定、資料與步驟，每次砍完重跑迴圈——只保留對失敗真正承重的東西。

為什麼要費心：最小重現會縮小第三階段的假設空間（剩下可懷疑的活動部件更少），並成為第五階段的乾淨回歸測試。

完成條件是**每個剩餘元素都承重**——移除任何一個都會讓迴圈變綠。

在完成重現**與**最小化之前，不要往下走。

## 第三階段——提出假設

在測試任何假設之前，產生 **3–5 個排序的假設**。單一假設容易錨定在第一個看起來合理的想法上。

每個假設都必須**可否證**：說出它做出的預測。

> 格式：「如果 <X> 是原因，那麼 <改變 Y> 會讓 bug 消失 / <改變 Z> 會讓它更糟。」

如果你說不出預測，那假設只是一種感覺——丟棄它或把它磨利。

**在測試之前，把排序後的清單給使用者看。** 他們通常擁有能立刻重新排序的領域知識（「我們剛剛部署了 #3 的改動」），或知道他們已經排除的假設。便宜的檢查點，省下大把時間。不要被它卡住——如果使用者 AFK，就照你的排序往下走。

## 第四階段——插樁

每個探針都必須對應到第三階段的特定預測。**一次只改一個變數。**

工具偏好：

1. **除錯器 / REPL 檢視**，如果環境支援的話。一個中斷點勝過十個日誌。
2. **具針對性的日誌**，放在能區分假設的邊界上。
3. 永遠不要「全部打日誌然後 grep」。

**用獨特前綴標記每個除錯日誌**，例如 `[DEBUG-a4f2]`。最後的清理變成單次 grep。未標記的日誌存活；標記的日誌陣亡。

**效能分支。** 對效能回歸，日誌通常是錯的。改成：先建立基線量測（時序測試架、`performance.now()`、profiler、查詢計畫），再二分。先量測，後修復。

## 第五階段——修復 + 回歸測試

**在修復之前**寫回歸測試——但只有當它有**正確的接縫**時。

正確的接縫是測試在**呼叫點實際發生的 bug 模式**上運作的接縫。如果唯一可用的接縫太淺（bug 需要多個呼叫者時卻只有單一呼叫者測試、無法重現觸發 bug 的鏈的單元測試），在那裡的回歸測試會給你錯誤的信心。

**如果沒有正確的接縫，那本身就是發現。** 記下它。程式庫架構正在阻止 bug 被鎖定。把這個標記給下一階段。

如果正確的接縫存在：

1. 把最小化的重現變成該接縫上的失敗測試。
2. 看它失敗。
3. 套用修復。
4. 看它通過。
5. 針對原始（未最小化）情境重跑第一階段的回饋迴圈。

## 第六階段——清理 + 事後檢討

宣告完成前必須完成：

- [ ] 原始重現不再重現（重跑第一階段迴圈）
- [ ] 回歸測試通過（或接縫缺失已記錄在案）
- [ ] 所有 `[DEBUG-...]` 插樁已移除（grep 該前綴）
- [ ] 一次性原型已刪除（或移到清楚標記的除錯位置）
- [ ] 正確的假設寫進 commit / PR 訊息——讓下一位除錯者學到

**然後問：什麼能防止這個 bug？** 如果答案涉及架構變更（沒有好的測試接縫、呼叫者糾纏不清、隱藏的耦合），把具體內容交接給 `/improve-codebase-architecture` 技能。在修復完成**之後**再提出建議，不要提前——你現在比開始時擁有更多資訊。

