最有效率的 debug,流程上其實和科學實驗一模一樣:觀察現象 ➡️ 提出假設 ➡️ 設計實驗驗證 ➡️ 根據結果修正假設。把這套流程內化,就不會在「亂改一通、改了又退回」的迴圈裡打轉。
flowchart TD
A[觀察現象] --> B[提出一個明確假設]
B --> C[設計最小實驗驗證]
C --> D{結果符合假設?}
D -- 是 --> E[縮小到更精確的位置]
D -- 否 --> F[推翻假設, 排除一種可能]
E --> B
F --> B
E --> G[定位根因]先觀察,別急著下結論#
最常見的 debug 錯誤,是太早認定「我知道問題在哪」,然後一頭栽進去改,結果改錯地方。
落實到前端:
- 「我以為
user.id有值」➡️ 在 Console 或中斷點實際印出來看。 - 「我以為這個 API 有被呼叫」➡️ 開 Network panel 確認請求真的送出、參數正確。
- 「我以為這段 CSS 有套用」➡️ 在 Elements 看 Computed 樣式,確認沒被覆蓋。
很多 bug 在「實際看一眼」的瞬間就破案了,根本不需要動手改。
一次只改一個變因#
實驗的鐵則:一次只改一個變數。如果你同時改了三個地方然後 bug 消失了,你並不知道是哪一個修好的——下次它再出現你還是束手無策。
「我多加了
await、又改了狀態順序、順手升級了套件,然後就好了。」這種修法等於沒修。務必逐一還原,確認到底是哪個改動起作用。
二分法:每一步砍掉一半#
定位問題最有威力的策略是二分搜尋——每次操作都讓「可能出錯的範圍」減半。
在程式碼裡二分:
- 把可疑的那段邏輯,用 early return 或註解切成兩半,看問題出現在前半還後半。
- 大量元素渲染出錯時,先只渲染一半,逐步逼近出問題的那筆資料。
- 懷疑是某個第三方套件或 middleware,逐一停用排除。
用 git bisect 在版本歷史裡二分:
當「以前好好的、現在壞了」,但不知道是哪個 commit 引入的,git bisect 能用 O(log n) 次測試自動定位。
git bisect start
git bisect bad # 目前這版有問題
git bisect good v1.2.0 # 這個舊版是好的
# git 自動 checkout 中間的 commit,你測試後回報:
git bisect good # 或 git bisect bad
# 重複幾次,git 直接指出第一個壞掉的 commit
git bisect reset # 結束,回到原本分支若有自動化的重現腳本,可用
git bisect run <script>全自動跑完,script 以離開碼 0/非 0 表示 good/bad。
建立最小可重現案例#
把問題從龐大的應用裡「剝離」出來,做成一個最小、獨立、能穩定重現的案例。這個過程本身往往就會讓你發現根因。
剝離的步驟:
- 逐步移除不相關的元件、樣式、資料,直到再移除問題就消失。
- 拔掉外部依賴(API 改用寫死的假資料、關掉非必要的套件)。
- 縮減到能貼進一個 CodeSandbox / 單一 HTML 檔的程度。
最小重現案例有兩個額外好處:一是排除了「其實是別的地方干擾」的可能;二是要對外求助(同事、GitHub issue)時,它就是最好的溝通材料。
小結#
- Debug = 觀察 ➡️ 假設 ➡️ 驗證 ➡️ 修正,循環逼近根因。
- 驗證而非假設:先「看」實際值,別急著改。
- 一次只改一個變因,否則等於沒修。
- 二分法砍半:程式碼裡切半、
git bisect在歷史裡切半。 - 做最小可重現案例,過程本身常常就破案。