<!----><!----><!----> <!---->
異常處理措施
——針對有效的錯誤處理設計異常管理系統
作者: Jean-Pierre Norguet , JavaWorld.com,11/15/07
<!----> 1. ??????????? <!----> 摘要
在面向對象的應用程序中,由于代碼重載、錯誤的問題處理方式,導致異常有越來越多的趨勢。在這篇文章中,作者 Jean-Pierre Norguet 介紹了如何設計異常,來實現一個簡單的、可讀的、健壯的、靈活的、面向調試的及用戶友好的錯誤處理系統。在本文中,作者提出了簡單異常集合的設計,并且給出了 Java 實現的源代碼。最后,作者介紹了如何將這樣的設計集成到一個 Java 的企業應用程序中。
在一個面向對象的項目中,設計異常處理的最好途徑從來也沒有如我們期待的那樣清晰。在舊的大的系統中,異常的發生有激增的趨勢,最終達到幾百行的代碼。對于一些常見的編程場景,異常檢查是必須的,但是也會帶來可觀的處理開銷。雖然安靜的異常捕獲可以找到問題的源頭,但是避免不了它的一些缺點(雖然這些缺點不是致命的);比如你必須熟悉這些代碼。
<!----> 2. ??????????? <!----> 序
本文將介紹如何通過有限的異常集合來滿足一個錯誤處理的需求。在建立起一個好的錯誤處理系統框架之后,將會指出異常處理設計中常見的錯誤,這些錯誤將會逐漸損害應用程序的性能;然后將會給出一個異常集合的例子,這個例子支持本文討論的異常處理設計的基本功能:異常系統應該被設計成能夠幫助外部系統(用戶)處理未知情況(在運行時發生),而不是設計成能夠幫助編程人員處理已知情況;還會介紹本文給出例子中的各個類的含義以及如何在一個特定的 Java 企業應用程序體系中使用它們;最后給出這個例子的 Java 實現。
<!----> 3. ??????????? <!----> 錯誤處理需求
什么是一個好的錯誤處理系統?拋開審美角度的考慮,一個好的錯誤處理系統通常要符合下面的條件:
·任何異常都不會導致應用系統的崩潰。
·在發生異常時,允許應用程序進行相應的處理。
·顯示給用戶的錯誤信息要清晰的描述發生了什么錯誤以及應該采取什么樣的處理。
·如果需要輔助信息,錯誤信息還要幫助用戶與幫助部門交互,為幫助部門團隊提供必要的信息,是他們能夠快速的容易的重現錯誤。
·日志信息能為開發團隊人員在識別錯誤、在應用程序代碼中定位錯誤產生的位置以及修正錯誤提供必要的信息。
·錯誤處理代碼不會降低應用程序代碼的可讀性。必要的時候,錯誤處理僅僅是一個安全網,它對應用程序的核心功能具有較低的反問權限。
一個錯誤處理系統的設計符合這些條件才能被認為是完整的。對于大多數 Java 開發人員來說接下來的問題就是:如何靈活的使用異常類來設計一個錯誤處理系統,而不是通過簡單的重載它們來實現。
<!----> 3.1. ??? <!----> 應該避免的常見用法
可以通過一個有限的異常類集合來滿足上面提到的需求。當設計這樣的一個異常類集合是,你應該避免一些常見的用法,例如:
·對每個問題都定義的異常類,這樣會導致系統中異常類的激增。
·對每個包定義一個異常類集合,這沒什么用處而且也會導致系統中異常類的激增。
·對每個異常都提供 checked 和 non-checked 兩個版本,引入了檢測異常開銷。異常語義的后序副本也會混淆異常處理的設計。
·最后,拋出和捕獲通用異常在很多方面也是錯誤傾向。
本文下面提到的異常類集合按照錯誤處理語義分類,避免了異常處理相關的常見問題。尤其是在應用程序的規模和復雜程度增長是,這種方式更值得推薦。
<!----> 4. ??????????? <!----> 異常處理設計
圖 1 展示了異常類集合的設計,這個設計避免了異常類激增、檢測異常開銷和異常的安靜捕獲。
<!----><!---->
<!---->
圖 1. 一個異常類集合的例子
在這個圖中你會發現有些異常是 checked ,而有些是 runtime 。為了避免異常拋出聲明的開銷, checked 異常基于下面兩個目的被保留下來:
<!----> 1、 ? <!----> 告訴調用方法,在這個處理過程中發生了一個可預見的意外情況。在這種情況下,問題的語義已經被處理方法定義了,直接捕獲會更好。
<!----> 2、 ? <!----> 告訴外部系統發生了一個未解決的問題,需要根據異常語義來處理這個問題。
<!----> 4.1. ??? <!----> 理解異常語義
在這里闡明本文對異常語義的定義。在作者看來,設計對象的類需要根據它們在現實世界中的等價物。一個水果或者一個人很容易設計成一個 Java 對象類。但是異常設計卻不同:因為一個水果或者一個人在現實世界中非常直觀,異常卻不同。實際上,上面的兩類異常中,只有第一類在現實世界中存在;而第二類異常模擬了系統執行過程中會發生什么錯誤,因此在系統之外就不存在了。直接捕獲因此也只對第一類異常適合。
作者的設計建議就是異常應該根據它們的目的來設計。系統內部的、自我調整的異常就意味著是幫助系統來處理不可預見的情況。這些異常的目的也就不是模擬系統中的問題,而是給一個系統需要采取什么措施的指示。
<!----> 5. ??????????? <!----> 一個異常類集合的例子
在圖 1 中你可以看到四類異常對應四類處理,如下:
<!----> 1、 ? <!----> BusinessException: 一種異常情況發生。這種情況是可預見的,也可以被調用方法檢測到并立即采取措施。
<!----> 2、 ? <!----> ParameterException: 輸入的數據對處理過程不合法。用戶被要求重新輸入有效數據或者修改處理過程的條件。
<!----> 3、 ? <!----> TechnicalException: 技術問題,如無效的 SQL 語句。這種情況下,請求操作未完成。用戶需要和幫助部門聯系,調查問題的原因;或者嘗試其它的服務。對使用系統的其它用戶沒有影響。
<!----> 4、 ? <!----> CriticalTechnicalException: 技術問題,如數據庫崩潰。在這種情況下整個應用程序都 ub 可用。用戶被建議稍后重試。在問題修復前,所有的用戶都不能使用系統。
這個異常類集合只是一個例子;很多異常類集合都可以參照它來定義。例如, TechnicalException 和 CriticalTechnicalException 可以被設計成一個類,這個類聲明一個 severity 布爾屬性。重要的是關注采取什么處理措施而不是什么問題引起異常。
<!----> 5.1. ??? <!----> 異常日志記錄
雖然異常語義關注采取的措施,但是出現的問題也很重要。例如,開發團隊人員可以使用這些信息調試代碼。在異常處理設計中,導致異常的信息能夠在以用程序的錯誤日志中發現。在適當的位置使用一個好的日志記錄框架,這個框架能夠有效的從異常信息和堆棧跟蹤中記錄問題信息。
剩下的唯一問題就是怎么樣設計異常類使之能夠方便的返回信息。一個解決方案就是為異常類聲明一個 id 屬性來代表遇到的問題的種類。另外,問題自身可帶有通用異常,也就是把通用異常內嵌到應用程序異常中。在捕獲的時候,原始的信息和堆棧跟蹤信息能夠通過內嵌的異常得到。 id 屬性和內嵌異常是包裝問題的兩種途徑。
<!----> 6. ??????????? <!----> 異常處理流程的設計
一旦你已經設計好異常類本身了,下一步需要思考的就是它在應用程序中的處理流程。一個標準的 JEE 應用程序體系通常包括四個部分:展現、業務、集成、持久。異常經常在集成和持久部分被拋出。在業務部分,里層的捕獲 checked 異常,而外層捕獲 runtime 異常并根據它們的類型來采取相應的處理措施。也可以在業務部分拋出一些 checked 異常并且捕獲它們。在這種模式下,集成和持久部分,包括業務部分的里層都將 runtime 異常轉化成具體的處理措施。圖 2 展示的就是一個典型的 JEE 應用程序異常處理流程。
<!----><!---->
<!---->
圖 2. 標準 JEE 包體系中異常的處理流程
異常拋出路徑是指從持久部分(假設)發生問題到問題被解決所經歷的流程。如果持久層的調用方法能夠解決這個問題,那么這個異常就直接被捕獲并采取相應的處理措施,業務流程一切照常;如果問題不能被解決,異常將內嵌到一個 runtime 異常中經過業務部分的中間層傳遞到應用程序的上層中,在這里,典型的處理方法就是使用一些應用程序控制器來捕獲這些 runtime 異常并采取相應的處理措施,展現層顯示相應的錯誤信息給用戶。直接捕獲 checked 異常和推遲捕獲 runtime 異常是異常處理設計中的兩種主要方案,如圖 3 所示。
<!----><!---->
<!---->
圖 3. 直接捕獲 checked 異常和推遲捕獲 runtime 異常
<!----> 7. ??????????? <!----> 擴展 java.lang.Exception
文中提到的異常處理設計方案在任何的面向對象語言中都可以很容易的實現,包括 Java 。一個相似的異常類樹已經在標準 Java 庫中提供了。在這個庫中異常被設計為 java.lang.Throwable , ckeched 異常被設計為 java.lang.Exception , runtime 異常被設計為 java.lang.RuntimeException 。
在 java.lang.Exception 下,有大量泛語義的業務異常。運行時應用程序異常,如 ParamterException 、 TechnicalException 、 CriticalTechnicalException (見圖 1 )各自都設計成相應的概要異常,如 IllegalArgumentException 、 MissingResourceException 、 IllegalStateException 。
在應用程序中,重用 Java 標準異常是一個不錯的主意,但是由 Java 標準類拋出的異常也會導致一些混亂。你可以通過擴展 java.lang.Exception 的自定義異常類樹來避免這樣的混亂。通過自定義的異常類樹,你還可以實現內嵌異常和問題 ID 。列表 1 給出了文中例子的 Java 代碼實現。注意,它包括內嵌異常和問題 ID 。
列表 1. 通過 Java 代碼實現內嵌異常和問題 ID
?
public class NestedException extends RuntimeException { protected Exception nestedException; protected int issueId; public NestedException(String msg, Exception e, int id) { super(msg); this.nestedException = e; this.issueId = id; } public Exception getNestedException() { return this.nestedException; } public int getIssue() { return this.issueId; } } public interface Issue { public final static int UNDEFINED = 0; public final static int EXTERNAL_SERVICE_1_DOWN = 1; public final static int EXTERNAL_SERVICE_2_DOWN = 2; public final static int SQL_STATEMENT_ERROR = 3; // ... }?
<!----> 8. ??????????? <!----> 總結
設計一個滿足好的錯誤處理系統需求的異常類樹非常簡單。簡單的秘訣就是將設計的主要精力集中在系統應該采取什么樣的處理措施,而不是關注會出現的什么樣的問題。在本文的設計當中,問題的信息封裝在異常類里面。通過在處理措施和問題之間分配異常語義,讓你將異常類樹限制在一個有限的異常類集合中(可能就六七個左右)。這種設計不僅限制了異常類激增,洱海保證代碼的可讀性,使你在以后的開發中以最佳的清晰度來關注應用程序的業務邏輯的編碼。
<!----> 9. ??????????? <!----> 作者簡介
Jean-Pierre Norguet 擁有布魯塞爾大學的計算機科學和網絡工程博士學位。在 IBM 經過了三年全職的電子商務應用程序關鍵任務的 Java 開發后,作為團隊領導者和教練,他擅長的技術領域包括了整個應用程序開發周期。目前專注于 Java 技術、實體集成和迷你 Web 應用的研究。除了一些在線 Java 文章外, Norguet 博士還與 Prentice 、 IBM 出版社一起出版了有關 JEE 的著作,同時還在 ACM 、 IEEE 和 Springer-Verlag 的國際研討會發表了一些文章。他的業務興趣還包括藝術繪畫、兼職法語教師和臨時健康顧問。
<!----> 10. ????????? <!----> 相關資源
本文中提到的異常類集合的完整代碼可在 custom exception set 下載。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
