03/22 20:12~22:21
以下的內容需要先閱讀『敏捷式例外處理設計的第一步:決定例外處理等級』比較容易理解。
今天 Teddy 回到例外處裡這個主題,要談的是 unprotected main program 這個 exception handling bad smell。如果鄉民們還記得『例外處裡強健性等級』,那麼應該知道你所開發的程式應該至少要達到 G1 (error-reporting) 等級:遇到例外直接往外丟,如果是 checked exceptions 則轉成 unchecked exceptions 往外丟。這下好了,大家都卯起來把例外往外丟,當這些未被捕捉 (uncaught) 的例外飄到程式最上層 (例如 main program,thread,或是跨過某個 layer),可能會導致系統不預期的終止。由於此時系統的狀態可能已經不正確了,所以終止執行也是應該的。但是就算是十惡不赦的壞人,在臨死之前也應該給他個機會交代一下遺言。而 unprotected main program 的問題就是『沒有給程式在臨死前交代遺言的機會』。請看以下 Java 程式片段:
static public void main(String [] args){
// some code
} 程式遇到例外直接結束,將控制權回傳給作業系統。
***
Avoid unexpected termination with big outer try block
要移除 unprotected main program 可以套用 Avoid unexpected termination with big
outer try block 這個 refactoring,請看:
static public void main(String [] args){
// some code
}
變成
static public void main(String [] args){
try {
// some code
} catch (Throwable e) {
// displaying and/or looging the exception
}
}
上面的例子看起來很簡單,當程式執行時也只有一個 main program,感覺Avoid unexpected termination with big outer try block 的應用好像很有限。其實這個 refactoring 可以應用在其他不同的地方,例如之前曾經提過的 Replace nested try block with method 這個 refactoring 的實做:
private void closeIO(Closeable c){
try {
if (in != null)
in.close();
}
catch(IOException e)
{
// log the exception
}
}
closeIO() 這個用來釋放 IO 資源的 method 內部就用一個 big outer try block 包起來,以防止 closeIO() 不正常終止。
***
鄉民甲:不是說程式狀態不對就應該要終止嗎?
Teddy:這裡的用法是一個特例,請參考 敏捷式例外處理設計 (4):我到底哪裡做錯之 nested try block。
***
如果你的軟體架構採用 layer架構,當例外從底層的某個 layer 要傳遞到上層的 layer,此時也可以套用 Avoid unexpected termination with big outer try block 來把例外轉成對上層 layer 比較有意義的例外。看一下這個例子:假設你寫了一個 updateUser() method用來更新資料庫裡面的使用者資料,這個 method 會被 Web UI 呼叫。由於客戶要求每一筆資料修改都要紀錄之前的歷史資料,所以你寫了一個通用的 updateChangeHistoryInternal() method 來執行紀錄歷史資料的工作。你將更新使用者的實做寫在 updateUserInternal() method 裡面,然後把 updateUserInternal() 和 updateChangeHistoryInternal() 包在 updateUser() 中,如下所示:
public void updateUser(User aUser){
updateUserInternal(aUser);
updateChangeHistoryInternal(aUser);
}
假設 updateUserInternal() 和 updateChangeHistoryInternal() 會丟出下列幾個 unchecked exceptions:
- updateUserInternal: InvalidDataException, DuplicateKeyException
- updateChangeHistoryInternal: ConstraintViolationException
如果 updateUser() 執行發生錯誤,呼叫它的人可能會收到 InvalidDataException, DuplicateKeyException,或是ConstraintViolationException 。這三個例外可以被視為是『實做例外』(implementation exception),這些實做例外不應該直接傳給不同 layer 的其他程式。如果讓 caller 收到實做例外,那麼 caller 會面對下列幾個問題:
- 太多實做例外對 caller 而言不容易捕捉並處裡。
- 如果實做方式改變,那麼 caller 的例外處裡程式碼也需要隨著改變。
請看下面範例程式碼:
public void caller(){
User user = new User(...);
try {
updateUser(user);
}
catch(InvalidDataException e){
// handler
}
catch(DuplicateKeyException e){
// handler
}
catch(ConstrintViolationException e){
// handler
}
}
如果 updateUserInternal() 不再丟出 DuplicateKeyException 那麼原本寫在 caller() 裡面的例外處裡程式就用不到了。
所以,老招數拿出來用:
public void updateUser(User aUser){
try {
updateUserInternal(aUser);
updateChangeHistoryInternal(aUser);
}
catch(Exception e){
throw new DAOErrorException(“Update user error.”, e);
}
}
將 updateUser() method 裡面加一個 big outer try block 也算是套用了Avoid unexpected termination with big outer try block。改完之後 caller 程式就變成:
public void caller(){
User user = new User(...);
try {
updateUser(user);
}
catch(DAOErrorException e){
// handler
}
}
***
鄉民乙:你的資料庫程式範例怎麼都沒有作 transaction control?
Teddy:你可以用 Spring AOP 來作 transaction control,這樣就不用直接再程式碼中寫 begin transaction, rollback 這類的控制。
***
友藏內心獨白:第一階段 exception handling bad smells 介紹完畢。
話說這一篇應該是(六)吧
回覆刪除而且哦 ....... 用AOP控制Transaction在實務上偶爾會發生令人"十分不快"的狀況
我應該頒發一個『忠實鄉民』獎給你...找到不少 bugs...
回覆刪除用 APO 做 transaction 會有什麼問題?趕快說來聽聽?
你好,我不知道在哪邊有讀到,try...catch...的block內的程式不是應該要越少越好嗎?這種一整塊程式都用try..catch包起來的refactor不會導致效能比較差嗎?
回覆刪除