There is a luxury in self-reproach. When we blame ourselves we feel no one else has a right to blame us.

— Oscar Wilde, The Picture of Dorian Gray

核心概念#

似乎每個程式設計師在職涯早期就必須背下一個信條。它是計算的基本信念,一個我們學會套用到需求、設計、程式碼、註解上的核心信念——幾乎套用到我們做的一切。它是這樣的:

「這不可能發生……」

「這個應用程式絕不會在國外使用,所以幹嘛做國際化?」「count 不可能是負數。」「日誌記錄不可能失敗。」

不要練習這種自我欺騙,尤其是在寫程式的時候。

Tip 39 - Use Assertions to Prevent the Impossible(用 Assertion 來防止不可能的事)

使用 Assertions#

每當你發現自己在想「但這當然不可能發生」,就加入程式碼來檢查它。最簡單的方式就是使用 assertions——大多數語言實作中都有某種形式的 assert,用來檢查布林條件。

基本用法#

如果一個參數或結果永遠不應該為 null,就明確地檢查:

assert (result != null);

在 Java 實作中,你可以(也應該)加上描述性字串:

assert result != null && result.size() > 0 : "Empty result from XYZ";

檢查演算法操作#

Assertions 也很適合用來檢查演算法的正確性。例如你寫了一個巧妙的排序演算法 my_sort,那就檢查它是否正確運作:

books = my_sort(find("scifi"))
assert(is_sorted?(books))

不要用於真正的錯誤處理#

不要用 assertions 來取代真正的錯誤處理。Assertions 檢查的是不應該發生的事情。你不會想寫出這樣的程式碼:

puts("Enter 'Y' or 'N': ")
ans = gets[0]
assert((ch == 'Y') || (ch == 'N'))    # 很糟糕的想法!

Assertions 與副作用#

當我們加入用來偵測錯誤的程式碼反而引入新的錯誤,這是很尷尬的。如果評估 assertion 的條件有副作用,就可能發生這種情況。例如:

// 糟糕的寫法——assertion 有副作用
while (iter.hasMoreElements()) {
  assert(iter.nextElement() != null);  // nextElement() 會移動迭代器!
  Object obj = iter.nextElement();
  // ....
}

nextElement() 呼叫有移動迭代器的副作用,所以迴圈只會處理集合中一半的元素。正確的寫法是:

while (iter.hasMoreElements()) {
  Object obj = iter.nextElement();
  assert(obj != null);
  // ....
}

這種問題屬於 Heisenbug——除錯行為改變了被除錯系統的行為。

不要關閉 Assertions#

有一個常見的誤解:assertions 增加了程式碼的開銷,因為它們檢查的是不應該發生的事。一旦程式碼經過測試並交付,就不再需要它們,應該關閉以讓程式碼跑得更快。

這裡有兩個明顯錯誤的假設:

  1. 假設測試能找到所有 bug:實際上,對於任何複雜的程式,你不太可能測試到程式碼會經歷的排列組合中的一小部分
  2. 忘記程式在危險的世界中運作:在測試期間,老鼠不太可能會咬斷通訊電纜、有人玩遊戲耗盡記憶體、日誌檔塞滿儲存空間。但這些事在生產環境中都可能發生

在你將程式交付到生產環境時關閉 assertions,就像是因為你曾經在練習中走過鋼索就不用安全網去走高空鋼索。這有戲劇性的價值,但很難買到人壽保險。

即使你確實有效能問題,也只關閉真正影響效能的那些 assertions。排序的範例可能是應用程式的關鍵部分,需要很快。額外的檢查意味著再次遍歷資料,這可能無法接受。把那個特定的檢查設為可選的,但其餘的都留著

真實案例: Andy 的一個前鄰居經營一家小型新創公司。他們成功的秘訣之一是決定在生產版本中保留 assertions。這些 assertions 經過精心設計,會報告所有相關資料並以美觀的 UI 呈現給使用者。這種來自真實使用者、真實情況的回饋,讓開發者得以修補那些模糊、難以重現的 bug,造就了穩定得驚人的軟體。這家小公司後來被以數億美元收購。

相關章節#

  • Topic 23,合約式設計
  • Topic 24,死程式不說謊
  • Topic 42,基於屬性的測試
  • Topic 43,保持安全