Item 43: Use Assertions#

雖然 unit tests 是定位有缺陷函式的重要工具,但它們無法涵蓋所有情況。Unit tests 能指出哪個函式失敗了,但無法幫你找到缺陷的確切位置;而且複雜演算法難以拆分為小的自包含函式來測試。Assertions 正是用來填補這個空缺的。

Assertion 的基本概念#

Assertion 是包含一個布林運算式的語句,保證在程式碼正確時為 true。如果運算式評估為 false,assertion 就會 fail,通常會終止程式並顯示錯誤資料。透過在關鍵位置放置 assertions,你可以從兩個方向縮小搜尋範圍:

  • 失敗的 assertion 告訴你問題發生的位置
  • 未失敗的 assertion 幫助你排除那段程式碼的嫌疑

效能考量#

大多數語言支援 assertions,可以在編譯期執行期控制是否啟用:

  • C/C++:定義 NDEBUG macro 來停用
  • Java:使用 -enableassertions / -disableassertions 選項

在開發期間啟用 assertion checking 是常見做法。是否在 production 環境啟用則需要權衡利弊。

用 Assertions 驗證演算法#

除錯演算法程式碼時,用 preconditionsinvariantspostconditions 的概念來思考特別有用:

  • Preconditions:演算法開始前必須成立的屬性
  • Invariants:演算法處理資料時始終維持的屬性
  • Postconditions:演算法完成後必須成立的屬性

適合放置 Assertions 的位置#

  • 程式開頭,驗證 CPU 的架構屬性(如整數型別大小)
  • 函式入口,驗證參數的型別和有效性(非 null、合理值)
  • 函式出口,驗證回傳結果
  • 常用或複雜 method 的前後,驗證類別狀態一致性
  • API 呼叫後,驗證呼叫是否如預期成功
  • 載入資源後,驗證部署正確性
  • 複雜運算式求值後,驗證結果合理性
  • switch 語句的 default case(以 false 作為 assertion 值捕捉未處理的 case)
  • 資料結構初始化後,驗證其持有預期值

如果 assertion 發現了可能在 production 環境出現的問題(如使用者輸入驗證、API 呼叫失敗),你必須將 assertion 替換為更健壯的錯誤處理邏輯。當一個函式既可以用 assertion 也可以用 unit test 來測試時,優先使用 unit test,因為它可以自動執行並貢獻測試覆蓋率。

重點回顧#

  • 用 assertions 補充 unit testing,更精確地定位缺陷位置
  • 用驗證 preconditions、invariants 和 postconditions 的 assertions 來除錯複雜演算法
  • 加入 assertions 來記錄你對程式碼的理解和測試你的猜測