🎯 什麼情境該想到我

當你卡在一個 bug、開始亂改亂試、卻毫無進展時。

⚙️ 怎麼用

  1. 先讓 bug 可穩定重現:不能重現就先想辦法重現(最小化輸入)。
  2. 看線索、做假設:讀錯誤訊息與最近的改動,別急著改。
  3. 二分逼近:用 print/log 或註解,把問題範圍一半一半地縮小。
  4. 一次只改一個變因,改完驗證,避免製造新 bug。
  5. 相信但要驗證:你「確定沒問題」的地方,正是最該檢查的地方。
  6. 修好後補一個測試鎖住它,避免復發。

★ 書中的完整招式(第 5 章「除錯」)

書把 bug 分成兩類,招式不同:

有好線索、錯誤簡單時(5.2):

招式意思
尋找熟悉的模式「我見過這個」常是解答的開始;常見錯誤都有特有的標誌
檢查最近的改動新版本有錯、舊版本沒有 → 新程式碼一定是問題的一部分
不要兩次犯同樣的錯修好一個 bug 後,問「程式裡其他地方是不是也犯了同樣的錯?」
現在就除錯,不要以後書舉「火星探路者」號:發射前測試時出現過重開機,當時在忙別的而忽略,結果到了火星上每天重開一次
取得堆疊追蹤失敗位置的行號、不該出現的參數值(空指標、超大整數、該正卻負)都是線索
動手前先讀抵抗立刻改程式碼的誘惑,先想清楚;休息一下,讓你看到程式碼實際寫的而不是你以為寫的
把程式碼解釋給別人聽書記載某大學計算中心在諮詢台放了一隻絨毛玩具熊,學生得先跟熊解釋,再找真人

沒有線索、錯誤困難時(5.3):

招式意思
讓錯誤可以重現構造出能穩定觸發的輸入,並包成一個指令就能跑
分而治之把輸入縮到最小;對程式碼也做二分——排除一半,看錯誤還在不在
研究失敗的數字特徵書中例子:字元偶爾消失 → 發現都發生在 1024 位元組邊界 → 緩衝區最後一格被空字元覆蓋
印出輸出來縮小範圍在關鍵點印出狀態,看從哪裡開始不對
寫自我檢查的程式碼在程式裡加斷言,讓它自己報告不一致
寫 log 檔事後才能重建發生了什麼
畫圖散佈圖比一列數字更容易看出異常
使用工具diff、版本控制——可以回到已知良好的狀態
保留紀錄記下試過什麼、結果如何,避免重複、避免漏掉

★ 現代工具把這些招式自動化

書中招式Go 的工具做法
檢查最近的改動 + 二分git bisect rungit bisect start BAD GOOD 後 git bisect run go test ./pkg/ -run TestX,自動在 commit 歷史上二分,找出第一個壞掉的 commit
讓錯誤可重現 + 縮小輸入fuzzing(go test -fuzz=FuzzX,Go 1.18+)找到失敗輸入時自動寫進 testdata/fuzz/,之後每次 go test 都會重跑它
取得堆疊追蹤panic 自帶;卡住的程式送 SIGQUIT(Ctrl+\)會印出所有 goroutine 的堆疊死結、goroutine 洩漏最有用
研究數字特徵-race(資料競爭)、-count=100(重複跑,抓不穩定)「偶爾」失敗的 bug 先用 go test -race -count=100 跑一輪
寫自我檢查的程式碼測試裡的不變式檢查;正式環境的 metrics
使用除錯器Delve:dlv test ./pkg -- -test.run TestX需要看變數在哪一步變壞時

★ 除錯的科學方法:一次一個假設

  1. 觀察:錯誤訊息、log、重現步驟。
  2. 假設:「我認為是 X 造成的」——要具體到可以被證偽。
  3. 預測:「如果是 X,那改 Y 之後現象應該變成 Z。」
  4. 實驗:只改 Y,看現象。
  5. 結論:符合預測 → 假設被支持;不符 → 划掉這個假設,寫下來,換下一個。

最常見的失敗是跳過第 3 步:沒有預測就改,改完現象變了卻不知道代表什麼。

🧪 我實際套用的紀錄

  • 2026-07-14:(待填)

⚠️ 注意

  • 難重現的 bug 常來自並發/未初始化/邊界;先穩定環境再查。

  • 「修好了」要證明:bug 消失可能是巧合(時序改變、快取、重啟)。修正前先寫一個會失敗的測試,修正後它變綠——這才證明你修的是對的地方。

  • 書的另一個提醒:除錯時容易「在沒有錯的地方找了很久」——多半是你對程式怎麼運作的心智模型本身就錯了。這時要回到最初的假設重新檢查。

🔗 相關工具