
不要再亂猜了——重現與定位的科學
一句話總結:Debug 是一門學科,不是碰運氣。重現問題是最重要的第一步,二分法是定位問題最強大的思維工具。
結論先講:沒辦法重現的 bug 基本上沒辦法修。你以為你修好了,其實只是那個 bug 剛好沒出現。每次只改一個變數、寫下你的假設、先讀 error message——這三條紀律比任何工具都有用。
為什麼 console.log 大法不夠用
你一定遇過這種情況:程式不 work 了,加一行 console.log('here 1'),然後 here 2、here 3… 最後 50 行 console.log,你盯著 terminal 裡面一堆 here 7、here 12、undefined、[object Object],依然搞不清楚哪裡壞了。
更慘的是忘記清掉,同事 code review 看到滿螢幕的 console.log('為什麼你不 work')。別笑,你一定幹過。
console.log 不是不能用——但它有極限:
| 問題 | 為什麼 console.log 搞不定 |
|---|---|
| 非同步 Bug | 輸出順序可能跟你以為的不一樣 |
| 生產環境問題 | 你不可能在 production 加 log 重新部署 |
| 效能問題 | Log 不會告訴你哪行花了 800ms |
| 間歇性 Bug | 出現機率 1/100,你要跑 100 次 |
| 跨服務問題 | 前端 log 跟後端 log 對不起來 |
第一步:重現(Reproduce)
Bug 報告進來了,你第一件要做的事不是看程式碼,是重現它。
這是 Debug 最重要的一步,也是最多人跳過的。沒辦法重現的 bug,基本上沒辦法修——因為你沒辦法確認修復是真的修好了,還是 bug 剛好沒出現。
PM 跟你說「那個頁面壞了」,你要把它變成精確步驟:
## 重現步驟
1. 登入 test@example.com
2. 進入 /dashboard
3. 點「匯出報表」
4. 選日期 2025-01-01 ~ 2025-01-31
5. 點「確認匯出」
## 預期結果:下載 CSV
## 實際結果:白屏,console 有 TypeError: Cannot read property 'map' of undefined重點在最小——精簡到只剩觸發 bug 的最少操作。如果跳過步驟 4 也能觸發,那日期範圍跟 bug 無關,你排除了一整個方向。
「在我電腦上可以跑啊」
當你想說這句話的時候,先比對:
| 檢查項 | 你的環境 | 出問題的環境 |
|---|---|---|
| Runtime 版本 | Node 18.17 | Node 16.14 |
| 資料庫 | SQLite | PostgreSQL |
| 資料量 | 100 筆 | 1,000,000 筆 |
| SSL | 無 | 有 |
| CDN | 無 | 有 |
環境差異是「只在 production 出現」的罪魁禍首。
第二步:定位範圍(Isolate)
你已經能重現 bug 了,現在要把範圍從「整個系統」縮小到「某一行程式碼」。
二分法:最強大的思維工具。 1000 行程式碼可能有問題,不用一行一行看。問:「問題在前 500 行還是後 500 行?」10 次二分後,定位到具體那一行。
git bisect:二分法的經典應用。 上禮拜的版本是好的,今天壞了,中間 128 個 commit:
git bisect start
git bisect bad # 目前版本壞的
git bisect good abc1234 # 上禮拜的版本好的
# Git checkout 中間的 commit,你測試一下
git bisect good # 或 git bisect bad
# 重複 7 次(log2(128)=7),找到引入 bug 的 commit
git bisect reset128 個 commit,7 次測試就定位。如果你的 commit message 寫得好(參考 CommitLint),找到那個 commit 後一看就知道改了什麼。
註解掉一半程式碼:
function processData(data) {
const step1 = transform(data);
const step2 = validate(step1);
const step3 = enrich(step2);
// const step4 = format(step3); // 先註解後半段
// const step5 = save(step4);
return step3; // 暫時回傳
}
// 問題消失了?那問題在 step4 或 step5由外而內:先看 Network 再看 Logic
不確定問題在哪一層?從最外面開始:
- Network tab 有沒有 request 失敗?
- 有 → Status code 是什麼?4xx 是前端問題或權限,5xx 看後端 log
- 沒有 → 前端邏輯問題(根本沒發出 request)
- 後端 log 的錯誤是 DB 相關還是邏輯相關?
- DB 相關 → 查 query 和 connection pool
- 邏輯相關 → 查 business logic 和 edge case
反方向也可以:懷疑某個函式有問題?先寫單元測試確認。單元測試過了?擴大到整合測試。這個思路跟 測試策略 的分層概念完全一致。
這篇的重點回顧
Debug 是學科不是碰運氣。第一步永遠是重現——最小重現步驟、環境差異比對。第二步是定位——二分法、git bisect、由外而內分層。
下一篇看前端 Debug 工具箱。
系列文章:
- 你在這裡 → Debug 方法論(一):重現與定位
- Debug 方法論(二):前端 Debug 工具箱
- Debug 方法論(三):後端與系統工具 + Debug 心法
- Debug 方法論(四):實戰案例與 Debug 日誌模板
延伸閱讀:
「Debug 就像偵探辦案——你不會在犯罪現場隨機翻找,你會系統性地收集證據、建立假設、逐一排除。」