除錯(Debugging)是找出錯誤根源並加以修正的過程,與偵測錯誤的「測試」互為互補。在某些專案中,除錯佔據了總開發時間的 50%。然而,研究顯示最優秀與最差的程式設計師在除錯效率上有高達 20:1 的差距——好消息是,透過系統化的方法,除錯不必是最困難的環節。
23.1 除錯概述#
除錯和測試一樣,本身並不是提升品質的手段,而是診斷缺陷的方式。真正的品質必須從需求、設計與高品質的編碼實踐中建立,除錯只是最後一道防線。
經典研究(Gould 1975)發現,最快與最慢的程式設計師在除錯效能上有巨大差異:最快的三人平均 5 分鐘找到缺陷、引入 3 個新缺陷;最慢的三人平均 14.1 分鐘、引入 7.7 個。若遞迴計算修正引入的新缺陷,最慢群組需要約 13 倍時間才能完全除錯——印證了提升品質同時降低開發成本的原則。
缺陷即學習機會#
每個缺陷都映照出你對程式的理解不足。善用機會去:理解程式本身、認識自己的錯誤模式、從讀者角度審視程式碼品質、改進除錯方法。
無效的除錯方式#
| 方式 | 說明 |
|---|---|
| 靠猜測找缺陷 | 隨機散佈 print 語句,東改西改直到「好像」能動 |
| 不花時間理解問題 | 認為問題很小,找到就好 |
| 修表面不修根源 | 用特殊判斷硬改結果(如 if (client == 45) sum[45] += 3.45) |
| 迷信式除錯 | 怪機器、怪編譯器——程式有問題,就是你的錯 |
即使錯誤看似不是你的問題,先假設它是你的責任。這個心態能幫助你更快找到缺陷,也能維護你的專業信譽。
23.2 尋找缺陷#
找到並理解缺陷通常佔除錯工作的 90%。有效的程式設計師不靠隨機猜測,而是運用科學方法(Scientific Method)系統性地除錯。
科學方法的除錯步驟#
- 穩定錯誤(Stabilize the error)——使錯誤可重現
- 定位來源(Locate the source)
- 蒐集產生缺陷的資料
- 分析資料,形成假設
- 設計方式來驗證或推翻假設
- 執行驗證
- 修正缺陷(Fix the defect)
- 測試修正(Test the fix)
- 尋找類似錯誤(Look for similar errors)
flowchart TD
A["穩定錯誤"] --> B["蒐集資料"]
B --> C["分析"]
C --> D["形成假設"]
D --> E["設計驗證"]
E --> F["執行驗證"]
F --> G{"假設成立?"}
G -->|"是"| H["修正缺陷"]
H --> I["測試修正"]
I --> J["尋找類似錯誤"]
G -->|"否"| B穩定錯誤#
無法穩定重現的錯誤幾乎不可能診斷。間歇性錯誤通常源自:
- 初始化問題:變數未正確初始化,偶爾剛好為零
- 時序問題(Timing issue)
- 懸空指標(Dangling pointer)
穩定化的目標不只是找到一個重現的測試案例,而是將它簡化到最小——任何面向的改變都會影響錯誤的行為。
尋找缺陷的實用技巧#
點擊展開:尋找缺陷的技巧清單
- 使用所有可用資料形成假設:假設必須能解釋所有已觀察到的現象,否則就繼續精煉
- 精煉測試案例:逐步調整條件,觀察哪些變化會影響錯誤
- 在單元測試中執行相關程式碼:將可疑程式碼抽出來獨立測試
- 善用工具:互動式除錯器、記憶體檢查器、語法導向編輯器等
- 用多種方式重現錯誤:從不同角度三角定位缺陷的真正位置
- 產生更多資料以產生更多假設:選擇與已知案例不同的測試輸入
- 善用否定測試的結果:被推翻的假設也能縮小搜索範圍
- 腦力激盪:不要只鑽研第一個假設,多想幾個可能性再逐一排除
- 寫下待嘗試的事項:避免在同一條死路上卡太久
- 縮小可疑範圍:用二分搜尋法,移除一半程式碼看錯誤是否仍在
- 注意曾出錯的類別與常式:缺陷傾向群聚
- 檢查最近變更的程式碼:新錯誤通常與近期修改有關
- 擴大可疑範圍:若在小區域找不到,考慮缺陷可能在別處
- 增量整合:一次加入一小塊程式碼,新錯誤必定來自新加入的部分
- 對照常見缺陷清單:利用檢查表刺激思考
- 跟別人說:解釋問題的過程中,常常自己就找到答案(「告解式除錯」)
- 休息一下:若焦慮感出現,就是該休息的信號——讓潛意識接手
暴力除錯法#
當精密分析行不通時,暴力法(Brute-force)雖然費力但保證有效:
- 對問題程式碼做完整的設計與程式碼審查
- 丟掉問題段落,從頭重寫
- 用最嚴格的編譯器警告等級編譯並修正所有警告
- 掛上單元測試框架,獨立測試問題程式碼
- 在除錯器中手動逐步執行大型迴圈
- 用不同的編譯器或環境編譯與執行
設定一個「快速除錯」的時間上限。若五分鐘的猜測法沒用,就切換到保證有效的暴力法,避免浪費數小時在碰運氣上。
語法錯誤#
語法錯誤隨著現代編譯器的進步已越來越少見,但仍需注意:
| 原則 | 說明 |
|---|---|
| 不要相信行號 | 編譯器可能把錯誤報在錯誤的位置 |
| 不要完全相信錯誤訊息 | 編譯器的描述可能有誤導性 |
| 不要信第二個錯誤訊息 | 修正第一個再重新編譯即可 |
| 分而治之 | 移除部分程式碼重新編譯,定位語法錯誤的位置 |
23.3 修正缺陷#
找到缺陷是困難的部分,但修正才是容易出錯的部分——研究發現缺陷修正首次正確的機率不到 50%(Yourdon 1986b)。
- 先理解問題再修正:用能重現和不能重現錯誤的案例來三角驗證你的理解
- 理解程式,不只是問題:對程式有全局理解的開發者修正成功率更高——至少理解缺陷周圍數百行程式碼
- 確認診斷:排除所有其他可能的原因後再動手
- 放輕鬆:匆忙修正會導致草率判斷與不完整的診斷
- 保存原始原始碼:修正前先備份,方便日後比對
- 修根源,不修表象:不要用特殊案例的 if 判斷來掩蓋問題
- 只在有充分理由時才改程式碼:修改後若結果出乎意料,代表你還不夠理解問題
- 一次只改一處:避免多個修改交互作用產生新的混淆
- 檢查你的修正:用自動化迴歸測試確認修正沒有副作用
- 新增暴露此缺陷的單元測試:避免缺陷將來被重新引入
- 尋找類似的缺陷:缺陷傾向群聚,若想不到如何搜尋類似缺陷,代表你還不夠理解問題
絕不要隨機修改程式碼直到「好像能動」。這是巫毒程式設計(Voodoo Programming)——你沒有學到任何東西,對正確性也毫無信心。
23.4 除錯中的心理因素#
除錯要求你在流暢的創造性思維與嚴格的批判性思維之間快速切換,同時還要對抗自己「我的程式碼沒問題」的自我保護心態。
心理定勢(Psychological Set)#
心理定勢是指人們傾向看到自己預期看到的東西:
- 學生期待
while迴圈像自然語言中的「當…就…」一樣持續評估,而非只在迴圈頂端或底端檢查 - 程式設計師把
SYSTSTS和SYSSTSTS當作同一個變數,直到程式跑了數百次才發現 - 看到沒有大括號的
if後面跟著多行縮排,自動腦補大括號的存在
良好的程式風格(格式化、命名、註解)能建立一致的視覺背景,讓缺陷作為「變異」更容易被察覺。
心理距離(Psychological Distance)#
心理距離指兩個項目被區分的容易程度。變數名稱如 stoppt vs. stcppt 幾乎不可見,而 product vs. sum 則一目了然。選擇命名時應確保足夠的心理距離,避免因視覺相似而引發除錯盲區。
23.5 除錯工具#
原始碼比對工具#
Diff 等比對工具(Source-code Comparator)可用來比較修改前後的版本,迅速定位差異。當新版本出現舊版沒有的缺陷時,比對檔案是最直接的方法。
編譯器警告訊息#
- 將警告等級設到最嚴格,並修正所有警告
- 將警告視為錯誤(Treat warnings as errors)
- 建立整個專案統一的編譯器設定標準
其他工具#
| 工具 | 說明 |
|---|---|
| 延伸語法與邏輯檢查器(如 lint) | 比編譯器更嚴格地檢查未初始化變數等問題 |
| 執行效能分析器(Execution Profiler) | 花幾分鐘研究程式效能分佈,可能揭露隱藏的缺陷 |
| 測試框架與鷹架程式碼(Test Framework / Scaffolding) | 將問題程式碼抽出獨立測試 |
| 互動式除錯器(Debugger) | 設定中斷點、逐步執行、檢查資料、倒退執行等 |
除錯器不能取代思考,但思考有時也不能取代好的除錯器。最有效的組合是好的思考加上好的除錯器。
更多資源#
- Agans, David J. Debugging: The Nine Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems. Amacom, 2003.
- Myers, Glenford J. The Art of Software Testing. John Wiley & Sons, 1979. 第七章專門討論除錯。
- Allen, Eric. Bug Patterns In Java. Apress, 2002. 提出與本章類似的科學方法論除錯框架。
要點#
- 除錯是軟體開發中影響甚鉅的環節。最佳策略是用本書其他技術從源頭避免缺陷,但提升除錯技能仍然值得,因為優劣之間的差距至少 10:1。
- 系統化方法是成功的關鍵。每次測試都應使你往前一步,善用科學方法來除錯。
- 在修正程式之前,先徹底理解根本問題。隨機猜測與隨機修改只會讓程式變得更糟。
- 將編譯器警告設到最嚴格的等級,並修正所有報告的問題——連明顯的錯誤都不修,遑論找到隱微的缺陷。
- 除錯工具是強大的輔助,找到它們、使用它們,同時記得運用你的大腦。