核心觀點#
錯誤處理是軟體開發中不可或缺的一部分,但如果處理不當,
錯誤處理的程式碼往往會支配並模糊了主要的業務邏輯。
Martin 強調:錯誤處理很重要,但如果它讓主要邏輯變得模糊不清,那就是錯誤的做法。
整潔的程式碼應該將「處理錯誤的邏輯」與「正常的業務邏輯」分離。
- 使用例外 (Exceptions) 取代錯誤碼 (Error Codes)
- 別回傳 Null,也別傳遞 Null
- 將錯誤處理視為一個獨立的關注點(Concern)
錯誤處理的結構策略#
1. 使用例外而非錯誤碼#
早期的程式語言常用回傳「錯誤碼(Error Flags/Codes)」的方式來處理異常,
這會導致呼叫者必須在每次呼叫後立即檢查錯誤,導致主流程充滿了 if (err != OK) 的巢狀結構。
使用例外(Exceptions)可以讓主邏輯保持乾淨,將錯誤處理交由專門的 catch 區塊負責。
2. 先寫 Try-Catch-Finally#
try 區塊某種程度上像是一種交易(Transaction)。
無論在 try 過程中發生什麼事,catch 區塊都應該讓程式維持在一致的狀態。
- 實踐建議: 當你撰寫可能拋出例外的程式碼時,先寫下
try-catch-finally結構。
這能幫你定義該區塊的使用者(Caller)發生問題時應預期的狀態
3. 使用不檢查例外 (Unchecked Exceptions)#
這是個針對 Java 等強型別語言的建議。
雖然「檢查型例外(Checked Exceptions)」強迫開發者處理錯誤看似安全,
但它違反了開放閉合原則 (Open/Closed Principle)。
- 代價: 如果底層函式修改了它拋出的 Checked Exception,
則所有上層呼叫它的函式都必須修改簽章(Signature),這破壞了封裝性 - 建議: 大多數情況下,使用 Unchecked Exceptions(Runtime Exceptions)
能讓程式碼結構更穩定
優化錯誤的使用體驗#
1. 提供情境資訊 (Context)#
當例外發生時,不要只拋出一個 Stack Trace。
你拋出的例外應該包含足夠的上下文,讓維護者能判斷錯誤發生的原因與位置。
- 建議包含:操作失敗的意圖、失敗時的相關數據
2. 依據「呼叫者的需求」定義例外類別#
在封裝第三方 API 或設計模組時,
不要直接將底層的所有例外(如 SocketTimeout, ConnectionRefused…)全部拋給上層。
- 包裝(Wrapping): 應該將這些底層錯誤包裝成對呼叫者有意義的通用例外(例如
PortDeviceFailure)。這樣不僅簡化了呼叫者的依賴,也讓替換底層 Library 變得更容易
3. 定義正常流程 (Define the Normal Flow)#
有時候,為了處理「特殊情況」而中斷業務邏輯去寫 try-catch 會讓程式碼變得很醜。
這時可以使用 特例模式 (Special Case Pattern)。
範例:特例模式 (Special Case Pattern)
重構前 (充滿防禦性檢查): 我們須不斷檢查是否有午餐紀錄,沒有則顯示預設值。
try:
meal = expense_report_dao.get_meals(employee_id)
total += meal.get_total()
except MealNotFoundError:
total += get_per_diem_default() # 處理例外邏輯重構後 (使用特例物件): 修改 get_meals,當找不到資料時,回傳一個「特例物件(PerDiemMeal)」,該物件本身就知道如何回傳預設值。
# 呼叫端不需要知道是否有例外,流程更順暢
meal = expense_report_dao.get_meals(employee_id)
total += meal.get_total()關於 NULL 的詛咒#
1. 別回傳 NULL (Don’t Return Null)#
回傳 null 基本上就是給呼叫者製造工作(強迫他們寫 if check)。
只要漏掉一個檢查,應用程式就會失控。
- 替代方案:
- 如果回傳型別是 List,請回傳 Empty List [] 而非 null。
- 使用 Optional 物件或上述的「特例模式」物件。
2. 別傳遞 NULL (Don’t Pass Null)#
在參數中傳遞 null 比回傳 null 更糟糕。
除非 API 明確設計為可接受 null,否則應盡量避免。這會導致函式內部充滿無意義的防禦性檢查。
如果發現程式碼充滿了 if (item != null) 的檢查,這通常代表您的 API 設計有問題,
或者是對這段程式碼的錯誤處理機制規劃不足。
結論#
整潔的錯誤處理邏輯能讓程式碼更健壯。
- 將錯誤處理隔離,不要讓它污染主要邏輯
- 利用例外而非錯誤碼
- 利用特例模式和空集合來減少 Null 檢查與例外捕捉的頻率