引言:
public
void
consumeAndForgetAllExceptions(){
try
{
...some code that throws exceptions
}
catch
(Exception ex){
ex.printStack
trace
();
}
}
采用上面這種方式處理異常時,catch代碼段接管控制權,然后catch段之后代碼繼續執行,好像什么都沒有發生。
public
void
someMethod() throws Exception{
}
該方法內可能並不會拋出異常
總結:忽略異常或隨意拋出異常是常見現象。
異常的本質
廣義的講,拋出異常分三種不同的情況:
- 編程錯誤導致的異常:譬如NullPointerException和IllegalArgumentException),代碼通常對編程錯誤沒有什么對策。
- 客戶端的錯誤導致的異常:試圖違背制定的規則,例如讀寫文件流的關閉操作。
- 資源錯誤導致的異常:當獲取資源錯誤時引發的異常。例如,系統內存不足,或者網絡連接失敗。
對於Java作為第一個使用checked exception的主流面向對象語言而言,高層必須catch或throw,若不能有效的處理異常的話,就會在API和代碼之間造成負擔。
public
List getAllAccounts() throws
FileNotFoundException, SQLException{
...
}
Checked exception被詬病破壞封裝性:getAllAccounts使得調用者必須處理這兩個異常,但調用者不知道什么文件找不到&什么數據庫語句失敗,也不知道該提供什么文件系統或者數據庫的事物層邏輯。這使得方法與調用者之間形成不恰當的緊耦合關系。
設計API的最佳實踐
1. 根據客戶端如何應對Exception決定使用checked exception還是unchecked exception
分析:若客戶端可以采取行動恢復的建議采用checked exception
unchecked exception建議多用,因為它不強迫客戶端必須處理,會擴散直到客戶端想處理。建議采用Java定義好的異常類,更有利於代碼的易讀性。
2. 保持封裝性
不要將針對某特定實現的checked exception用到更高的層次中去。
例如不要讓SQLException擴散到邏輯層去,因為邏輯層是不需要知道SQLException。解決辦法:a.客戶端有相應應對措施時,轉換SQLException為另一個checked exception
b.若客戶端無法處理,轉換SQLException為unchecked exception
4. 將異常文檔化
你可以采用Javadoc’s @throws標簽將你的API拋出的checked和unchecked exception都文檔化。
3. 不要忽略異常當一個API方法拋出checked exception時,如果對你沒什么意義,直接轉換成unchecked exception拋出,而不要僅僅用空的catch來忽略該異常。
3. 不要忽略異常當一個API方法拋出checked exception時,如果對你沒什么意義,直接轉換成unchecked exception拋出,而不要僅僅用空的catch來忽略該異常。
處理示例:
之前代碼:
public
void
dataAccessCode(){
try
{
..some code that throws SQLException
}
catch
(SQLException ex){
ex.printStack
trace
();
}
}
上面catch段僅僅抑制住了異常,什么都沒做,改成下面部分后,
public
void
dataAccessCode(){
try
{
..some code that throws SQLException
}
catch
(SQLException ex){
throw
new
RuntimeException(ex);
}
}
此時,若發生SQLException異常,會拋給高層RuntimeException異常,使得當前線程掛起並爆出異常信息。此時邏輯層也不需要進行不必要的異常處理,尤其是邏輯層不知道怎么處理SQLException。
補充:若邏輯層打算采取恢復措施應對SQLException,那可以拋出自定義的checked exception。
4. 如果自定義的異常沒有提供有用的信息的話,請不要創建它們。
a.當前異常發生時,給出一些相關的數據信息
public
class
DuplicateUsernameException
extends
Exception {
public
DuplicateUsernameException
(
String
username){....}
public
String
requestedUsername(){...}
public
String
[] availableNames(){...}
}
b.若不想獲取詳細信息,拋出標准異常也可以。
throw
new
Exception(
"Username already taken"
);
注:拋出什么異常參見最佳實踐第一點
使用異常的最佳實踐
1. 記得釋放資源
public
void
dataAccessCode(){
Connection conn =
null
;
try
{
conn = getConnection();
..some code that throws SQLException
}
catch
(SQLException ex){
ex.printStack
trace
();
}
finally
{
DBUtil.closeConnection(conn);
}
}
class
DBUtil{
public
static
void
closeConnection
(Connection conn){
try
{
conn.close();
}
catch
(SQLException ex){
logger.error(
"Cannot close connection"
);
throw
new
RuntimeException(ex);
}
}
}
DBUtil關閉連接工具類,最重要的部分在於finally,無論異常發不發生都會執行關閉連接操作,如果關閉發生異常會拋出一個RuntimeException。
2. 不要使用異常作控制流程之用
public
void
useExceptionsForFlowControl() {
try
{
while
(
true
) {
increaseCount();
}
}
catch
(MaximumCountReachedException ex) {
}
//Continue execution
}
public
void
increaseCount()
throws MaximumCountReachedException {
if
(count >=
5000
)
throw
new
MaximumCountReachedException();
}
生成棧回溯的代價很昂貴,棧回溯的價值是在於調試,而不是流程控制中。
3 不要catch最高層次的exception
try
{
..
}
catch
(Exception ex){
}
上面這種寫法不建議,應該對具體異常分類處理。