錯誤的分類有很多種方式。......然而,當我們在應用程式裡定義例外類別時,我們最關心的應該是,他們是如何被捕捉的。
我們來看一個分類不佳的例子:
ACMEPort port = new ACMEPort(12);
try {
port.open();
} catch (DeviceResponseException e) {
reportPortError(e);
logger.log("Device response exception", e);
} catch (ATM1212UnclockedException e) {
reportPortError(e);
logger.log("Unclock exception", e);
} catch (GMXError e) {
reportPortError(e);
logger.log("Device response exception", e);
} finally {
...
}
這段敘述有大量重複的程式碼,並且我們知道這類的處理程序,不論是哪種例外事件,所做的事大都相同。所以我們只要包裹 (wrap) 呼叫的 API,並確保其回傳共用的例外型態,便能大幅簡化程式。
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailure e) {
reportError(e);
logger.log(e.getMessage, e);
} finally {
...
}
而 LocalPort 只是一個簡單的包裹類別:
public class LocalPort {
private ACMEPort innerPort;
public LocalPort(int portNumber) {
innerPort = new ACMEPort(portNumber);
}
public void open() {
try {
innerPort.open();
} catch (DeviceResponseException e) {
throw new PortDeviceFailure(e);
} catch (ATM1212UnclockedException e) {
throw new PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
}
...
}
包裹第三方 API 是非常好的技巧,能夠減少對其依賴。除了將來要更換不同函式庫時更加容易,在測試自己程式碼時也更方便模擬呼叫。
昨天撰寫工作上的專案時,程式在運行中報了一些錯誤,源自於一些來自後端 API 回傳的資料,在前端做使用時沒有檢查 null 的情形。於是我添加了些函式運行時的 null 檢查,並補上了資料中的 null 型別,順利解決了這個小問題。
今天在閱讀錯誤處理篇章時,作者說:「我們很容易發現程式碼的問題,在於沒有進行 null 的檢查,然而事實上,我們的問題可能出在做了太多 null 的檢查。」
我認為原因是當我們要進行 null 檢查時,很容易因為疏忽而漏檢查,並且檢查 null 會使的程式變的冗長,進而模糊程式的意圖。所以最好的方法是,不要回傳 null 值
。
如果想用函式回傳 null,或是 null 來自第三方 API 的回傳,那不如拋出一個例外事件,或使用特殊情況模式 (Special Case Pattern),回傳一個 SPECIAL CASE 物件。而以我個人而言,若下一次接收 API 資料時收到 null,我可能會其做資料轉換,例如改成空字串或空陣列,以避免 null 的出現及後續判斷。
同理,既然要避免函式回傳 null,勢必也要避免函式傳遞 null。
只是我個人認為,在參數中傳遞 null 有時並非刻意,而是未預期的錯誤,所以在傳遞 null 這件事上,僅能盡量避免以減少錯誤狀況的發生,卻是難以杜絕。
錯誤處理的議題在今天告一段落,明天將會開始聊聊關於程式與程式間的邊界話題。