中贏網,有我中國贏!
IT頻道
當前位置: 中贏網 » IT頻道 » 編程開發

設計異常管理系統

2009-06-30 作者:Jean-Pierre Norguet 來源:http://www.uml.org.cn/zjjs/200906301.asp

在面向對象的應用程序中,由於代碼重載、錯誤的問題處理方式,導致異常有越來越多的趨勢。在這篇文章中,作者Jean-Pierre Norguet介紹瞭如何設計異常,來實現一個簡單的、可讀的、健壯的、靈活的、面向調試的及用戶友好的錯誤處理系統。在本文中,作者提出了簡單異常集合的設計,並且給出了Java實現的源代碼。最後,作者介紹瞭如何將這樣的設計集成到一個Java的企業應用程序中。

在一個面向對象的項目中,設計異常處理的最好途徑從來也沒有如我們期待的那樣清晰。在舊的大的系統中,異常的發生有激增的趨勢,最終達到幾百行的代碼。對於一些常見的編程場景,異常檢查是必須的,但是也會帶來可觀的處理開銷。雖然安靜的異常捕獲可以找到問題的源頭,但是避免不了它的一些缺點(雖然這些缺點不是致命的);比如你必須熟悉這些代碼。

本文將介紹如何通過有限的異常集合來滿足一個錯誤處理的需求。在建立起一個好的錯誤處理系統框架之後,將會指出異常處理設計中常見的錯誤,這些錯誤將會逐漸損害應用程序的性能;然後將會給出一個異常集合的例子,這個例子支持本文討論的異常處理設計的基本功能:異常系統應該被設計成能夠幫助外部系統(用戶)處理未知情況(在運行時發生),而不是設計成能夠幫助編程人員處理已知情況;還會介紹本文給出例子中的各個類的含義以及如何在一個特定的Java企業應用程序體系中使用它們;最後給出這個例子的Java實現。

錯誤處理需求

什麼是一個好的錯誤處理系統?拋開審美角度的考慮,一個好的錯誤處理系統通常要符合下面的條件:

1、任何異常都不會導致應用系統的崩潰。

2、在發生異常時,允許應用程序進行相應的處理。

3、顯示給用戶的錯誤信息要清晰的描述發生了什麼錯誤以及應該採取什麼樣的處理。

4、如果需要輔助信息,錯誤信息還要幫助用戶與幫助部門交互,爲幫助部門團隊提供必要的信息, 使他們能夠快速的容易的重現錯誤。

5、日誌信息能爲開發團隊人員在識別錯誤、在應用程序代碼中定位錯誤產生的位置以及修正錯誤提供必要的信息。

6、錯誤處理代碼不會降低應用程序代碼的可讀性。必要的時候,錯誤處理僅僅是一個安全網,它對應用程序的核心功能具有較低的訪問權限。

一個錯誤處理系統的設計符合這些條件才能被認爲是完整的。對於大多數Java開發人員來說接下來的問題就是:如何靈活的使用異常類來設計一個錯誤處理系統,而不是通過簡單的重載它們來實現。

應該避免的常見用法

可以通過一個有限的異常類集合來滿足上面提到的需求。當設計這樣的一個異常類集合時,你應該避免一些常見的用法,例如:

1、對每個問題都定義的異常類,這樣會導致系統中異常類的激增。

2、對每個包定義一個異常類集合,這沒什麼用處而且也會導致系統中異常類的激增。

3、對每個異常都提供checked和non-checked兩個版本,引入了檢測異常開銷。異常語義的後續副本也會混淆異常處理的設計。

4、最後,拋出和捕獲通用異常在很多方面也是錯誤傾向。

本文下面提到的異常類集合按照錯誤處理語義分類,避免了異常處理相關的常見問題。尤其是在應用程序的規模和複雜程度增長是,這種方式更值得推薦。

異常處理設計

圖1展示了異常類集合的設計,這個設計避免了異常類激增、檢測異常開銷和異常的安靜捕獲。

設計異常管理系統

圖1.一個異常類集合的例子

在這個圖中你會發現有些異常是checked,而有些是runtime。爲了避免異常拋出聲明的開銷,checked異常基於下面兩個目的被保留下來:

  • 告訴調用方法,在這個處理過程中發生了一個可預見的意外情況。在這種情況下,問題的語義已經被處理方法定義了,直接捕獲會更好。
  • 告訴外部系統發生了一個未解決的問題,需要根據異常語義來處理這個問題。

理解異常語義

在這裏闡明本文對異常語義的定義。在作者看來,設計對象的類需要根據它們在現實世界中的等價物。一個水果或者一個人很容易設計成一個Java對象類。但是異常設計卻不同:因爲一個水果或者一個人在現實世界中非常直觀,異常卻不同。實際上,上面的兩類異常中,只有第一類在現實世界中存在;而第二類異常模擬了系統執行過程中會發生什麼錯誤,因此在系統之外就不存在了。直接捕獲因此也只對第一類異常適合。

作者的設計建議就是異常應該根據它們的目的來設計。系統內部的、自我調整的異常就意味着是幫助系統來處理不可預見的情況。這些異常的目的也就不是模擬系統中的問題,而是給一個系統需要採取什麼措施的指示。

一個異常類集合的例子

在圖1中你可以看到四類異常對應四類處理,如下:

  • BusinessException:一種異常情況發生。這種情況是可預見的,也可以被調用方法檢測到並立即採取措施。
  • ParameterException:輸入的數據對處理過程不合法。用戶被要求重新輸入有效數據或者修改處理過程的條件。
  • TechnicalException:技術問題,如無效的SQL語句。這種情況下,請求操作未完成。用戶需要和幫助部門聯繫,調查問題的原因;或者嘗試其它的服務。對使用系統的其它用戶沒有影響。
  • CriticalTechnicalException:技術問題,如數據庫崩潰。用戶被建議稍後重試。在問題修復前,所有的用戶都不能使用系統。
  • 這個異常類集合只是一個例子;很多異常類集合都可以參照它來定義。例如,TechnicalException和CriticalTechnicalException可以被設計成一個類,這個類聲明一個severity布爾屬性。重要的是關注採取什麼處理措施而不是什麼問題引起異常。

    異常日誌記錄

    雖然異常語義關注採取的措施,但是出現的問題也很重要。例如,開發團隊人員可以使用這些信息調試代碼。在異常處理設計中,導致異常的信息能夠在以用程序的錯誤日誌中發現。在適當的位置使用一個好的日誌記錄框架,這個框架能夠有效的從異常信息和堆棧跟蹤中記錄問題信息。

    剩下的唯一問題就是怎麼樣設計異常類使之能夠方便的返回信息。一個解決方案就是爲異常類聲明一個id屬性來代表遇到的問題的種類。另外,問題自身可帶有通用異常,也就是把通用異常內嵌到應用程序異常中。在捕獲的時候,原始的信息和堆棧跟蹤信息能夠通過內嵌的異常得到。id屬性和內嵌異常是包裝問題的兩種途徑。

    異常處理流程的設計

    一旦你已經設計好異常類本身了,下一步需要思考的就是它在應用程序中的處理流程。一個標準的JEE應用程序體系通常包括四個部分:展現、業務、集成、持久。異常經常在集成和持久部分被拋出。在業務部分,裏層的捕獲checked異常,而外層捕獲runtime異常並根據它們的類型來採取相應的處理措施。也可以在業務部分拋出一些checked異常並且捕獲它們。在這種模式下,集成和持久部分,包括業務部分的裏層都將runtime異常轉化成具體的處理措施。圖2展示的就是一個典型的JEE應用程序異常處理流程。

    設計異常管理系統

    圖2.標準JEE包體系中異常的處理流程

    異常拋出路徑是指從持久部分(假設)發生問題到問題被解決所經歷的流程。如果持久層的調用方法能夠解決這個問題,那麼這個異常就直接被捕獲並採取相應的處理措施,業務流程一切照常;如果問題不能被解決,異常將內嵌到一個runtime異常中經過業務部分的中間層傳遞到應用程序的上層中,在這裏,典型的處理方法就是使用一些應用程序控制器來捕獲這些runtime異常並採取相應的處理措施,展現層顯示相應的錯誤信息給用戶。直接捕獲checked異常和推遲捕獲runtime異常是異常處理設計中的兩種主要方案,如圖3所示。

    設計異常管理系統

    圖3.直接捕獲checked異常和推遲捕獲runtime異常

    擴展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;

    // ...

    }

    總結

    設計一個滿足好的錯誤處理系統需求的異常類樹非常簡單。簡單的秘訣就是將設計的主要精力集中在系統應該採取什麼樣的處理措施,而不是關注會出現的什麼樣的問題。在本文的設計當中,問題的信息封裝在異常類裏面。通過在處理措施和問題之間分配異常語義,讓你將異常類樹限制在一個有限的異常類集閤中(可能就六七個左右)。這種設計不僅限制了異常類激增,保證代碼的可讀性,使你在以後的開發中以最佳的清晰度來關注應用程序的業務邏輯的編碼。

    聲明:中贏網登載此文出於傳遞更多信息之目的,並不意味着贊同其觀點或證實其描述,文章內容僅供參考。