天天看點

優雅地處理異常真是一門學問啊!(2)

04、建議

關于異常處理機制的使用,我這裡總結了一些非常實用的建議,希望你能夠采納。

1)盡量捕獲原始的異常。

實際應該捕獲 FileNotFoundException,卻捕獲了泛化的 Exception。示例如下。

InputStream is = null;
try {
    is = new FileInputStream("沉默王二.txt");
} catch (Exception e) {
    e.printStackTrace();
}      

這樣做的壞處顯而易見:假如你喊“王二”,那麼我就敢答應;假如你喊“老王”,那麼我還真不敢答應,萬一你喊的我妹妹“王三”呢?

很多初學者誤以為捕獲泛化的 Exception 更省事,但也更容易讓人“丈二和尚摸不着頭腦”。相反,捕獲原始的異常能夠讓協作者更輕松地辨識異常類型,更容易找出問題的根源。

2)盡量不要列印堆棧後再抛出異常

當異常發生時列印它,然後重新抛出它,以便調用者能夠适當地處理它。就像下面這段代碼一樣。

public static void main(String[] args) throws IOException {
    try (InputStream is = new FileInputStream("沉默王二.txt")) {
    }catch (IOException e) {
  e.printStackTrace();
  throw e;
    } 
}      

這似乎考慮得很周全,但是這樣做的壞處是調用者可能也列印了異常,重複的列印資訊會增添排查問題的難度。

java.io.FileNotFoundException: 沉默王二.txt (系統找不到指定的檔案。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at learning.Test.main(Test.java:10)
Exception in thread "main" java.io.FileNotFoundException: 沉默王二.txt (系統找不到指定的檔案。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at learning.Test.main(Test.java:10)      

3)千萬不要用異常處理機制代替判斷

我曾見過類似下面這樣奇葩的代碼,本來應該判 null 的,結果使用了異常處理機制來代替。

public static void main(String[] args) {
    try {
  String str = null;
  String[] strs = str.split(",");
    } catch (NullPointerException e) {
  e.printStackTrace();
    }
}      

捕獲異常相對判斷花費的時間要多得多!我們可以模拟兩個代碼片段來對比一下。

代碼片段 A:

long a = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    try {
  String str = null;
  String[] strs = str.split(",");
    } catch (NullPointerException e) {
    }
}
long b = System.currentTimeMillis();
System.out.println(b - a);      

代碼片段 B:

long a = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    String str = null;
    if (str != null) {
  String[] strs = str.split(",");
    }
}
long b = System.currentTimeMillis();
System.out.println(b - a);      

100000 萬次的循環,代碼片段 A(異常處理機制)執行的時間大概需要 1983 毫秒;代碼片段 B(正常判斷)執行的時間大概隻需要 1 毫秒。這樣的比較雖然不夠精确,但足以說明問題。

4)不要盲目地過早捕獲異常

如果盲目地過早捕獲異常的話,通常會導緻更嚴重的錯誤和其他異常。請看下面的例子。

InputStream is = null;
try {
    is = new FileInputStream("沉默王二.txt");
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
int b;
try {
    while ((b = is.read()) != -1) {
    }
} catch (IOException e) {
    e.printStackTrace();
}
finally {
    try {
  is.close();
    } catch (IOException e) {
  e.printStackTrace();
    }
}      

假如檔案沒有找到的話,InputStream 的對象引用 is 就為 null,新的 NullPointerException 就會出現。

java.io.FileNotFoundException: 沉默王二.txt (系統找不到指定的檔案。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at learning.Test.main(Test.java:12)
Exception in thread "main" java.lang.NullPointerException
    at learning.Test.main(Test.java:28)      

NullPointerException 并不是程式出現問題的本因,但實際上它出現了,無形當中幹擾了我們的視線。正确的做法是延遲捕獲異常,讓程式在第一個異常捕獲後就終止執行。

05、最後

好了,關于異常我們就說到這。異常處理是程式開發中必不可少的操作之一,但如何正确優雅地對異常進行處理卻是一門學問,好的異常處理機制可以確定程式的健壯性,提高系統的可用率。

繼續閱讀