核心概念#
你有沒有注意到,有時候別人比你自己更早察覺你不對勁?別人的程式碼也是如此。當我們的程式開始出問題,有時是函式庫或框架的常式先捕捉到了。也許我們傳入了一個 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,保持安全