l

2010年3月16日 星期二

敏捷式例外處理設計 (4):我到底哪裡做錯之 nested try block

03/16 20:4421:55

以下的內容需要先閱讀『敏捷式例外處理設計的第一步:決定例外處理等級』比較容易理解。

今天 Teddy 要談 nested try block 這個 exception handling bad smell。請看以下 Java 程式片段:

public void doIt(){

    FileInputStream in = null;
    try {
         in = new FileInputStream(...);
    }
     catch (IOException e){
          // do something
     }
     finally {
         try {
              if (in != null)
                     in.close();
        }
        catch(IOException e)
       {
              // log the exception
       }
    }
}



請注意finally clause 裡面出現了另外一個 try block,這個 try block 被另外一個 try block 包圍,因此裡面的這個 try block 就稱為 nested try block。那麼 nested try block 有什麼問題,為什麼是一個 bad smell


廣義的來說,程式碼出現 nested code constructs (例如很多層的 if-then-else, for while loops) 將會使程式變得不容易閱讀,測試,維護。這個問題同樣也會出現在 nested try block 之中。請鄉民們再看一下上面那一小段 code,才兩層的 nested try block 就已經有點讓人眼花撩亂了。


第二個問題,也是比較容易被忽略的問題,就是nested try block可能會造成 duplicate code。寫在 finally clause 裡面的這一段 code :
 
try {
      if (in != null)
           in.close();
}
catch(IOException e)
{
       // log the exception
}


是一段常見用來釋放 IO 資源的程式碼(cleanup code),先判斷 IO 物件是否有被正常產生(不是 null)。如果是,就呼叫 close() 來歸還 IO 資源(通常是還給作業系統)。這樣的程式碼其實是可以重複使用的,如果直接在 finally clause 裡面用一個 nested try block cleanup code 包起來,就造成 duplicate code。這樣的 duplicate code 其實還滿容易看到的,但卻容易被忽略。


*******

Replace nested try block with method


要移除 nested try block 可以套用 Replace Nested try Block with Method 這個 refactoring,請看:



public void doIt(){

    FileInputStream in = null;
    try {
         in = new FileInputStream(...);
    }
     catch (IOException e){
          // do something
     }
     finally {
         try {
              if (in != null)
                     in.close();
        }
        catch(IOException e)
       {
              // log the exception
       }
    }
}  
 

變成



public void doIt(){

    FileInputStream in = null;
    try {
          in = new FileInputStream(...);
    }
     catch (IOException e){
         // do something
    }
    finally {
        closeIO(in);
    }
private void closeIO(Closeable c){

    try {
          if (in != null)
               in.close();
    }
   catch(IOException e)
   {
        // log the exception
   }
}

如果要共用 closeIO() 這個 method,可以把它變成 public static 然後寫在某個 utility class 上面。基本上這個 refactoirng extract method 的概念是一樣的,只不過 replace nested try block with method 強調的是用來改善例外處裡的程式。

另外,眼尖的鄉民們可能會注意到,在 closeIO() 裡面發生的例外被忽略了,這樣不是造成了前天所講的 dummy handler 這個 bad smell?光是從『程式碼結構』(code structure)來看,這的確是一個 dummy handler。不過由於這個 closeIO() 是放在 finally clause 裡面,此時並不適合套用 Replace Dummy Handler with Rethrow。因為如果在 finally clause 中丟出例外,則該例外將會『覆蓋』之前發生的例外(如果在 try clause 或是 catch clause 中有發生例外,那麼將會被 finally clause 所丟出的例外給覆蓋)。 所以,Java語言建議 programmers 不要在 finally clause 中丟出例外。因此,在這個限制之下,我們認為將例外記錄下來是一種妥善的例外處裡方法,也就不算是 dummy handler了。

如果 closeIO() 真的發生例外那該怎麼辦? closeIO() 的例外代表 cleanup 發生問題,也就是程式(在這個例子裡面就是 Java 提供的 FileInputStream 這個物件可能有 bug)可能存在因為資源釋放失敗而導致的 memory leak。由於在此僅僅將例外記錄下來,因此 runtime 的時候程式還是會繼續執行,只能靠事後檢查 log file 來查看是否有問題。不過『理論上』,這種例外『應該』不會發生才對...

友藏內心獨白:Java 的設計者有沒有想過IO 物件的 close() method 所丟出的 IOException 到底要如何處理?

6 則留言:

  1. > Java 的設計者有沒有想過IO 物件的 close() method 所丟出的 IOException 到底要如何處裡?

    兩眼開開準備投胎?!

    and 最後一個字打錯了,是處"理" ......

    回覆刪除
  2. 據我所了解,.NET 中的 Dispose() 方法,建議不要丟出任何 Exception。內建的函式庫可能都有遵守這個方針,只要自己寫的也遵守,那麼就不必在 finally 中的清除動作再作 try/catch。

    回覆刪除
  3. 今天很巧看到 Comparison of Java and C Sharp http://en.wikipedia.org/wiki/Comparison_of_C_Sharp_and_Java
    搜尋 dispose 的第一處,有
    using (StreamWriter file = new StreamWriter("test.txt"))
    {
    file.Write("test");
    }
    這裡的 using 關鍵字,保證脫離 {} 時一定會執行 file.Dispose(),不管有沒有丟出例外。用這個寫法就可以避免使用 try/finally 了。

    回覆刪除
  4. Hi Chris:

    感謝您的補充,關於您提到的 Comparison of Java and C Sharp 這篇文章 Teddy 之前也有看過,使用 using 的確可以避免在 finally clause 出現 nested try block 的問題 。由於文章的用意主要是要說明 nested try block 是一個 bad smell, 所以一定要想辦法生一個例子出來,很自然的就拿 Java 來當例子。由於 Java 區分 checked, unchecked exceptions, 所以有一些 exception handling smells 在 Java 很容易看到,在其他語言就比較少。不過 C# 應該也是會有出現 nested try block 的機會,有時間 Teddy 再去找個例子。

    回覆刪除
  5. 終於找到『Finally Clause小叮嚀』的相關網誌。那天聽學長再說的時候,我就覺得,好像有在哪裡看到一模一樣的說法,果然,還是學長說得,而且這麼早就說過一次了。

    回覆刪除