(點選上方公眾號,可快速關註)
來源:ImportNew – 曹旭東
本文是回答StackOverflow上的問題,但因為寫太長了,所以就發到這裡了。
http://stackoverflow.com/q/299068/315943
實際上,真正要討論的問題並不是,“相對‘那些不會發生錯誤的程式碼’來說,‘那些以異常形式上報的錯誤’會有多慢?”,因為你可能也認同“已接受的回答”。相反,真正的問題是,“相對‘那些以其他形式上報的錯誤’來說,‘那些以異常形式上報的錯誤’會有多慢?”
通常認為,“不要丟擲你想要捕獲的異常”。所以,丟擲一個其他人——如平臺或框架API——要捕獲的異常是合適的。或者在編寫一些工具API時,丟擲異常也可以的,如日誌記錄或訊息傳送,這些操作需要處理外部虛擬機器的錯誤,例如檔案IO或網路IO錯誤。
這是適合丟擲異常的例子,應該沒有人會在這些例子上有爭議。現在,看一下簡單方法中出現錯誤時會發生什麼。假設方法簽名如下:
/**
* Transforms SomeClass into SomeOtherClass.
* @param input some class instance
* @return the transformed instance,
* or null if the transformation was unsuccessful
*/
public SomeOtherClass transform(SomeClass input)
呼叫該方法的程式碼如下所示:
SomeClass input = … // get the input from somewhere
SomeOtherClass result = transform(input);
if(result == null) {
// handle the failure
} else {
// use the result
}
但現在,當方法傳回null時,我們想知道哪裡出現錯誤了。簡單來說可以這樣:
/**
* Transforms SomeClass into SomeOtherClass.
* @param input some class instance
* @return the transformed instance, never null
* @throws TransformationException on failure
*/
public SomeOtherClass transform(SomeClass input) throws TransformationException
為了實現這個功能,你需要將“return null”改為“return new TransformationException(“reason”);”。呼叫方法需要做改動麼?
SomeClass input = … // get the input from somewhere
try {
SomeOtherClass result = transform(input);
// use the result
} catch(TransformationException e) {
// handle the failure
}
沒有人會去讀上面的程式碼塊,沒什麼意義。所以也沒什麼可驚訝的。你可能每天都在寫類似的程式碼,但也說不上是“程式碼異味”。可是,將來你會明白在“已預料到”的錯誤上使用異常是多麼大的錯誤假設有一天你開始讀到在“已預料到”的錯誤上使用異常是非常不好的。現在,捕獲“未預料到”異常是非常可笑的,因為編寫catch程式碼塊,並顯式的處理異常本身就是預料到會有異常。但沒關係,我們還可以修改程式碼改變這種窘境。於是,我們退回到了C語言時代,傳回一個異常值來表示錯誤。
/**
* Transforms SomeClass into SomeOtherClass.
* @param input some class instance
* @return the SomeOtherClass instance or,
* if the transform fails, a TransformFailure instance
*/
public Object transform(SomeClass input)
於是,呼叫部分的程式碼也不得不奇葩一些:
SomeClass input = … // get the input from somewhere
Object result = transform(input);
if(result instanceof SomeOtherClass) {
SomeOtherClass success = (SomeOtherClass)result;
// use the result
} else {
TransformFailure failure = (TransformFailure)result;
// handle the failure
}
所以,如果你覺著使用異常有程式碼異味,但可以接受打破型別安全,那麼你最終要面對的是難以維護,沒法使用,僅僅比基於異常的解決方案快兩倍的程式碼。但是你又不能接受型別安全被破壞,因為這2倍的效能提升還未被證明,現在就用實在太魯莽。所以,你決定使用結果物件,而不是傳回異常值。
/**
* Transforms SomeClass into SomeOtherClass.
* @param input some class instance
* @return the TransformResult instance
*/
public TransformResult transform(SomeClass input)
現在,相應地,呼叫部分的程式碼變成了這樣:
SomeClass input = … // get the input from somewhere
TransformResult result = transform(input);
if(result.isSuccess()) {
SomeOtherClass success = result.getSuccess();
// use the result
} else {
TransformFailure failure = result.getFailure();
// handle the failure
}
現在,我們有了一個隱晦,但可管理的解決方案。可是,它比使用異常的第一個方案慢2倍,即使你將TransformResult和TransformFailure合併也是一樣的。再說明一遍,使用結果物件比使用異常慢,即使在呼叫過程中發生了錯誤。每次你都需要建立一個新的結果物件,這沒什麼實際意義,而異常物件只在發生錯誤的時候才會建立。
對於異常,還有一個要討論的地方。假設有人在使用方法transform時,沒有認真看javadoc。在使用異常的例子中,會有下麵的程式碼:
SomeClass input = … // get the input from somewhere
SomeOtherClass result = transform(input);
// use the result
在使用異常的例子中,他們知道傳回值的型別,以及是否一個“已檢查異常”,他們可能會得到一個編譯時錯誤,或者他們會在throws陳述句中宣告相應的異常。即使是“未檢查異常”,錯誤會傳遞到上層呼叫。現在,考慮使用異常傳回值的例子:
SomeClass input = … // get the input from somewhere
SomeOtherClass result = (SomeOtherClass)transform(input);
// use the result
這個粗心的使用者寫的程式碼看起來挺漂亮,但當執行過程中發生錯誤時,就滿不是那麼回事了。那時,你費儘力氣提供的錯誤資訊會因為發生了ClassCastException異常為全部丟失。使用結果物件也不會好到哪去。
SomeClass input = … // get the input from somewhere
SomeOtherClass result = transform(input).getSuccess();
// use the result
再說一遍,上面的程式碼看來相當正常。如果他們盲目使用本文中給出的第一個方法,那麼在程式執行過程中,肯定會出現NullPointerException異常。
這裡主要想說的是,處理邏輯錯誤時,使用異常的例子可以按預想的方式正常工作,報告錯誤資訊。但是其他的解決方案卻會產生一些沒用的異常,即使你已經正確將軟體重新部署了一遍,它仍然會出錯,只有這時,你才能得到錯誤資訊。
所以,唯一符合邏輯性的結論是,如果你想上報錯誤資訊,那麼就應該使用異常。它比結果物件效能高,比異常傳回值安全,而且執行穩定。
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能