l

2011年6月1日 星期三

Checked or unchecked exceptions (2)

June 01 21:26~23:15

Checked or unchecked exceptions 第 1 集 結尾 Teddy 留下了四個問題,今天來談一下第一個問題:

雖說 checked exceptions for recoverable conditions,但是為什麼偏偏當程式中遇到一個 checked exception 的時候鄉民們卻打死也不知道要如何去 recover 任何東東?舉個最常見的 checked exception, IOException,為例,處理檔案,stream,socket 都會『附贈』IOException,收到這份大禮之後該怎麼辦?沒人知道。

把上面這個問題再簡化一下,就是說:為什麼大家都不知道要如何處裡 exceptions(尤其是 checked exceptions)? 看一個 The Java Programming Language, 4ed, p.33 的例子:

class BadDataSetException extends Exception {}

Class MyUtility {

    public double[] getDataSet(String setName) throws BadDataSetException {
        String file = setName + ".dest";
        FileInputStream in = null;
    try {
       in  = new FileInputStream(file);
       return readDataSet(in);
    } catch (IOException e) {
       throw new BadDataSetException();
    } finally {
       try {
           if (in != null)
                    in.close();
       } catch (IOException e);
          ; // ignore; we either read teh data OK
        // or we're throwing BadDataSetException
       }
    }      
  }
    // ... definition of readDataSet ...
}

這是一個很典型的例子,寫過 Java 程式的鄉民應該都不陌生。getDataSet() method 遇到兩次 IOException:

  • 第一次 IOException:執行 new FileInputStream(file) 會丟出 IOException,請看 FileInputStream 的宣告 public FileInputStream(String name) throws FileNotFoundException。上面的這個例子捕捉到這個 IOException 之後,處裡的方法就是『轉成 BadDataSetException 然後繼續往外丟』。也就是說,把這個問題推給別人去煩惱。
  • 第二次 IOException:執行 in.close() 會丟出 IOException,這次更妙,直接忽略這個 IOException。奇怪了,Java 的 exception handling 規則中,不是有一條提到『不可以忽略例外嗎』,為什麼這個範例就可以正大光明的忽略例外?難到這就是傳說中的『叔叔有練過,小朋友不要學喔』。

***

當收到一個 exception 時(不管是 checked or unchecked),要判斷如何處裡,光靠 exception type 是不夠滴,需要『同時』考慮好幾個因素: 

Recoverability

收到這個 exception 時,有沒有可能修復例外所造成的錯誤。如果套用 Java 原本的設計精神,遇到 unchecked exceptions 表示程式中有 bug,所以鄉民們也不用想著在程式中如何處理 unchecked exceptions,只要乖乖的回報給 users or developers 便可,不要連在程式中連 unchecked exceptions 都給吞掉了,以下為錯誤示範:

try {
         // do something

} catch (RuntimeException e) {
        // 沒事不補抓 unchecked exceptions,除非是在 main program 中要準備回報該例外
}

那 checked exceptions 就一定是 recoverable 的嘛?也不一定,因為一個 exception 只提供了某種 local context,光依靠這個 local context 在絕大多數狀況下是無法直接判斷要採取何種措施,通常要考慮到下面這個因素(application context)才能夠判斷是否應該在『此時,此地』來 recover 某個由例外所造成的錯誤狀態。

Application Context

Application context 提供了一個 global context,協助 developers 來設計例外處理的方法。最簡單的一個例子,就是依據 exception 發生在 software architecture 的那一層(如果是 layered architecture)或是那一個 tier 來協助 developers 判斷要如何處理例外。舉個具體的例子,如果鄉民們的軟體架構長成這樣子:

------------------------
        UI
------------------------
   Controller
------------------------             
      Utility
 ------------------------           

如果有一個 IOException 發生在 Utility 這一層,鄉民們該如何處置?由於 Utility 這一層的物件很有可能被其他的 applications 所共用,所以在這裡面如果存在太多  application specific exception handling code 那就會降低 Utility 的可重複使用性。所以,通常在越底層的物件,由於其所具備的 application context 知識較少,所以例外處理方法越簡單越好,可能只要保證當例外發生的時候,自己的狀態是正確的,並且將例外回報給上一層的人便可。

但是,如果 Controller 這一層的人收到例外,就有比較多的 information 可以判斷要如何處理。例如,鄉民們撰寫一個檔案複製的工具,Utility 這一層收到了一個 IOException,不知道如何處理,繼續往上丟。在 Controller 這一層的 CopyController 物件收到了底層所丟上來的 IOException,此時 CopyController 就可以選擇 retry 這種策略。

Robustness Level

有時候明明知道某個 exception 在某些狀況下一定會發生,而且有很具體的方法可以來處理,但是實際上 developers 卻沒有去處理,這是為什麼?答案很簡單,通常是...『沒時間』(因為老闆說要 time to market 啊)。長期下去,程式變得越來越亂,也越來越不可能有時間去處理這些以前留下來的例外處理問題。所以,收到一個 exception 的時候,到底要不要去花時間 recover ,就取決於所謂的 Robustness Level(請參考『敏捷式例外處理設計的第一步:決定例外處理等級』)。鄉民明把 Robustness Level 簡單想成『例外處理的 requirement』便可,基本上只有三個簡單的 level:
  • Level 1: Error-reporting (錯誤回報)
  • Level 2: State-recovery (狀態回復)
  • Level 3: Behavior-recovery (行為回復)
在此就不再重複說明了,只是簡單補充一下,如果鄉民們在某一個 sprint 決定這個 sprint 的所有  stories 的例外處理等級都只要達到  Level 1 就好了,那麼就算是遇到某個很明顯可以 recover 的 checked exception,此時也不用處理,直接轉成 unchecked exception 往上丟即可。

Exception Handling Policy (Strategy)

最後一點就是鄉民們可能不知道要如何撰寫例外處理程式來 recover 錯誤狀況。假設從 recoverability,application context,  robustness level 這三個面向來判斷,某個 exception 是應該要被處理的,但是,問題來了,要採用哪種方法,策略來處理這個例外狀況?舉個例子,假設鄉民們在 Windows 上要讀取硬碟的 serial number 資料,這個資料可以從 WMI 裡面得到,但是,某一天突然有客戶回報說,他的某顆硬碟在 Windows 2003 上面讀不到 serial number,請問 單兵 鄉民該如何處置?

用 exception handling 的術語來講,primary method (採用 WMI 方式讀取 serial number)失敗,那就可以有下列三種選擇:
  • Retry with the original method and arguments.
  • Retry with the original method and new arguments.
  • Retry with alternative methods.
以 WMI 的例子來看,這看起來好像是 Windows 2003 WMI 的 bug,所以前兩種策略是行不通的(有問題的方法,呼叫再多次還是有問題)。看起來要找其他讀 serial number 的方法(alternative)才有可能可以解決。
***

結論,為什麼遇到一個 exception (無論是 checked or unchecked)不知道要如何處理?看完這一篇,就算是沒有辦法涵蓋 100% 的狀況,至少也包含了 99.99% 80% 的情形。什麼,還是不懂...嗯嗯...如有此種現象的鄉民,請報名『exception handling 保證班』...XD。

***

友藏內心獨白:Exception handling 的觀念其實很像保險 。

沒有留言:

張貼留言