l

2011年6月15日 星期三

Checked or unchecked exceptions (3)

June 09 22:41~23:59

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

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


簡單的說,當收到一個 exception 的時候,除了 exception type 之外,還要考慮下列幾個因素才能決定要如何處理例外:
  • Recoverability
  • Application Context
  • Robustness Level 
  • Exception Handling Policy (Strategy)
今天先跳過第二個問題,來談一下第三的問題:

Checked exceptions 要宣告在 method 的 signature 上,所以如果一個  method 會丟出去的 checked exceptions 數目或是 type 改變了,那個這個 method 的 signature 也變了,就等於 interface 也變了。改變介面這件事可不是好玩滴...

上述現象有一個術語來稱呼它,就叫做『由 checked exception 所引起的 interface evolution(以下簡稱 interface evolution)』。相信很多(絕大多數?)有在寫 Java 程式的鄉民們都會認為 interface evolution 是一件很煩的事情,因為如果某個 method 一旦發生 interface evolution,那麼所有呼叫到該 method 的 clients 都會被影響。看一下這個例子:


public method x() throws IOException {}

public method callee () {

    try {
         x();
   }
   catch (IOException e) {
        // do something...
  }
}


如果 x 變成了:

public method x() throws IOException,  SQLException {}

那個 callee 要被迫改成下面這個樣子(還記得 Java 的 handle-or-declare rule 嗎...):


public method callee () {


    try {
         x();
   }
   catch (IOException e) {
        // do something...

  }
   catch (SQLException e) {
        // do something...

  }

}
或是:

public method callee () throws SQLException {


    try {
         x();
   }
   catch (IOException e) {
        // do something...

  }

}

只因為程式中用到的某個 method 多丟出了一個 checked exception,原本可以 work 的程式連 compile 都不過,這樣不是很討厭嗎?如果改用 unchecked exceptions 那該多好。請看下面這個改用  unchecked exception 的例子:


public method x() throws IOException {
    // do something...

    throw new MyRuntiimeException();
}


好棒喔,因為用了 runtime exceptions,所以原本的 callee 就不需要修改也不會有 compile errors 了。


public method callee () {


    try {
         x();
   }
   catch (IOException e) {
        // do something...

  }

}
***

事情當然沒有鄉民們想得那麼簡單,請思考下面幾點:
  • 如果 method x 在第一版(只會丟出 IOException 的那個版本)完成之後發現會因為其他的原因導致 method x 執行失敗,所以需要通知 callee 讓 callee 有機會可以做相對應的因應措施。此時 method x 如果採用  runtime exception 來代表一個 recoverable exception,那麼關於 method x 的修正版會丟出一個新的 exception 這件事情, callee 是『無從得知』的(無法從 compiler 那裡得到通知)。假設有 N 個 clients 都呼叫了 method x,那麼這 N 個 clients 在 compile time『看起來』是不會因為 method x 的 interface evolution 造成任何影響,但是實際上 runtime 的行為其實已經因為 method x 丟出一個新的 exception 這件事情而被改變了。
  • 也就是說,從『廣義』的角度來看,checked exceptions 會造成 explicit interface evolution ,而 unchecked exceptions(如果是拿來當成 recoverable exceptions 來用的話)會造成 implicit interface evolution
  • 當然,如果 unchecked exceptions 是當成 programming errors(bugs)的用法,那麼就不會造成 interface evolution。

Interface evolution(無論是否因為 exceptions 所造成)雖然討厭(尤其是當改變介面的那個 method 已經很有多人使用者的時候),但卻是必要的。為什麼?舉個例子,在結婚前身份證上面的配偶欄是空白的,那麼結婚之後配偶欄就會登記配偶的姓名(interface evolution)。如果沒有這個 interface evolution,那麼很多人就可能在『無意之下』變成了別人的『小三』(小三內心吶喊:怎麼會這樣,我老早就檢查過了,XXX 身份證上的配偶欄是明啊明是空白的啊!)這下子知道 interface evolution 有多重要了吧...XD

所以,如果程式的行為已經改變,其變化之大已經和原本的行為『不相容』了(如何定義程式行為以及新舊的行為是否相容則又是另外一個複雜的問題了),那麼就應該要改變原先的介面(或是創造一個新版本的介面),讓 client 知道該 method 已非昔日阿蒙了...XD。

***

最後結論,如果真的需要改面介面的時候該怎麼辦?
  • Separating a public interface from a published interface
  • Refactoring unpublished interfaces
  • Declaring published interfaces with circumspection
  • Making published interfaces immutable
上述四點請自行端詳...

***

友藏內心獨白:睡覺先。

2 則留言:

  1. Hi Teddy, 因為買了您的新書才得知這個blog, 剛好看到您寫的一系列exception handling的文章,今天一口氣看完,受益良多,釐清許多重要觀念,感覺內功又增強了,真的很棒。

    回覆刪除
  2. Hi Stone:

    有人可以一口氣把 exception handling這系列看完想必也是不簡單的人物啊...XD。說來慚愧,其實 exception handling 才是 Teddy 『理論上』的專長,不過後來不務正業部落格上面都在講 Scrum XD。

    很高興你會喜歡這系列的文章。

    回覆刪除