例外處理(Exception Handling)

  • 19425
  • 0
  • 2011-01-21

例外處理(Exception Handling) 在資訊系統中是一個非常重要的議題,它可以確保系統的可用性與可靠性,避免系統在意想不到的狀況下發生錯誤,甚至導致服務中斷或資料毀損。

例外處理(Exception Handling) 在資訊系統中是一個非常重要的議題,它可以確保系統的可用性與可靠性,避免系統在意想不到的狀況下發生錯誤,甚至導致服務中斷或資料毀損。

早期的程式語言本身並不支援例外處理,像 C 語言靠的是利用函式回傳值的檢查,如果不檢查很容易讓系統發生錯誤。Visual Basic 6.0 提供 On Error 陳述式支援例外處理,但卻會破壞程式碼結構,因此又被稱為非結構化例外處理(Unstructured Exception Handling)。現代較新的物件導向程式語言,像 Java、C# 或 Visual Basic .NET 則提供了 try...catch...finally 陳述式,這是一個很棒的結構化例外處理(Structured Exception Handling) 機制,不僅可以有效簡化例外處理複雜度,還可維持程式碼可讀性。

然而一個問題來了,就像 C 語言的函式回傳值檢查一樣,呼叫端若沒有檢查函式回傳值,表示呼叫端程式碼不太可靠。同樣的假設換到 C#,若呼叫端未嘗試捕捉例外狀況呢?其實一樣不太可靠。因此使用支援結構化例外處理的程式語言是一回事,養成良好的程式碼撰寫習慣才是最重要的,也是一位優秀程式設計師的基本涵養。

例外狀況(Exception),顧名思義,表示在正常狀況下所不會發生的狀況,因為非正常狀況,故系統必須特別處理以避免不可挽救的錯誤發生。例外處理並非全是程式設計師的責任,事實上在系統分析階段就已經開始處理這件工作了。在 UML 中可利用使用案例描述(Use Case Description) 來說明對於例外狀況的處理方式,這段描述通常是撰寫在例外情境(Exception Scenario)

程式語言對於例外處理的方式是先利用 try block 將一段可能發生例外的程式碼包裝起來,隨後用 catch block 捕捉各種可能發生的例外狀況,finally block 則是不論例外發生與否都會進行的流程。大家很容易忽略在 catch block 中很重要的一件事是要捕捉的例外狀況是什麼?捕捉到了又要做什麼

一段 try...catch...finally 陳述式中可以有多個 catch block 是眾所皆知的事,在 Java 或 C# 中已經定義了一些例外繼承體系,可以讓程式設計師從較特殊的例外逐層捕捉到較一般性的例外。大家也都知道 Exception class 是所有例外的基底類別(Base class),只要捕捉 Exception class 就不會遺漏任何例外狀況,結果卻造成許多程式設計師只去捕捉 Exception class,這是一個很不好的示範。

許多系統忽略應該要定義屬於自己的例外繼承體系,而只是使用 Framework 已定義的例外,事實上這是不夠的。以 .NET Framework 為例,ApplictionException class 是由使用者自訂例外的基底類別,系統可以依它為基礎再去擴充例外[1]。至於為何要自訂例外狀況繼承體系?原因很簡單,為了分辨不同的例外。好處是讓系統知道發生什麼例外該有什麼處理方式,這就是例外處理要做的事,而這樣也才足夠實作使用案例描述中的例外情境。我認為一個品質良好的 Framework 都應當定義屬於自己的例外繼承體系,一個品質良好的系統也是一樣。即使不做系統一樣能賣一樣能執行,但肯定常聽到使用者抱怨系統不穩、常常發生奇怪的錯誤、錯誤訊息都看不懂…諸如此類的抱怨。

 
 

圖一、.NET Framework 部份例外狀況繼承體系

好,假如我們有心要自定例外狀況繼承體系,那麼該怎麼定義?又該定義哪些?答案其實也不難,主要跟系統要解決的問題領域(Question Domain) 有很深的關係。舉個簡單的例子,圖一中 IOException class 是 .NET Framework 在解決資料輸出入問題時的例外狀況基底類別,它有一個子類別 EndOfStreamException 則是表示資料串流已經讀取結束了,程式又嘗試讀取所造成的例外。另外像是 DBException abstract class 是在資料庫領域中的例外狀況基底類別,SqlException 則是存取 SQL Server 資料庫的例外狀況基底類別。

由以上可推論一個問題領域需要定義一個例外狀況基底類別用來識別屬於該領域的例外狀況,而這有什麼好處?如果我們提供的是一群系統的整合解決方案,好處就很明顯了。如圖二,我們可以很快地知道哪個系統出了問題,如果技術上可行且有必要的話,甚至能自動發通知給系統廠商。像網路購物系統這樣全球 7day-24hr 一直有人在使用的系統就非常重要,若金流系統無法運作可能就在一夕間流失了大批訂單。

 
 

圖二、系統與系統例外基底類別

更進一步看,圖三簡單表示了各系統的例外狀況繼承體系,這是由各系統依據自己的問題領域所分析定義出來的例外狀況。我們可以發現這些例外狀況在各個問題領域內是很基本該考量的問題,只是看有沒有特別被定義出來而已。事實上系統分析師有很重要的責任去發現問題領域中的例外狀況,否則程式設計師是很難知道這些例外狀況的。而且系統分析師除發現例外狀況外,還必須撰寫在使用案例描述的例外情節中,否則程式設計師也不知該如何處理例外。

 
 

圖三、各系統之例外狀況繼承體系

有一種例外狀況是屬於系統分析師不會知道的,但程式設計師會知道的,就屬資訊基礎建設的例外狀況。基本上我們可以假設 .NET Framework 中所定義的例外狀況系統分析師都不會知道,例如 EndOfStreamException,這種非常技術細節的例外狀況就需仰賴程式設計師來處理。所以說是不是只有例外情節的例外才需捕捉?可想而知當然不是,程式設計師也有責任去發現並處理例外。

還有,例外狀況其實還分可預期不可預期可矯正不可矯正這四種。可預期的例外狀況就是我們已經發現的,它會呈現在例外狀況繼承體系內;不可預期的則是完完全全意想不到的例外狀況,但可能會因為例外的發生而發現它是某一個可預期的例外狀況。可矯正的例外一定是可預期的例外,因為系統有辦法捕捉的例外狀況也只會出現在例外狀況繼承體系中;不可矯正的例外則可能是可預期或不可預期的。

這裡要特別定義矯正這個名詞,矯正是指在例外發生的當下,系統能提供另一種配套措失或重試,以滿足使用者的目的。以程式語言來說,catch 是用來捕捉例外,撰寫在 catch block 中的程式碼是處理例外,但不一定能矯正例外。如果 catch block 中只把例外再擲出(throw) 或紀錄(Logging) 而已,那不算矯正。請特別記住,要能滿足使用者的目的才算是矯正。

更多參考:

註解:

  1. 依 .NET Framework 4.0 的建議:如果您所設計的應用程式需要建立自己的例外狀況,建議您從 Exception 類別衍生自訂例外狀況。我們原本認為自訂例外狀況應該衍生自 ApplicationException 類別;但是在實務上這樣做並無顯著的增值成效。雖然如此,但仍不影響系統應該自訂例外狀況繼承體系這件事。