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

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