57.只針對異常的情況才使用異常
簡介
錯誤示例:
try{
int i = 0;
while(true){
rang[i++].climb();
}
}catch(ArrayIndexOutOfBoundsException e){}
- 異常機制的設計初衷是用於不正常的情形,所以很少會有
JVM
實現視圖對他們進行優化 - 把代碼放在
try-catch
塊中反而阻止了現代 JVM本來可能要執行的某些優化. - 對數組進行遍歷 的標準模式 並不會導致 冗餘的檢查.
而正確的寫法,應該是:
for(Mountain m: range){
m.climb();
}
錯誤的觀點:
認爲
VM
對每次數組的訪問都要檢查越界情況,導致性能差.
使用異常模式
可以提高性能
事實
在現代 JVM
的實現中,基於異常的模式比標準模式 要慢得多,降低了性能.
基於異常模式會模糊代碼意圖,如果出現了bug
,它還會掩蓋了 bug
.
小結
- 異常應該只用於異常情況下,不應該用於正常的控制流.
- 設計良好的API不應該強迫它的客戶端爲了正常的控制流而是用異常.
58.對可恢復的情況使用受檢異常,對編程錯誤使用運行時異常
簡介
Java 提供了三種可拋出結構: 受檢異常,運行時異常和錯誤.
使用規範
- 如果期望調用者能夠適當的恢復,就應該使用受檢異常.
通過拋出受檢異常,強迫調用者處理異常
- 運行時和錯誤都是不應該被捕獲的可拋出結構
如果程序拋出這個,往往都是不可恢復的情形
- 用
運行時
異常表明編程錯誤
錯誤
往往是被JVM
保留用於標示資源不足
,約定失敗
或者其他使程序無法繼續執行的條件
小結
對於 可恢復
的情況,使用受檢異常
對於程序錯誤
,使用運行時異常.
受檢異常,往往需要指明一個可恢復的條件.
59.避免不必要的使用受檢的異常
簡介
上一個條目說 使用受檢的異常,調用者就需要在
catch
中處理這個異常.
也就是說過分的使用受檢異常
會使API 使用起來非常不方便,因爲需要在一個或多個catch塊
中處理這個異常. 或者thows
這些異常
小結
- 如果即便合理的調用了API也會遇到異常情形,並且捕獲異常之後能夠進行一些有意義的操作,才應該使用
checked exception
,其他情況下都應該使用RuntimeException
- 如果一個方法會拋出
checked exception
,都可以將其拆分爲兩個方法,一個用於判斷是否會拋出異常,另一部分用於處理正常情況,如果不符合約定,就拋出RuntimeException
60.優先使用標準的異常
Java 平臺類庫未受檢異常
代碼重用是值得提倡的,異常也不例外
重用現有的異常有多方面的好處:
- 使你的
API
更加易於學習和使用 - 對於用到這些
API
的程序而言,可讀性更好 - 異常類越少,意味着內存 印記 就越小,
裝載這些異常
的時間開銷
也越少
常用的有IllegalArgumentException
,IllegalStateException
…
小結
選擇重用哪個異常,並不總是那麼精確.沒有嚴格的規則/.
61.拋出與抽象相對應的異常
簡介
- 異常轉譯
更高層
的實現應該捕獲低層
的異常,同時拋出
可以按照高層抽象進行解釋
的異常. 就是異常轉譯
// Exception Translation
try {
// Use lower-level abstraction to do our bidding
...
} catch(LowerLevelException e) {
throw new HigherLevelException(...);
}
- 異常鏈
如果低層的異常對於調試導致高層異常的問題非常有幫助,就可以使用異常鏈
// Exception Chaining
try {
// Use lower-level abstraction to do our bidding
...
} catch(LowerLevelException e) {
throw new HigherLevelException(e);
}
大多數標準的異常 都支持
異常連的構造器
如果不支持,可以使用Throwable#initCause
來設置
異常連也不能亂用,最好的做法是, 在調用低層方法之前確保他們會成功執行,從而避免他們拋出異常
小結
- 如果
不能阻止
或者處理
來自更低層的異常
,一般的做法是使用異常轉譯
. - 如果碰巧
低層方法
可以保證它拋出的所有異常
對高層也合適
,就可以將異常
從低層傳播到高層.
62.每個方法拋出的異常都要有文檔
簡介
- 不要通過聲明拋出多個異常的父類來實現拋出多種異常的效果。
- 要利用
javadoc
的@throws
標記準確的記錄下拋出受檢異常的條件 - 不要使用
@throws
將未受檢的異常也包含在方法的聲明中 - 如果一個類的很多方法都拋出同一個異常,那麼可以將文檔放到
class doc
中,而不是method doc
中
63.在細節消息中包含能捕獲失敗的信息
簡介
- 爲了捕獲失敗,異常的細節信息,應該包含所有”對該異常有貢獻”的參數和域的值.
- 異常類型的
toString
方法應該儘可能多地返回有關失敗原因的信息. - 爲異常的”失敗捕獲”信息提供一些訪問方法是合適的.提供這樣的方法對於受檢異常,比未受檢異常更爲重要.
64.努力使失敗保持原子性
簡介
當對象拋出異常後,我們期望這個對象仍然保持在一種定義良好的可用狀態.
- 一般而言,失敗的方法調用,應該使對象保持在被調用之前的狀態
具有這種屬性的方法被稱爲具有失敗原子性
創建具有失敗原子性的方法
- 最 簡單的方法 莫過於
設計一個不可變得對象
,如果對象是不可變的,那顯然是具有原子性的 - 在可變對象上,最常見的方法是: 在
執行操作之前
,檢查參數的有效性,
在對象的狀態被改變之前拋出適當的異常,如:
public Object pop(){
if(size ==0){
throw new EmptyStackException();
//...
}
}
如果不在 開始進行檢查,在執行過程中也會 拋出異常,然而會導致
size
域 保持在不一致的狀態,導致後續調用都會失敗
- 編寫一段
恢復代碼
,由它來攔截操作過程中發生的失敗,以及使對象回滾到操作之前的狀態. - 在對象的一份
臨時拷貝上執行操作
,當操作完成之後再用臨時拷貝中的內容替換對象中的內容
65.不要忽略異常
簡介
這條看似是顯而易見的.但是卻常常被違反,因此不可以忽視它
設計者聲明一個方法拋出異常,等於正在試圖說明什麼.
// Empty catch block ignores exception
try{}catch(Exception e){}
- 空的
catch
塊,會使異常達不到應有的目的. - 至少,
catch
塊也應該包含一條說明,說明爲什麼可以忽略這個異常.