天天看點

提高你的Java代碼品質吧:提倡異常封裝

一、分析

Java語言的異常處理機制可以確定程式的健壯性,提高系統的開發效率,但是JavaAPI提供的異常都是比較低級(這裡的低級指的是“低級别的異常”),隻有開發人員才能看的懂,才明白發生了什麼問題。對于終端使用者來說,這些異常基本上是天書,與業務無關,是純計算機語言的描述。

這就需要我們對異常進行封裝了。

二、場景

異常封裝有三方面的優點:

1.提高系統的友好性

例如,打開一個檔案,如果檔案不存在,則會報FileNotFoundException異常,如果該方法的編寫不做任何處理,直接上抛上層,則會降低系統的友好性,代碼如下所示:

public static void doStuff()throws Exception{ 
    InputStream is = new FileInputStream("無效檔案.txt"); 
    /*檔案操作*/ 
}
           

此時doStuff方法的友好性極差:出現異常時(比如檔案不存在),該方法直接把FileNotFoundException異常抛出到上層應用中(或者是終端使用者),而上層應用(或使用者)要麼自己處理,要麼接着抛出,最終的結果就是讓使用者對着“天書”式的文字發呆,使用者不知道這是什麼問題,隻是系統告訴他”哦,我出錯了,什麼錯誤?你自己看着辦吧“。

解決辦法就是封裝異常,可以把系統的閱讀者分為兩類:開發人員和使用者。開發人員查找問題,需要列印出堆棧資訊,而使用者則需要了解具體的業務原因,比如檔案太大,不能同時編寫檔案等,代碼如下:

public static void doStuff2()throws MyBussinessException{ 
    try{ 
        InputStream is = new FileInputStream("無效檔案.txt"); 
    }catch(FileNotFoundException e){ 
        //為了友善開發和維護人員而設定的異常資訊 
        e.printStackTree(); 
        //抛出業務異常 
        throw new MyBussinessException(e); 
    } 
} 
           

2.提高系統的可維護性

public void doStuff(){ 
    try{ 
        //do something 
    }catch(Exception e){ 
        e.printStackTrace(); 
    } 
} 
           

這是很多程式員容易犯的錯誤,抛出異常是吧?分類處理多麻煩,就寫一個catch塊來處理所有異常吧。而且還信誓旦旦的說”JVM會列印出棧中的錯誤資訊“,雖然這沒有錯,但是該資訊隻有開發人員自己才看的懂,維護人員看見這段異常基本上無法處理,因為需要深入到代碼邏輯中去分析問題。

正确的做法是對異常進行分類處理,并進行封裝輸出,代碼如下:

public void doStuff(){ 
try{ 
    //do something 
    }catch(FileNotFoundException e){ 
        log.info("檔案夾未找到,使用預設配置檔案...."); 
    }catch(SecurityException 3){ 
        log.info("無權通路,可能原因是...."); 
        e.printStackTrace(); 
    } 
} 
           

如此包裝後,維護人員看到這樣子的異常就有了初步的判斷,或者檢查配置,或者初始化環境,不需要直接到代碼層級去分析了。

3.解決Java異常機制自身的缺陷

Java中的異常一次隻能抛出一個,比如,doStuff方法有兩個邏輯代碼片段,如果在第一個邏輯片段中抛出異常,則第二個邏輯片段就不執行了,也就無法抛出第二個異常了。那麼如何才能一次抛出兩個異常呢?

其實,使用自行封裝的異常可以解決問題,代碼如下:

class MyException extends Exception{ 
    //容納所有異常 
    private List<Throwable> causes = new ArrayList<Throwable>(); 
    //構造函數,傳遞一個異常清單 
    public MyException(List<? extends Throwable> _causes){ 
        cause.addAll(_causes); 
    } 
 
    //讀取所有的異常 
    public List<Throwable> getException(){ 
        return causes; 
    } 
} 
           

MyException異常隻是一個異常容器,可以容納多個異常,但它本身并不代表任何異常含義,它所解決的是一次抛出多個異常的問題,具體調用如下:

public static void doStuff()throws MyException{ 
    List<Throwable> list = new ArrayList<Throwable>(); 
    //第一個邏輯片段 
    try{ 
        //Do something 
    }catch(Exception e){ 
        list.add(e); 
    } 
 
    //第二個邏輯片段 
    try{ 
        //Do something 
    }catch(Exception e){ 
        list.add(e); 
    } 
 
    //檢查是否有必要抛出異常 
    if(list.size() > 0){ 
        throw new MyException(list); 
    } 
} 
           

這樣一來,doStuff方法的調用者就可以一次獲得多個異常,也能夠為使用者提供完整的例外情況說明。

那麼在什麼情況下,需要一個方法抛出多個異常呢?比如Web界面注冊時,展示層依次把User對象傳遞給邏輯層,Register方法需要對各個Field進行校驗并注冊,例如,使用者名不能重複,密碼必須符合密碼政策等,不要出現第一次送出系統提示”使用者名重複“,在修改在送出後,系統提示“密碼長度少6位”的情況,這種操作模式使用者體驗非常糟糕,最好的解決辦法就是封裝異常,建立異常容器,一次性地對User對象進行校驗,然後傳回所有異常。

三、建議

在開發的過程中,根據具體的情況和需要,對異常進行封裝。