異?!x《編寫高質量代碼:改善Java程序的151個建議》(八)

讀書,收獲,分享
建議后面的五角星僅代表筆者個人需要注意的程度。
Talk is cheap.Show me the code

建議110:提倡異常封裝★★☆☆☆

Java API提供的異常只有開發(fā)人員才能看得懂,而對于終端用戶來說,這些異?;旧暇褪翘鞎窃撛趺崔k?這就需要我們對異常進行封裝了。異常封裝有三方面的優(yōu)點:

  1. 提高系統(tǒng)的友好性,如下:

    public static void doStuff2() throws MyBussinessException{
            try {
                InputStream is = new FileInputStream("無效文件.txt");
            } catch (FileNotFoundException e) {
                //為方便開發(fā)和維護人員而設置的異常信息
                e.printStackTrace();
                //拋出業(yè)務異常
                throw new MyBussinessException(e);
            }
            /*文件操作*/
        }
    
  2. 提高系統(tǒng)的可維護性,如下:

    public void doStuff2(){
            try{
                //do something
            }catch(FileNotFoundException e){
                log.info("文件問找到,使用默認配置文件……");
            }catch(SecurityException e){
                log.error("無權訪問,可能原因是……");
                e.printStackTrace();
            }
        }
    
  3. 解決Java異常機制自身的缺陷(Java中的異常一次只能拋出一個)。如下:

    class MyException extends Exception {
        // 容納所有的異常
        private List<Throwable> causes = new ArrayList<Throwable>();
    
        // 構造函數,傳遞一個異常列表
        public MyException(List<? extends Throwable> _causes) {
            causes.addAll(_causes);
        }
    
        // 讀取所有的異常
        public List<Throwable> getExceptions() {
            return causes;
        }
    }
    
    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);
            }
    
        }
    

建議111:采用異常鏈傳遞異常★★☆☆☆

設計模式中有一個模式叫做責任鏈模式(Chain of Responsibility),它的目的是將多個對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止,異常的傳遞處理也應該采用責任鏈模式。

對于異常先封裝,然后傳遞,過程如下:

  1. FileNotFoundException封裝為MyException。
  2. 拋出到邏輯層,邏輯層根據異常代碼(或者自定義的異常類型)確定后續(xù)處理邏輯,然后拋出到展現層。
  3. 展現層自行決定要展現什么,如果是管理員則可以展現低層級的異常,如果是普通用戶則展示封裝后的異常。

示例如下:

public class IOException extends Exception {

    public IOException() {
        super();
    }
    //定義異常原因
    public IOException(String message) {
        super(message);
    }
    //定義異常原因,并攜帶原始異常
    public IOException(String message, Throwable cause) {
        super(message, cause);
    }
    //保留原始異常信息
    public IOException(Throwable cause) {
        super(cause);
    }
}

//調用
      try{
            //doSomething
        }catch(Exception e){
            throw new IOException(e);
        }

建議112:受檢異常盡可能轉化為非受檢異常★☆☆☆☆

受檢異常不足的地方:

  1. 受檢異常使接口聲明脆弱

    interface User{
        //修改用戶密碼,拋出安全異常
        public void changePassword() throws MySecurityException;
    }
    //這會導致所有的User調用者都要追加對RejectChangeException異常問題的處理。
    
  2. 受檢異常使代碼的可讀性降低

    public static void main(String[] args) {
        //調用者必須對異常進行處理
            try{
                user.changePassword();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    
  3. 受檢異常增加了開發(fā)工作量

注意:受檢異常威脅到系統(tǒng)的安全性、穩(wěn)定性、可靠性、正確性時,不能轉換為非受檢異常。

建議113:不要在finally塊中處理返回值★☆☆☆☆

可能會引發(fā)以下問題:

  1. 覆蓋了try代碼塊中的return返回值

    public static Person doStuffw() {
            Person person = new Person();
            person.setName("張三");
            try {
                return person;
            } catch (Exception e) {    
    
            } finally {
                // 重新修改一下值
                person.setName("李四");
            }
            person.setName("王五");
            return person;
        }
    
  2. 屏蔽異常

    public static void doSomeThing(){
            try{
                //正常拋出異常
                throw new RuntimeException();
            }finally{
                //告訴JVM:該方法正常返回
                return;
            }
        }
    

注意:不要在finally代碼塊中出現return語句。

建議114:不要在構造函數中拋出異常★☆☆☆☆

Java的異常機制有三種:

  1. Error類及其子類表示的是錯誤,無法處理。
  2. RuntimeException類及其子類表示的是非受檢異常,需要處理。
  3. Exception類及其子類(不包含非受檢異常)表示的是受檢異常,必須處理。

從系統(tǒng)設計和開發(fā)的角度來分析,盡量不要在構造函數中拋出異常,原因分析:

  1. 構造函數拋出錯誤是程序員無法處理的

  2. 構造函數不應該拋出非受檢異常

    • 加重了上層代碼編寫者的負擔
    • 后續(xù)代碼不會執(zhí)行
  3. 構造函數盡可能不要拋出受檢異常

    • 導致子類代碼膨脹
    • 違背了里氏替換原則,里氏替換原則是說“父類能出現的地方子類就可以出現,而且將父類替換為子類也不會產生任何異?!?/li>
    • 子類構造函數擴展受限

建議115:使用Throwable獲得棧信息★☆☆☆☆

JVM在創(chuàng)建一個Throwable類及其子類時會把當前線程的棧信息記錄下來,以便在輸出異常時準確定位異常原因,我們來看Throwable源代碼:

public class Throwable implements Serializable {
    //出現異常記錄的棧幀
    private StackTraceElement[] stackTrace;
    //默認構造函數
    public Throwable() { 
        //記錄棧幀
        fillInStackTrace();
    }
    //本地方法,抓取執(zhí)行時的棧信息
    private synchronized native Throwable fillInStackTrace();

}

在出現異常時(或主動聲明一個Throwable對象時),JVM會通過fillInStackTrace方法記錄下棧幀信息,然后生成一個Throwable對象,這樣我們就可以知道類間的調用順序、方法名稱及當前行號等了。

建議116:異常只為異常服務★☆☆☆☆

異常雖然是描述例外事件的,但能避免則避免。

建議在異常誕生前就消除掉,比如增加判斷,以提高程序的性能和穩(wěn)定性。

注意:異常只為確實異常的事件服務。

建議117:多使用異常,把性能問題放一邊★☆☆☆☆

異常有一個缺點:性能比較慢。

Java的異常處理機制確實比較慢,這個“比較慢”是相對于諸如String、Integer等對象來說的,單單從對象的創(chuàng)建上來說,new一個IOException會比String慢5倍,這從異常的處理機制上也可以解釋:因為它要執(zhí)行fillInStackTrace方法,要記錄當前棧的快照,而String類則是直接申請一個內存創(chuàng)建對象,異常類慢一籌也就在所難免了。而且,異常類是不能緩存的。

經過測試,在JDK 1.6下,一個異常對象創(chuàng)建的時間只需要1.4毫秒左右(注意是毫秒,通常一個交易處理是在100毫秒左右),難道我們的系統(tǒng)連如此微小的性能消耗都不允許嗎?

注意:性能問題不是拒絕異常的借口。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容