try-catch 幾乎是每天必碰的程式碼,新手程式設計師應該很容易瞭解 try-catch 該怎麼使用,但不見得能深入思考「為什麼」要有 例外狀況(Exception) 的存在,存在的理由很多,但我個人認為最重要也最單純的理由是『例外狀況試圖不讓你的程式繼續執行下去』,這聽起來像是個廢話,但或許有人沒有認真的想過這個問題,而這樣的一個理由在面對日常程式開發上又有什麼重大的影響呢?
在程序導向的程式開發架構下,錯誤處理都是靠 if-then-else 等於法,有時後會讓程式又臭又長變的難以維護也難以閱讀,到了物件導向程式設計的世界裡,有時也會難逃這種狀況,畢竟對人腦的思路來說「物件導向」一直都是「不人性化」的,我們學習物件導向技術就是為了克服人性,尋求一個更有結構、更有效率、更易於管理維護的解決方案。
在不熟悉物件導向程式設計時,通常都會用好多層的 if-then-else 處理各種例外狀況,為的不就是『當錯誤發生時不要執行到自己覺得不應該執行的程式碼』嗎?如果你今天寫程式的過程中,研判物件執行到某種狀態「有可能」造成程式發生不穩定的情況時,你就應該不要再繼續執行下去,否則一錯再錯的情況下,就會有更多狀況不可遇測了,這時你可以單純的 return value (也許是 null 之類的),或你也可以丟出一個例外狀況(Exception)。
丟出例外狀況與單純的 return value 有些不太一樣的地方,也就在於「例外狀況」可以包含比原本從方法(method) 或 函式(function) 「回傳資料」時有更多的資訊可以傳遞給呼叫者(Caller),這也是使用「例外狀況」最大的好處。
相對的 Exception 最大的壞處也是相同的地方,因為越多的資訊傳遞帶來的卻是記憶體耗用、CPU 運算資源耗用等負面影響,所以當一個物件「有可能」頻繁的發生 Exception 時,這時就必須考慮運用其他的方式或設計範式(Design Pattern)來處理例外狀況帶來的效能問題,建議可以參考閱讀 MSDN 文章的【例外狀況和效能 / Exceptions and Performance】章節。
舉一個最常見的例子:Int32 ( int )
在 .NET 1.1 版的 Int32 成員中有個常用的 靜態方法(method) 叫 Int32.Parse 方法,這個方法相信大家也很常用,他可以傳入一個字串(System.String),解析後轉換成 Int32 型別,當轉換失敗時就會丟出一個 FormatException,但字串的型態何其多,很容易就會傳入「非數字」的字串,這時就「有可能」造成頻繁的發生例外狀況並造成效能衝擊。
從 .NET 2.0 開始,Int32 成員就多了一個 Int32.TryParse 靜態方法,主要的目的就是讓呼叫者不會頻繁的接收到過多且不需要的例外狀況,也徹底解決例外狀況帶來的效能問題。
前面都是講例外狀況的基本概念,終於要進入正題了:自訂例外狀況
在設計自訂例外狀況時建議可參考 .NET Framework 開發人員手冊 的 設計自訂例外狀況 章節,依照上述的方針進行設計有助於您寫出正確且有用的例外型別。
標準的自訂例外型別範本如下 ( 參照自 MSDN 的 設計自訂例外狀況 章節 ):
public class NewException : BaseException, ISerializable {
public NewException() {
// Add implementation.
}
public NewException(string message) {
// Add implementation.
}
public NewException(string message, Exception inner) {
// Add implementation.
}
// This constructor is needed for serialization.
protected NewException(SerializationInfo info, StreamingContext context) {
// Add implementation.
}
}
我另外提供一個我自己寫過的自訂例外型別範例,這是一個當「使用者不存在」時所使用的例外狀況,當登入作業無法完成時,基本上不能再讓程式執行下去,所以我就丟出一個例外中斷程式執行。但事實上我在使用時並非讓應用程式因為例外狀況而自動關閉,而是用 try-catch 取得完整的例外資訊後顯示適當的錯誤訊息,範例程式如下:
class UserNotFoundException : Exception, ISerializable
{
public UserNotFoundException()
: base("使用者不存在") { }
public UserNotFoundException(string message)
: base(message) { }
public UserNotFoundException(string message, Exception inner)
: base(message, inner) { }
protected UserNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
在第一個建構子帶入一個預設的錯誤訊息,也就是當執行到下列程式時,由呼叫者取得的例外資訊就會包含這個例外型別提供的預設錯誤訊息:
throw new UserNotFoundException();
實際上 Exception 型別可以攜帶的資料還有更多,在 Exception 成員就可以看到其他內建的屬性,像是 HelpLink 可以儲存這個例外狀況相關聯的連結,Source 可以儲存造成錯誤的應用程式或物件名稱,InnerException 可以儲存造成這個例外的例外物件,Data 屬性型別為 IDictionary,可以儲存任意任何提供關於此例外狀況的額外使用者定義資訊。
例外狀況有這麼多的資訊可以傳遞,相信在程式開發上會有非常大的幫助,但也要小心隨之而來的效能衝擊,只要妥善掌握例外狀況開發的原則相信能夠寫出較合理的例外狀況處理程式。
相關連結