「例外」的廣義定義#

本書定義:「例外」泛指任何不常見、會改變程式正常控制流的條件——不限於語言內建的 throw/catch 機制。

即使只是用回傳值表達「沒能完成正常行為」,也屬於例外。

例外可能來自哪裡#

  • 呼叫端傳入錯誤的參數或設定
  • 被呼叫的方法無法完成請求(I/O 失敗、資源不可得)
  • 分散式系統中的封包遺失、伺服器逾時、對端行為超出預期
  • 程式偵測到內部 bug 或不一致狀態

大型系統,特別是分散式或要求容錯(fault-tolerant)的系統,例外處理可能佔一大半程式碼

例外處理為何特別難寫#

例外打斷正常流程,意味著「事情不如預期」。處理時可選兩條路,每條都很難:

路徑一:往前進、繞過例外#

  • 例如封包遺失就重送、資料壞了就用備份回復
  • 但繞過本身可能引發新問題

路徑二:終止當前操作並向上回報#

  • 困難在於:例外發生時,系統狀態可能不一致(資料結構部分初始化)
  • 處理程式碼必須先把狀態還原(unwinding)

二次例外(Cascading Exceptions)#

例外處理本身也會引發新例外:

  • 重送封包:原本沒丟、只是延遲 → 對端收到重複封包 → 新例外
  • 從備份回復:備份也壞了 → 新例外
  • 二次例外通常比一次例外更微妙、更複雜
  • 若處理是「終止並回報上層」,又變成上層的另一個例外

必須在某處停止這條鏈式反應——否則會無限蔓延。

語法笨重#

例如 Java 讀取序列化推文:

try (
    FileInputStream fileStream = new FileInputStream(fileName);
    BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
    ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);
) {
    for (int i = 0; i < tweetsPerFile; i++) {
        tweets.add((Tweet) objectStream.readObject());
    }
}
catch (FileNotFoundException e) { ... }
catch (ClassNotFoundException e) { ... }
catch (EOFException e) {
    // Not a problem: not all tweet files have full set of tweets.
}
catch (IOException e) { ... }
catch (ClassCastException e) { ... }
  • 光是 try-catch 樣板就比正常情境的程式碼還多
  • 哪個例外從哪裡丟出?不容易看出
  • 替代方案是把每行可能丟出例外的程式碼包進獨立 try → 但會打散程式流,且常重複處理碼

例外處理難以驗證#

沒被執行過的程式碼,就是不會動的程式碼」(作者最愛的格言之一)。

  • 某些例外(I/O 錯誤)很難在測試環境重現
  • 例外在運行系統中也不常發生 → 處理碼極少被執行
  • 一旦某天真的需要它,很可能根本就壞了

一份近期研究發現:分散式資料密集系統中的災難性失敗,逾 90% 由錯誤的錯誤處理引起