核心概念#

你有沒有注意到,有時候別人比你自己更早察覺你不對勁?別人的程式碼也是如此。當我們的程式開始出問題,有時是函式庫或框架的常式先捕捉到了。也許我們傳入了一個 nil 值或空列表,也許 hash 中缺少了一個 key,也許是網路錯誤或檔案系統錯誤導致了空資料或損壞的資料。

很容易掉入「這不可能發生」的心態。我們大多數人都寫過沒有檢查檔案是否關閉成功、或 trace 語句是否如預期寫入的程式碼。但我們正在做防禦性編碼:確認資料是我們認為的那樣、確認部署的程式碼是我們認為的那個版本、確認載入了正確版本的依賴。

所有錯誤都會給你資訊。 你可以說服自己錯誤不可能發生而選擇忽略。但務實的程式設計師告訴自己:如果有錯誤發生,那就是非常、非常糟糕的事情。別忘了閱讀該死的錯誤訊息。

Catch and Release is for Fish(捕獲再釋放是釣魚用的)#

有些開發者覺得捕捉所有例外、寫一些日誌訊息、然後重新拋出是好的風格。他們的程式碼充滿了這樣的東西:

try do
  add_score_to_board(score);
rescue InvalidScore
  Logger.error("Can't add invalid score. Exiting");
  raise
rescue BoardServerDown
  Logger.error("Can't add score: board is down. Exiting");
  raise
rescue StaleTransaction
  Logger.error("Can't add score: stale transaction. Exiting");
  raise
end

務實的程式設計師會這樣寫:

add_score_to_board(score);

這樣寫有兩個好處。首先,應用程式碼不會被錯誤處理淹沒。其次,也更重要的是,程式碼耦合度更低。在冗長版本中,我們必須列出 add_score_to_board 可能拋出的每一個例外。如果該方法的作者新增了另一個例外,我們的程式碼就悄悄過時了。在務實版本中,新的例外會自動傳播。

Tip 38 - Crash Early(盡早崩潰)

Crash, Don’t Trash(崩潰,不要垃圾化)#

盡早偵測問題的好處之一是,你可以更早崩潰,而崩潰往往是你能做的最好的事。替代方案可能是繼續執行——把損壞的資料寫入重要的資料庫,或命令洗衣機進入第二十個連續脫水循環。

Erlang 和 Elixir 語言擁抱這個哲學。Joe Armstrong(Erlang 的發明者)常被引用:「防禦性程式設計是浪費時間。讓它崩潰吧!」在這些環境中,程式被設計為允許失敗,但失敗由 supervisors 管理。supervisor 負責執行程式碼,知道程式碼失敗時該做什麼——包括清理、重新啟動等等。supervisor 本身失敗怎麼辦?它自己的 supervisor 會管理,形成 supervisor trees

在其他環境中,直接結束執行中的程式可能不太適當。你可能已經佔用了資源、需要寫日誌、關閉交易或與其他行程互動。但基本原則不變:當你的程式碼發現了某件應該不可能的事情剛剛發生了,你的程式就不再可靠。從此刻起,它做的任何事都是可疑的,所以盡快終止它。

一個死掉的程式通常比一個殘廢的程式造成的損害少得多。

相關章節#

  • Topic 20,除錯
  • Topic 23,合約式設計
  • Topic 25,Assertion 式程式設計
  • Topic 26,如何平衡資源
  • Topic 43,保持安全