貼心還是造成麻煩?

Java 將例外區分為受檢例外與非受檢例外,在例外繼承架構中,Exception子類但非RuntimeException子類,就是受檢例外,這類例外必須明 確在程式碼中聲明使用try..catch處理,或者在方法上使用throws宣告此方法會丟出的受檢例外。

RuntimeException子類的例 外,則通常是JVM會自行丟出的例外,不用特別使用程式碼處理,編譯器也會讓你通過編譯。

例外處理的本意是,在程式錯誤發生時,能夠有明確的方式通知API客戶端,讓客戶端採取進一步的動作修正錯誤,而就撰寫本文的時間點來說,Java是唯一採用受檢例外(Checked exception)的語言,這有兩個目的:一是文件化,受檢例外宣告會是API操作介面的一部份,客戶端只要查閱文件,就可以知道方法可能會引發哪些例外,並事先加以處理,而這是API設計者決定是否拋出受檢例外時的考量之一,另一個目的是提供編譯器資訊,讓編譯器能夠在編譯時期就檢查出API客戶端沒有處理例外

也可以考慮為應用程式自訂專屬例外類別,讓例外更能表現應用程式特有的錯誤資訊。自訂例外類別時,可以繼承Throwable、Error或Exception的相關子類別,通常建議繼承自Exception,如果不是繼承自Error或RuntimeException,那麼就會是受檢例外。

public class CustomizedException extends Exception { // 自訂受檢例外的一個例子
    ...
}

錯誤發生時,如果上下文環境並沒有足夠的資訊讓你處理例外,你可以就現有資訊處理完例外後,重新拋出例外,既然你已經針對錯誤做了某些處理,那麼也就可以考慮自訂例外,用以更精確地表示出未處理的錯誤,如果認為呼叫API的客戶端應當有能力處理未處理的錯誤,那就自訂受檢例外、填入適當錯誤訊息並重新拋出,並在方法上使用throws加以宣告,如果認為呼叫API的客戶端沒有準備好就呼叫了方法,才會造成還有未處理的錯誤,那就自訂非受檢例外、填入適當錯誤訊息並重新拋出。

public class CustomizedException extends RuntimeException { // 自訂非受檢例外的一個例子
    ...
}

一個基本的例子是這樣的:

try {
    ....
} catch(SomeException ex) {
    // 作些可行的處理
    // 也許是 Logging 之類的
    throw new CustomizedException("error message..."); // Checked 或 Unchecked?
}

類似地,如果流程中要拋出例外,也要思考一下,這是客戶端可以處理的例外嗎?還是客戶端沒有準備好前置條件就呼叫方法,才引發的例外?

if(someCondition) {
    throw new CustomizedException("error message"); // Checked 或 Unchecked?
}

無論如何,Java採用了受檢例外的作法,Java的標準API似乎也打算一直這麼區分下去,只是受檢例外讓開發人員無從選擇,會由編譯器強制性要求處理,確實會在設計上造成麻煩,因而有些開發者在設計程式庫時,乾脆就選擇完全使用非受檢例外,一 些會封裝應用程式底層行為的框架,如Spring或Hibernate,就選擇了讓例外體系是非受檢例外,例如Spring中的DataAccessException,或者是Hibernate 3中的HibernateException,它們選擇給予開發人員較大的彈性來面對例外(也許也需要開發人員更多的經驗)。

隨著應用程式的演化,例外也可以考慮演化,也許一開始是設計為受檢例外,然而隨著應用程式堆疊的加深,受檢例外老是一層一層往外宣告拋出造成麻煩時,這也許代表了,原先認為客戶端可處理的例外,每一層客戶端實際上都無力處理了,每層客戶端都無力處理的例外,也許該視為一種臭蟲,也許客戶端在呼叫時都該準備好前置條件再行呼叫,以避免引發錯誤,這時將受檢例外演化為非受檢例外,也許就有其必要。