軟體除錯技巧

我們在養蟲,蟲也在養我們

fcamel
fcamel的程式開發心得
4 min readJun 24, 2018

--

Tip 1: 預防勝於治療

我們多數時間在讀程式,寫反而占少部份。寫得時候就有考慮如何除錯的話,日後有問題比較好處理。那要怎麼寫出容易除錯的程式?

答案是《寫出容易測試的程式》。容易測試表示容易驗證程式邏輯,出錯時就容易釐清問題。不一定要寫測試碼,重要的是設計時有考慮可測性,自然會降低除錯難度。

另外開發時放入的除錯程式如 debug log,作完後不用全數移除。可重覆使用的除錯碼對自己以及其他人也很有幫助。用 C/C++ 可用 macro 在 release build 透過編譯器自動移除除錯碼,不影響效能,像是這樣:

Java 可用 ProGuard 移除自訂的 debug log class,這裡有 Android 使用 ProGuard 的說明

Tip 2: 錯誤的模型比沒有模型好

剛開始除錯時會有類似經驗,若不清楚程式怎麼運作的,亂試一通很沒效率。後來學會要先搞懂整體運作的流程,再開始除錯。但是專案規模變大之後,有太多模組要理解,沒辦法一個個搞清楚才開始除錯。該如何是好?

答案是: 先假設一個可能的運作流程,依自己的猜測開始除錯。明確的錯誤模型比沒有模型好,因為它會讓你有目的性地觀察,然後逐漸修正成更正確的模型。這個模型不需要100%正確,只要有足夠資訊除錯即可。心中先有猜測的流程再看程式,會比逐行細讀快上不少。因為流程看似和猜測相符時,不需深入細節。直到有部份看起來和猜測不符時,才需要深入調查相關的部份。

這和科學研究很像,心中都要有個模型,藉此預測事物是如何運作,然後實驗驗證那裡和預期不符,接著釐清是模型錯了或實作有誤。不斷地重覆:

  1. 修正模型
  2. 提出假設
  3. 驗證假設,排除無慮的地方。針對違反假設之處作更深入調查。

我會記錄已驗證的事實、由事實衍生的推測以及資訊不足產生的疑問。務必區分三者,三不五時重新整理全貌,然後動手實驗驗證推測或解開疑問。

舉例來說,若遠端桌面操作沒反應,可能的流程是:

  1. 想像遠端桌面「輸入」到「輸出」之間發生什麼事。假設輸入會經過那些層模組,輸出會經過那些層模組回來。
  2. 在懷疑的點放 log 看是在那一層漏了。
  3. 若是輸入就沒傳進去,回頭研究處理輸入部份。獲得更多資訊後再假設輸入可能處理資料的流程。
  4. 若輸入有進 server,但沒有反應,研究正常情況下會如何反應,然後依正常情況建立的運作模型,再來推測有問題狀況應該如何,並在關鍵地方加 log 觀察行為。
  5. 若發現某個模組陷入 deadlock,用 debugger 查看卡在何處,從附近了解它在等什麼資源,了解資源取得和釋放的時機。推測是取得的時機有問題?還是因別的原因卡住釋放資源?然後驗證假設。
  6. 反覆針對必要部份建立更細節的模型,以此提出假設然後驗證。

Tip 3: 快速驗證

盡可能用最快的方式驗證推測,確認有效再用妥善的方法改寫。

比方說:

  • 懷疑某個參數改成 5566 會對,就在使用它的地方寫死,不要從源頭試著一次改對讓程式自己算出5566。若是沒效,就白費時間了。
  • 懷疑某段程式沒被執行,在裡面加入 abort(),有執行到馬上有 backtrace 可看。
  • 懷疑某段程式沒有被編進去,加入 #error,有編譯到會直接產生編譯錯誤。
  • 需要在 class 內加新屬性解決問題,但修改標頭檔會引發重編上千個檔案,改在 cpp 檔用全域變數的 map,用 address 當 key 模擬作出 membor field。

盡可能將時間用在 Tip 2 的流程,確認 root cause 後,看看有沒有最快的方法驗證解法有效。確認有效再用妥善的方法重寫。有時我偷懶想一步到位,最後發現沒解決問題,反而浪費更多時間在處理和問題無關的事上 (像是如何傳入我以為需要的參數)。

Tip 4: 善用工具

《在 Linux 理解大型 C/C++ 專案的輔助工具》有詳細的說明。這可大幅減少操作時間,將主要時間投入 Tip 2 的事。《找出函式呼叫處的方法》列了一些小技巧輔助了解程式流程。

--

--