如何更好的使用異常

Java的基本理念是,「結構不佳的代碼不能運行」java

        —— Java編程思想程序員

何時使用異常

發現錯誤的理想時機是在編譯階段,也就是試圖運行程序以前。然而,編譯階段並不能找出全部的錯誤,餘下的問題必須在運行期間解決。這就須要錯誤源可以經過某種方式,把適當的信息傳遞給某個接收者——該接收者將知道如何正確處理這個問題。編程

舉個例子,當一個方法遇到一種狀況,這種狀況下,它不能知足約定,這時應該如何處理?數組

傳統的作法是方法應該返回某種錯誤碼。調用者被迫區檢查錯誤,若是調用者也不能處理錯誤,那就給調用者的調用者返回一個錯誤碼……架構

程序員並不老是檢查和傳遞返回的錯誤碼,結果錯誤沒有被檢測到,致使後面的嚴重破壞!併發

C以及其餘早期語言經常具備多種錯誤處理模式,這些模式每每創建在約定俗成的基礎之上,而並不屬於語言的一部分。ide

只針對異常的狀況才使用異常

從一個反面教材開始:測試

try {
	int i = 0;
	while (true)
		range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

上面這種狀況就是亂用異常的典型,它的構想是很是拙劣的。當這個循環企圖訪問數組邊界以外的第一個數組元素時,用throw、catch和忽略ArrayIndexOutOfBoundsException的手段來達到終止無限循環的目的,對於任何一個合格的程序員來講,它的實現用下面的標準模式一看便知:優化

for (Mountain m : range)
	m.climb();

異常機制的設計初衷是用於不正常的情形。因此不多會有JVM實現試圖對它們進行優化,使得異常與顯示的測試同樣快速,例如for等。把代碼放到try-catch塊中,反而阻止了JVM實現原本可能要執行的某些特定優化。這個例子說明:atom

異常應該只用於異常的狀況下,他們永遠不該該用於正常的控制流;

設計良好的API不該該強迫它爲了正常的控制流而使用異常;

Java的異常架構

全部的異常類都派生自Throwable。當發生來某種異常,而這種異常不是指望應用程序處理的,好比內存耗盡等,則JVM會拋出Error。這種異常不推薦程序員使用。

通常程序員使用的異常屬於Exception類的異常,他們分爲兩種:

未檢查異常(unchecked exception),屬於RuntimeException的子類,它屬於不須要也不該該被捕獲的可拋出結構。用運行時異常來代表編程錯誤。大多數的運行時異常都表示前提違例(precondition violation),意思是API的客戶沒有遵照API的規範創建的約定,好比要求參數不能爲Null,但仍是傳遞Null參數,實現的全部未檢查異常(未受檢異常)都應該是RuntimeException的子類

已檢查異常(checked exception),屬於Exception的子類,也稱爲受檢異常,若是指望調用者可以適當地恢復,對於這種狀況就應該使用它。經過拋出異常,強迫調用者在一個catch裏處理該異常,或將它傳播出去,尤爲在設計API時,設計者讓API用戶面對受檢的異常

異常也是個對象,能夠在它上面定義任意的方法。這些方法的主要用途是爲捕獲異常的代碼提供額外的信息,特別是關於引起這個異常條件的信息。

避免沒必要要的使用受檢的異常

受檢異常強迫使用該API的用戶必須catch異常,大大增長來程序的可靠性。可是多度使用會使API使用起來很是不方便。用戶必須聲明它拋出的這些異常,或者讓它們傳播出去,不管哪一種方法,都給API用戶增添來負擔。

那何時使用受檢異常比較合適呢?

若是,正確的使用了API,但並不能組織這種異常條件的發生,而且一旦產生異常,使用API的用戶會當即採起有用的動做,這種狀況就是使用受檢異常的典型場景。

例如,API的設計人員能夠嘗試着問本身:API用戶將如何處理該異常,會有比下面代碼更好的方式嗎?

catch (CheckedException e) {
	throw new AssertionError();
}

下面這種作法如何?

catch (CheckedException e) {
	e.printStackTrace();
	System.exit(1);
}

若是使用API的用戶沒法作的比這更好,那仍是使用未受檢異常更爲合適。

這段代碼的意思爲,若是這個異常發生了,程序註定會中止,或者要完成的事情註定不能夠完成,那直接拋出未受檢異常讓程序中斷,暴露出問題更爲合適。

例如,在繳費電話費時,因爲餘額不足致使繳費失敗,能夠拋出受檢異常,並提供一個方法,查詢所需的金額等。

優先使用標準的異常

代碼重用是值得提倡的,這是一條通用的規則,異常也不例外。

Java提供來一組基本的未受檢異常,它們知足了絕大多數API的異常需求。

異常 場合
IllegalArgumentException 非null的參數值不正確
IllegalStateException 對於方法調用而言,對象狀態不合適
NullPointerException 在禁止使用null的狀況下參數值爲null
IndexOutOfBoundsException 下標參數值越界
ConcurrentModificationException 進行併發修改的狀況下,檢測到併發修改對象
UnsupportedOperationException 對象不支持用戶請求的方法

拋出與抽象相對應的異常

若是方法拋出的異常與它所執行的任務沒有明顯聯繫,這種情形將會令人不知所措。

更高層的實現應該捕獲底層的異常,同時拋出能夠按照高層抽象進行解釋的異常。

以下所示:

try {
} catch (LowerLevelException e) {
	throw new HigherLevelException(...);
}

或者:

Iterator i = ...
	try {
	return i.next()
} catch (NoSuchElementException e) {
	throw new IndexOutOfBoundsException("Index: " + index);
}

還有一種特殊的異常轉義形式,稱爲異常鏈(exception chaining),若是低層的異常對於調試致使高層異常的問題很是有幫助,使用異常鏈就很合適。底層異常的緣由能夠傳給高層異常,高層異常提供訪問方法,來得到底層異常:

try {
	
} catch (LowerLevelException cause) {
	throw new HigherLevelException(cause);
}

儘管異常轉移與不佳選擇地從底層傳遞異常的作法相比有所改進,可是它也不能被濫用。

若是可能,處理來自跌穿那個異常的最好作法是,在調用底層方法以前,前確保他們會執行成功,從而避免它們拋出異常。

努力保持失敗的原子性

當對象拋出異常後,一般指望這個對象仍然保持在一種定義良好的可用狀態之中,即便失敗是發生在執行某個操做過程當中間,對於受檢異常而言,這點尤其重要,由於API用戶指望能從這種異常中恢復。

通常而言,失敗的方法調用應該使對象保持在被調用以前的狀態。具備這種屬性的方法被稱爲具備失敗原子性(failure atomic)。

有幾種方法能夠實現這種效果:

最簡單的辦法是設計一個不可變對象;

設計處理過程的順序,使得任何可能會失敗的部分都在對象狀態改變以前發生;

編寫一段恢復代碼(recovery code);

在對象的一份臨時拷貝上執行操做;

異常文檔的重要性

始終要單獨聲明受檢的異常,而且利用JavaDoc的@throws標記,準確地記錄下拋出每一個異常的條件。

永遠不要throws Exception,或者throws Throwable,它們會掩蓋該方法拋出的任何其餘異常。

未受檢的異常一般表明編程上的錯誤,讓API用戶瞭解全部這些錯誤都有助於幫助他們避免調用錯誤。對於方法可能拋出的未受檢異常,若是將這些異常信息很好的組織成列表文檔,就能夠有效地描述出這個方法被成功執行的前提條件(precondition)。

對於接口中的方法,在文檔中記錄下它可能拋出的未受檢異常顯得尤其重要。這份文檔構成了該接口的通用約定(general contract)的一部分,它指定來該接口的多個實現必須遵照的公共行爲。

使用JavaDoc的@throws標籤記錄下一個方法可能拋出的每一個未受檢異常,可是不要使用throws關鍵字將未受檢的異常包含在方法的聲明中。

若是一個類中的許多方法處於一樣的緣由拋出同一個異常,在類的文檔注視中,堆這個異常創建文檔。

不要忽略異常

空的catch達不到應有的目的,至少,catch塊也應該包含一條說明,解釋爲何忽略這個異常。

但願不要忽略異常。

附錄A

java.lang.RuntimeException的直接子類有這些:

  1. AnnotationTypeMismatchException
  2. ArithmeticException
  3. ArrayStoreException
  4. BufferOverflowException
  5. BufferUnderflowException
  6. CannotRedoException
  7. CannotUndoException
  8. ClassCastException
  9. CMMException
  10. ConcurrentModificationException
  11. DataBindingException
  12. DOMException
  13. EmptyStackException
  14. EnumConstantNotPresentException
  15. EventException
  16. IllegalArgumentException
  17. IllegalMonitorStateException
  18. IllegalPathStateException
  19. IllegalStateException
  20. ImagingOpException
  21. IncompleteAnnotationException
  22. IndexOutOfBoundsException
  23. JMRuntimeException
  24. LSException
  25. MalformedParameterizedTypeException
  26. MirroredTypeException
  27. MirroredTypesException
  28. MissingResourceException
  29. NegativeArraySizeException
  30. NoSuchElementException
  31. NoSuchMechanismException
  32. NullPointerException
  33. ProfileDataException
  34. ProviderException
  35. RasterFormatException
  36. RejectedExecutionException
  37. SecurityException
  38. SystemException
  39. TypeConstraintException
  40. TypeNotPresentException
  41. UndeclaredThrowableException
  42. UnknownAnnotationValueException
  43. UnknownElementException
  44. UnknownTypeException
  45. UnmodifiableSetException
  46. UnsupportedOperationException
  47. WebServiceException
相關文章
相關標籤/搜索