最有效率的 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 在歷史裡切半。
  • 做最小可重現案例,過程本身常常就破案。