java exception的使用規範


合理使用JAVA異常機制可以使程序健壯而清晰,但不幸的是,JAVA異常處理機制常常被錯誤的使用,下面就是一些關於Exception的注意事項:

1. 原則:不要忽略checked Exception

請看下面的代碼:
try
{
    method1();  //method1拋出ExceptionA
}
catch(ExceptionA e)
{
    e.printStackTrace();
}

由於這裏的操作已經發生異常,將會導致以後的操作並不能按照預期的情況發展下去,可能導致兩個結果:

一是由於這裏的異常導致在程序中別的地方拋出一個異常,這種情況會使程序員在調試時感到迷惑,因爲新的異常拋出的地方並不是程序真正發生問題的地方,也不是發生問題的真正原因;

另外一個是程序繼續運行,並得出一個錯誤的輸出結果,這種問題更加難以捕捉,因爲很可能把它當成一個正確的輸出。

那麼應該如何處理呢,這裏有四個選擇:

(1)處理異常,進行修復以讓程序繼續執行。例如在進行數據庫查詢時,數據庫連接斷鏈後重建鏈接成功。

(2)在對異常進行分析後發現這裏不能處理它,那麼重新拋出異常,讓調用者處理。異常依次向上拋出,如果所有方法都不能恰當地處理異常,最終會在用戶界面以恰當的方式提示用戶,由用戶來判斷下一步處理措施。例如在進行數據庫查詢時,斷鏈後重試幾次依然失敗的情況。

(3)將異常轉換爲其他異常再拋出,這時應該注意不要丟失原始異常信息。這種情況一般用於將底層異常封裝爲應用層異常。

(4)不要捕獲異常,直接在函數定義中使用throws聲明將拋出該異常。讓調用者去處理該異常。

因此,當捕獲一個checked Exception的時候,必須對異常進行處理;如果認爲不必要在這裏作處理,就不要捕獲該異常,在方法體中聲明方法拋出異常,由上層調用者來處理該異常。


2. 建議:不要捕獲unchecked Exception


有兩種unchecked Exception:

Error:這種情況屬於JVM發生了不可恢復的故障,例如內存溢出,無法處理。

RuntimeException:這種情況屬於錯誤的編碼導致的,出現異常後需要修改代碼才能修復,一般來說catch後沒有恰當的處理方式,因此不應該捕獲。(該規則有例外情況,參見:5.11守護線程中需要catch runtime exception)

例如由於編碼錯誤,下面的代碼會產生ArrayIndexOutofBoundException。修改代碼後才能修復該異常。

int[] intArray = new int[10];

intArray[10]=1;


3. 原則:不要一次捕獲所有的異常

請看下面的代碼:

try
{
   method1();  //method1拋出ExceptionA
   method2();  //method1拋出ExceptionB
   method3();  //method1拋出ExceptionC
}
catch(Exception e)
{
   ……
}

這是一個很誘人的方案,代碼中使用一個catch子句捕獲了所有異常,看上去完美而且簡潔,事實上很多代碼也是這樣寫的。但這裏有兩個潛在的缺陷,一是針對try塊中拋出的每種Exception,很可能需要不同的處理和恢復措施,而由於這裏只有一個catch塊,分別處理就不能實現。二是try塊中還可能拋出RuntimeException,代碼中捕獲了所有可能拋出的RuntimeException而沒有作任何處理,掩蓋了編程的錯誤,會導致程序難以調試。

下面是改正後的正確代碼:


try
{
  method1();  //method1拋出ExceptionA
   method2();  //method1拋出ExceptionB
   method3();  //method1拋出ExceptionC
}
catch(ExceptionA e)
{
   ……
}
catch(ExceptionB e)
{
   ……
}
catch(ExceptionC e)
{
   ……
}


4  原則:使用finally塊釋放資源

什麼是資源:程序中使用的數量有限的對象,或者只能獨佔式訪問的對象。例如:文件、線程、線程池、數據庫連接、ftp連接。因爲資源是“有限的”,因此資源使用後必須釋放,以避免程序中的資源被耗盡,影響程序運行。某些資源,使用完畢後會自動釋放,如線程。某些資源則需要顯示釋放,如數據庫連接。

   finally關鍵字保證無論程序使用任何方式離開try塊,finally中的語句都會被執行。在以下情況下,finally塊的代碼都會執行:

(1) try塊中的代碼正常執行完畢。

(2)在try塊中拋出異常。

(3)在try塊中執行return、break、continue。

(4) catch塊中代碼執行完畢。

(5)在catch塊中拋出異常。

(6)在catch塊中執行return、break、continue。

因此,當你需要一個地方來執行在任何情況下都必須執行的代碼時,就可以將這些代碼放入finally塊中。當你的程序中使用了資源,如數據庫連接,文件,Ftp連接,線程等,必須將釋放這些資源的代碼寫入finally塊中。

finally關鍵字可和catch關鍵字一起使用。如下:

try
{
   ……
}
catch(MyException e)
{
   ……
}
finally
{
   ……
}

finally關鍵字也可以單獨使用,不catch異常,將異常throw給調用者處理。
try
{
   ……
}
finally
{
   ……
}

5.    原則:finally塊不能拋出異常

JAVA異常處理機制保證無論在任何情況下都先執行finally塊的代碼,然後再離開整個try,catch,finally塊。在try,catch塊中向外拋出異常的時候,JAVA虛擬機先轉到finally塊執行finally塊中的代碼,finally塊執行完畢後,再將異常拋出。但如果在finally塊中拋出異常,try,catch塊的異常就不能拋出,外部捕捉到的異常就是finally塊中的異常信息,而try,catch塊中發生的真正的異常堆棧信息則丟失了。

請看下面的代碼:

 Connection con = null;
try
{
   con = dataSource.getConnection();
   ……
}
catch(SQLException e)
{
   ……
   throw e;//進行一些處理後再將數據庫異常拋出給調用者處理
}
finally
{
   try
    {
       con.close();
    }
   catch(SQLException e)
   {
        e.printStackTrace();
        ……
   }
}

運行程序後,調用者得到的信息如下

java.lang.NullPointerException

 atmyPackage.MyClass.method1(methodl.java:266)

而不是我們期望得到的數據庫異常。這是因爲這裏的con是null的關係,在finally語句中拋出了NullPointerException,在finally塊中增加對con是否爲null的判斷可以避免產生這種情況。

6.  原則:異常不能影響對象的狀態

異常產生後不能影響對象的狀態,這是異常處理中的一條重要規則。在一個函數

中發生異常後,對象的狀態應該和調用這個函數之前保持一致,以確保對象處於正確的狀態中。

如果對象是不可變對象(不可變對象指調用構造函數創建後就不能改變的對象,即

    創建後沒有任何方法可以改變對象的狀態),那麼異常發生後對象狀態肯定不會改變。如果是可變對象,必須在編程中注意保證異常不會影響對象狀態。有三個方法可以達到這個目的:

(1)將可能產生異常的代碼和改變對象狀態的代碼分開,先執行可能產生異常的代碼,如果產生異常,就不執行改變對象狀態的代碼。

(2)對不容易分離產生異常代碼和改變對象狀態代碼的方法,定義一個recover方法,在異常產生後調用recover方法修復被改變的類變量,恢復方法調用前的類狀態。

(3)在方法中使用對象的拷貝,這樣當異常發生後,被影響的只是拷貝,對象本身不會受到影響。

7. 原則:拋出自定義異常異常時帶上原始異常信息

請看下面的代碼:


public void method2()
{
    try
    {
         ……
        method1();  //method1進行了數據庫操作
    } 
    catch(SQLExceptione)
   {
         ……
        throw new MyException(“發生了數據庫異常:”+e.getMessage);
    }
}
public void method3()
{
    try
    {
        method2();
    }
    catch(MyExceptione)
    {
        e.printStackTrace();
          ……
    }
}

上面method2的代碼中,try塊捕獲method1拋出的數據庫異常SQLException後,拋出了新的自定義異常MyException。這段代碼是否並沒有什麼問題,但看一下控制檯的輸出:

MyException:發生了數據庫異常:對象名稱'MyTable' 無效。

at MyClass.method2(MyClass.java:232)

at MyClass.method3(MyClass.java:255)

原始異常SQLException的信息丟失了,這裏只能看到method2裏面定義的MyException的堆棧情況;而method1中發生的數據庫異常的堆棧則看不到,如何排錯呢,只有在method1的代碼行中一行行去尋找數據庫操作語句了,祈禱method1的方法體短一些吧。

JDK的開發者們也意識到了這個情況,在JDK1.4.1中,Throwable類增加了兩個構造方法,publicThrowable(Throwable cause)和public Throwable(String message,Throwable cause),在構造函數中傳入的原始異常堆棧信息將會在printStackTrace方法中打印出來。


8.    原則:打印異常信息時帶上異常堆棧


爲便於分析異常原因,在進行異常輸出時需要帶上異常的堆棧,在進行編碼時該問題容易忽視,需要注意,如下:

public void method3()
{
    try
    {
         method2();
    }
    catch(MyExceptione)
   {
              ……//對異常進行處理
             System.out.println(e);//打印異常信息
   }
}

System.out.println(e)相當於System.out.println(e.toString()),不能打印異常堆棧,不利於事後對異常進行分析。

 正確的打印方式:

public void method3()
{
    try
    {
         method2();
    }
    catch(MyExceptione)
    {
             ……//對異常進行處理
              e.printStackTrace();//打印異常信息
    }
}

9. 原則:不要使用同時使用異常機制和返回值來進行異常處理

請看下面一段代碼
try
{
    doSomething();
}
catch(MyException e)
{
    if(e.getErrcode == -1)
    {
       ……
    }
    if(e.getErrcode == -2)
    {
       ……
    }
    ……
}

假如在過一段時間後來看這段代碼,你能弄明白是什麼意思嗎?混合使用JAVA異常處理機制和返回值使程序的異常處理部分變得“醜陋不堪”,並難以理解。如果有多種不同的異常情況,就定義多種不同的異常,而不要像上面代碼那樣綜合使用Exception和返回值。

修改後的正確代碼如下:

try
{
   doSomething();  //拋出MyExceptionA和MyExceptionB
}
catch(MyExceptionA e)
{
    ……
}
catch(MyExceptionB e)
{
   ……
}

10.  建議:不要讓try塊過於龐大

出於省事的目的,很多人習慣於用一個龐大的try塊包含所有可能產生異常的代碼,

這樣有兩個壞處:

閱讀代碼的時候,在try塊冗長的代碼中,不容易知道到底是哪些代碼會拋出哪些異常,不利於代碼維護。

使用try捕獲異常是以程序執行效率爲代價的,將不需要捕獲異常的代碼包含在try塊中,影響了代碼執行的效率。


11. 原則:守護線程中需要catch runtime exception

守護線程是指在需要長時間運行的線程,其生命週期一般和整個程序的時間一樣長,用於提供某種持續的服務。例如服務器的告警定時同步線程,客戶端的告警分發線程。由於守護線程需要長時間提供服務,因此需要對runtime exception進行保護,避免因爲某一次偶發的異常而導致線程被終止。

while (true)
{
    try
    {
         doSomethingRepeted();
    }
    catch(MyExceptionA e)
    {
        //對checkedexception進行恰當的處理
        ……
    }
    catch(RuntimeException e)
    {
         //打印運行期異常,用於分析並修改代碼
         e.printStackTrace();
    }
}




發佈了46 篇原創文章 · 獲贊 35 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章