l

2010年3月17日 星期三

敏捷式例外處理設計 (5):我到底哪裡做錯之 spare handler

03/17 20:25~22:13


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

今天 Teddy 要談 spare handler 這個 exception handling bad smellSpare handler 的意思是把 exception handler (就是寫在 catch clause 裡面的程式碼) 當成是其相對應 try clause 的備胎(有點繞口,多念幾次)。也就是說當寫在 try clause 裡面的程式碼發生例外的時候,exception handler 會嘗試提供另外一種實做來達成原本在 try clause 裡面所要達成的任務。請看以下 Java 程式片段:

public void readUser(){

    try {
          // primary implementation
    }
    catch (SomeException e){
        try {
              // alternative implementation
        }
        catch (AnotherException e){
              throw new FailureException(e);
       }
   }
}

因為寫在 catch clause 裡面的『備胎程式』在執行的時候也可能會發生例外,因此 spare handler 就很容易形成一個 nested try block(昨天介紹過的 smell)。此外,由於 spare handler 的效果相當於透過『重試』(retry) 來達成原本 try clause 所要提供的服務(PS:重試可以是重複執行原本一模一樣的程式,或是執行另外一種實做的程式,或是提供不同的參數),從上面的例子來看這樣的程式結構只能達到『重試一次』的目的,如果要達到『重試 N 次』勢必會造成更深的 nested try block


*******

Introduce resourceful try clause

要移除 spare handler 可以套用 Introduce resourceful try clause 這個 refactoring,請看:


public void readUser(){

    try {
          // primary implementation
    }
     catch (SomeException e){
            try {
                 // alternative implementation
            }
            catch (AnotherException e){
                  throw new FailureException(e);
           }
     }
}

變成 

public void readUser(){

    int attempt = 0;
    int maxAttempt = 2;
    boolean retry = false;

    do {
          try {
                retry = false;
                if (attempt == 0)
                         // primary implementation
               else
                        // alternative implementation
          }
          catch (SomeException e){
               attempt++;
               retry = true;
              if (attempt > maxAttempt)
                       throw new FailureException(e);
        }
    } while(attempt <= maxAttempt && retry)
}

Resourceful try clause』的意思是說把 try clause 寫成具備『多才多藝』(擁有一種以上的方法可以提供相同的服務)的程式碼,如此一來當例外發生的時候try clause 就可以依據 programmers 事先設定的規則來決定用何種方法提供服務。由於 Java 的例外處裡模式 (exception handling model)是採用 termination model,也就是說當例外發生的時候會終止目前控制流程,因此當例外發生之後為了讓 try clause 有機會繼續執行,必須將 try block 寫在 loop 裡面(在上面的例子中使用了一個 do-while loop),然後宣告必要的變數來控制重試的次數。

上面的例子算是『呷粗飽』的方法,程式看起來有點亂,感覺一不小心控制邏輯就會寫錯。如果要『吃的精緻一點』,則可以寫一個 class 來包裝控制重試次數的變數與邏輯。如果要求要吃到『三井宴』的等級,那麼另外一種更高檔的方法則可以將整個 do-while loop 還有控制重試邏輯的程式寫成一個 RepeatExecutor class, 接著將 try clause 所以提供的各種不同實做方法 (implementation methods) 當成參數傳給這個 RepeatExecutor class。然後 Retry class 可以透過 reflection 的方式來執行不同的實做方法。看一個簡單的例子:

public class Acceptor{

       private ServerSocket fSocket = null;
      
      public void createSocket(Integer port) throws IOException {
              fSocket = new ServerSocket(port);
     }
}

public void create Acceptor(Integer port) thorws
                                             CannotCreateAcceptorException {

    Acceptor acceptor = new Acceptor();
    IRepeatExecutor repair = new RepearExecutor();
    repair.setAttempt(2);
    repair.setMode(IRepeatExecutor.Mode.defaultValue);
    repair.addDefaultArgs(port + 1);
    repair.addDefaultArgs(port + 2);

    try {
         repair.run(acceptor, “createSocket”, port);
    }
    catch(RepeatExecutorFailureException e){
         throw new CannotCreateAcceptorExecution(e);
    }
}

上面的例子,try clause 裡面剩下一行程式:

     repair.run(acceptor, “createSocket”, port);

repair 物件透過 reflection port 當成參數執行 acceptor 物件的 createSocket() method。如果執行結果發生例外,則 repair 會重試兩次:

     repair.setAttempt(2);

第一次重試會將 port + 1 當成參數傳給 createSocket():

     repair.addDefaultArgs(port + 1);

第二次重試會將 port + 2 當成參數傳給 createSocket():
     repair.addDefaultArgs(port + 2);

repair 物件支援下列幾種常見的重試方法,分別是:
  • 用原來的參數,重複執行原本的方法 (primary implementation)
  • 用新的參數,重複執行原本的方法
  • 用原來的參數,執行其他方法 (alternative implementation)
  • 用新的參數,執行其他方法 (alternative implementation)

上面這個例子採用的是第二種:用新的參數,重複執行原本的方法。無論採用哪種方法,重點是try clause變成了 resourceful try clause。而這是一種讓程式達到 G3 (behavior-recovery) 的常用方法(前提是當例外發生之後要確保程式狀態是對的)。

友藏內心獨白:一下子從 G1跳到 G3,好像有點複雜...達到 G3 果然比較『厚工』。

沒有留言:

張貼留言