电竞比分网-中国电竞赛事及体育赛事平台

分享

Effective Java Exceptions && 高效的Java異常處理

 燮羽 2011-01-09
Java開(kāi)發(fā)人員可以做出的最重要的架構(gòu)性決策之一就是如何使用Java異常模型。Java異常一直以來(lái)就是社群中許多爭(zhēng)議的靶子。有人爭(zhēng)論到,在Java語(yǔ)言中的異常檢查已是一場(chǎng)失敗的試驗(yàn)。本文將辨析,失敗的原因不在于Java異常模型,而在于Java類(lèi)庫(kù)的設(shè)計(jì)者未能充分了解到方法失敗的兩個(gè)基本原因。

本文倡導(dǎo)一種對(duì)異常條件本質(zhì)的思考方式,并描述一些有助于設(shè)計(jì)的模式。最后,本文還將在AOP模型中,作為相互滲透的問(wèn)題,來(lái)討論異常的處理。當(dāng)你能正確使用異常時(shí),它們會(huì)有極大的好處。本文將幫助你做到這一點(diǎn)。

為何異常是如此重要


Java應(yīng)用中的異常處理在很大程度上揭示了其所基于架構(gòu)的強(qiáng)度。架構(gòu)是在應(yīng)用程序各個(gè)層次上所做出并遵循的決定。其中最重要的一個(gè)就是決定應(yīng)用程序中的類(lèi),亞系統(tǒng),或?qū)又g溝通的方式。Java異常是Java方法將另類(lèi)執(zhí)行結(jié)果交流出去的方式,所以值得在應(yīng)用架構(gòu)中給予特殊關(guān)注。

一個(gè)衡量Java設(shè)計(jì)師水平和開(kāi)發(fā)團(tuán)隊(duì)紀(jì)律性的好方法就是讀讀他們應(yīng)用程序里的異常處理代碼。首先要注意的是有多少代碼用于捕獲異常,寫(xiě)進(jìn)日志文件,決定發(fā)生了什么,和在不同的異常間跳轉(zhuǎn)。干凈,簡(jiǎn)捷,關(guān)聯(lián)性強(qiáng)的異常處理通常表明開(kāi)發(fā)團(tuán)隊(duì)有著穩(wěn)定的使用Java異常的方式。當(dāng)異常處理代碼的數(shù)量甚至要超過(guò)其他代碼時(shí),你可以看出團(tuán)隊(duì)之間的交流合作有很大的問(wèn)題(可能在一開(kāi)始就不存在),每個(gè)人都在用他們自己的方式來(lái)處理異常。

對(duì)突發(fā)異常的處理結(jié)果是可以預(yù)見(jiàn)的。如果你問(wèn)問(wèn)團(tuán)隊(duì)成員為什么異常會(huì)被拋出,捕獲,或在特定的一處代碼里忽視了異常的發(fā)生,他們的回答通常是,“我沒(méi)有別的可做”。如果你問(wèn)當(dāng)他們編寫(xiě)的異常真的發(fā)生了會(huì)怎么樣,他們會(huì)皺皺眉,你得到的回答類(lèi)似于這樣,“我不知道。我們從沒(méi)測(cè)試過(guò)。”

你可以從客戶端的代碼判斷一個(gè)java的組件是否有效利用了java的異常。如果它們包含著大堆的邏輯去弄清楚在何時(shí)一筆操作失敗了,為何失敗,是否有彌補(bǔ)的余地,那么原因很有可能要?dú)w咎于組件的報(bào)錯(cuò)設(shè)計(jì)。錯(cuò)誤的報(bào)錯(cuò)系統(tǒng)會(huì)在客戶端產(chǎn)生大量的“記錄然后忘掉”的代碼,這些代碼鮮有用途。最差的是弄擰的邏輯,嵌套的try/catch/finally代碼塊,和一些其他的混亂而導(dǎo)致脆弱而難于管理的應(yīng)用程序。

事后再來(lái)解決Java異常的問(wèn)題,或根本就不解決,是軟件項(xiàng)目產(chǎn)生混亂并導(dǎo)致滯后的主要原因。異常處理是一個(gè)在設(shè)計(jì)的各個(gè)部分都急需解決的問(wèn)題。對(duì)異常處理建立一個(gè)架構(gòu)性的約定是項(xiàng)目中首要做出的決定。合理使用Java異常模型對(duì)確保你的應(yīng)用簡(jiǎn)單,易維護(hù),和正確有著長(zhǎng)遠(yuǎn)的影響。


解析異常


正確使用Java異常模型所包含的內(nèi)容一直以來(lái)有著很大的爭(zhēng)議。Java不是第一種支持異常算法語(yǔ)義的;但是,它卻是第一種通過(guò)編譯器來(lái)執(zhí)行聲明和處理某些異常的規(guī)則的語(yǔ)言。許多人都認(rèn)為編譯時(shí)的異常檢查對(duì)精確的軟件設(shè)計(jì)頗有幫助。圖1顯示的Java異常的等級(jí)。

\\\\\\\\\
圖1:Java異常的等級(jí)

通常,Java編譯器強(qiáng)迫拋出基于java.lang.Throwable的異常的方法要在它聲明中的“throws”部分加上那個(gè)異常。而且,編譯器還會(huì)證實(shí)客戶端的方法或者捕獲已聲明的異常,或者特別聲明自己也拋出同樣的異常。這些簡(jiǎn)單的規(guī)則對(duì)世界范圍的Java程序員都有深遠(yuǎn)的影響。

編譯器放松了對(duì)Throwable繼承樹(shù)中兩個(gè)分支的異常檢查。java.long.Error和java.lang.RuntimeException的子類(lèi)免于編譯時(shí)的檢查。在這兩類(lèi)中,軟件工程師通常對(duì)運(yùn)行中異常更感興趣。“不檢查”的異常指的是這一組,以便和所有其它“檢查”的異常區(qū)別開(kāi)。

我可以想象那些接受“檢查”的異常的人,也會(huì)很看重Java的數(shù)據(jù)類(lèi)型。畢竟,編譯器對(duì)數(shù)據(jù)類(lèi)型施加的限制鼓勵(lì)嚴(yán)格的編碼和精確的思維。編譯時(shí)的類(lèi)型檢查對(duì)減少運(yùn)行時(shí)的嚴(yán)重問(wèn)題有幫助。編譯時(shí)的異常檢查也能起到類(lèi)似的作用,它會(huì)提醒開(kāi)發(fā)人員某個(gè)方法可能會(huì)有預(yù)想不到的結(jié)果需要處理好。

早期的建議是盡可能的使用“檢察的異常”,以此來(lái)最大限度的利用編譯器提供的幫助來(lái)寫(xiě)出無(wú)錯(cuò)誤的軟件。Java類(lèi)庫(kù)API的設(shè)計(jì)者們都認(rèn)同這一點(diǎn),他們廣泛地使用“檢察的異常”來(lái)模擬類(lèi)庫(kù)方法中幾乎所有的緊急應(yīng)變措施。在J2SE5.1 API規(guī)格中,“檢察的異常”類(lèi)型已2比1的比率超過(guò)了“未檢查的異常”類(lèi)型。

對(duì)程序員而言,看上去在Java類(lèi)庫(kù)中大多數(shù)的常用方法對(duì)每一個(gè)可能的失敗都聲明了“檢察的異常”。例如,java.io包
對(duì)IOException這個(gè)“檢察的異常”就有著很大的依賴。至少有63個(gè)Java類(lèi)庫(kù)包,或直接,或通過(guò)十幾個(gè)下面的子類(lèi),拋出這個(gè)異常。

I/O的失敗極其稀有,但是卻很?chē)?yán)重。而且,一旦發(fā)生,從你所寫(xiě)的代碼里基本上是無(wú)法補(bǔ)救的。Java程序員意識(shí)到他們不得不提供IOException或類(lèi)似的不可補(bǔ)救的事件,而一個(gè)簡(jiǎn)單的Java類(lèi)庫(kù)方法的調(diào)用就可能讓這些事件發(fā)生。捕獲這些異常給本來(lái)簡(jiǎn)單的代碼帶來(lái)了一定的晦澀,因?yàn)榧词乖诓东@的代碼塊里也基本上幫不上忙。但是不加以捕獲又可能更糟糕,因?yàn)榫幾g器要求你的方法必須要拋出那些異常。這樣你的實(shí)施細(xì)則就不得不暴露在外了,而通常好的面向?qū)ο蟮脑O(shè)計(jì)都是要隱藏細(xì)節(jié)的。

這樣一個(gè)不可能贏的局面導(dǎo)致了我們今天所警告的絕大多數(shù)臭名卓著的異常處理的顛覆性格局。同時(shí)也衍生了很多正確或錯(cuò)誤的補(bǔ)救之道。

一些Java界的知名人物開(kāi)始質(zhì)疑Java的“檢察的異常”的模型是否是一個(gè)失敗的試驗(yàn)。有一些東西肯定是失敗的,但是這和在Java語(yǔ)言里加入對(duì)異常的檢查是毫無(wú)關(guān)聯(lián)的。失敗是由于在Java API的設(shè)計(jì)者們的思維里,大多數(shù)失敗的情形是雷同的,所以可以通過(guò)同一種異常傳達(dá)出去。


故障和應(yīng)變


讓我們來(lái)考慮在一個(gè)假想的銀行應(yīng)用中的CheckingAccount類(lèi)。一個(gè)CheckingAcccount屬于一個(gè)用戶,記載著用戶的存款余額,也能接受存款,接受止兌的通知,和處理匯入的支票。一個(gè)CheckingAcccount對(duì)象必須協(xié)調(diào)同步線程的訪問(wèn),因?yàn)槿魏我粋€(gè)線程都可能改變它的狀態(tài)。CheckingAcccount類(lèi)里processCheck的方法會(huì)接受一個(gè)Check對(duì)象為參數(shù),通常從帳戶余額里減去支票的金額。但是一個(gè)管理支票清算的用戶端程序調(diào)用processCheck方法時(shí),必須有兩種可能的應(yīng)變措施。一,CheckingAccount對(duì)象里可能對(duì)該支票已有一個(gè)止付的命令;二,帳戶的余額可能不足已滿足支票的金額。

所以,processCheck的方法對(duì)來(lái)自客戶端的調(diào)用可以有3種方式回應(yīng)。正常的是處理好支票,并把方法簽名里聲明的結(jié)果返回給調(diào)用方。兩種應(yīng)變的回應(yīng)則是需要與支票清算端溝通的在銀行領(lǐng)域?qū)崒?shí)在在存在的情況。processCheck方法所有3種返回結(jié)果都是按照典型的銀行支票帳戶的行為而精心設(shè)計(jì)的。

在Java里,一個(gè)自然的方法來(lái)表示上述緊急的應(yīng)變是定義兩種異常,比如StopPaymentException(止付異常)和InsufficientFundsException(余額不足異常)。一個(gè)客戶端如果忽略這些異常是不對(duì)的,因?yàn)檫@些異常在正常操作的情況下一定會(huì)被拋出。他們?nèi)缤椒ǖ暮灻粯臃从沉朔椒ǖ娜嫘袨椤?br>
客戶端可以很容易的處理好這兩種異常。如果對(duì)支票的兌付被停止了,客戶端把該支票交付特別處理。如果是因?yàn)橘Y金不足,用戶端可以從用戶的儲(chǔ)蓄帳戶里轉(zhuǎn)移一些資金到支票帳戶里,然后再試一次。

在使用CheckingAccount的API時(shí),這些應(yīng)變都是可以預(yù)計(jì)的和自然的結(jié)果。他們并不是意味著軟件或運(yùn)行環(huán)境的失敗。這些異常和由于CheckingAccount類(lèi)中一些內(nèi)部實(shí)施細(xì)則引起的真正失敗是不同的。

設(shè)想CheckingAccount對(duì)象在數(shù)據(jù)庫(kù)里保持著一個(gè)恒定的狀態(tài),并使用JDBC API來(lái)對(duì)之訪問(wèn)。在那個(gè)API里,幾乎所有的數(shù)據(jù)庫(kù)訪問(wèn)方法都有可能因?yàn)楹虲heckingAccount實(shí)施無(wú)關(guān)的原因而失敗。比如,有人可能忘了把數(shù)據(jù)庫(kù)服務(wù)器運(yùn)行起來(lái),一個(gè)未有連上的網(wǎng)絡(luò)數(shù)據(jù)線,訪問(wèn)數(shù)據(jù)庫(kù)的密碼改變了,等等。

JDBC依靠一種“檢查的異常”,SQLException,來(lái)匯報(bào)任何可能的錯(cuò)誤。可能出錯(cuò)的絕大多數(shù)原由都是數(shù)據(jù)庫(kù)的配置,連接,或其所在的硬件設(shè)施。對(duì)processCheck方法而言,它對(duì)上述錯(cuò)誤是無(wú)計(jì)可施的。這不應(yīng)該,因?yàn)閜rocessCheck至少了解它自己的實(shí)施細(xì)則。在調(diào)用棧里上游的方法能處理這些問(wèn)題的可能就更小。

CheckingAccount這個(gè)例子說(shuō)明了一個(gè)方法不能成功返回它想要的結(jié)果的兩個(gè)基本原因。這里是兩個(gè)描述性的術(shù)語(yǔ):

應(yīng)變

與實(shí)際預(yù)料相符,一個(gè)方法給出另外一種回應(yīng),而這種回應(yīng)可以表達(dá)成該方法所要達(dá)到的目的之一。這個(gè)方法的調(diào)用者預(yù)料到這個(gè)情況的出現(xiàn),并有相對(duì)的應(yīng)付之道。

故障
在未經(jīng)計(jì)劃的情況下,一個(gè)方法不能達(dá)到它的初衷,這是一個(gè)不訴諸該方法的實(shí)施細(xì)則就很難搞清的情況。

應(yīng)用這些術(shù)語(yǔ),對(duì)processCheck方法而言,一個(gè)止付的命令和一個(gè)超額的提取是兩種可能的應(yīng)變。而SQLException反映了可能的故障。processCheck方法的調(diào)用者應(yīng)該能夠提供應(yīng)變,但卻不一定能有效的處理好可能發(fā)生的故障。


Java異常的匹配


在建立應(yīng)用架構(gòu)中Java異常的規(guī)則時(shí),以應(yīng)變和故障的方式仔細(xì)考慮好“什么可能會(huì)出錯(cuò)”是有長(zhǎng)遠(yuǎn)意義的。

條件
應(yīng)變
故障
被考慮成
設(shè)計(jì)的一部分
一個(gè)糟糕的意外
預(yù)計(jì)到會(huì)發(fā)生
經(jīng)常發(fā)生
絕不發(fā)生
關(guān)注方
上游對(duì)該方法的調(diào)用者
需要修好這個(gè)問(wèn)題的人
舉例 另一種返回方式
程序bug,硬件系統(tǒng)故障,配置錯(cuò)誤,丟失的文件,服務(wù)器沒(méi)有運(yùn)行
最好的匹配
一個(gè)檢查的異常
一個(gè)未檢查的異常

應(yīng)變情況恰如其分地匹配給了Java檢查的異常。因?yàn)樗鼈兪欠椒ǖ恼Z(yǔ)義算法合同中不可缺少的一部分,在這里借助于編譯器的幫助來(lái)確保它們得到解決是很有道理的。如果你發(fā)現(xiàn)編譯器堅(jiān)持應(yīng)變的異常必須要處理或者在不方便的時(shí)候必須要聲明會(huì)給你帶來(lái)些麻煩,你在設(shè)計(jì)上幾乎肯定要做些重構(gòu)了。這其實(shí)是件好事。

出現(xiàn)故障的情況對(duì)開(kāi)發(fā)人員而言是蠻有意思的,但對(duì)軟件邏輯而言卻并非如此。那些軟件”消化問(wèn)題“的專(zhuān)家們需要關(guān)于故障的信息以便來(lái)解決問(wèn)題。因此,未檢查的異常是表示故障的很好方式。他們讓故障的通知原封不動(dòng)地從調(diào)用棧上所有的方法濾過(guò),到達(dá)一個(gè)專(zhuān)門(mén)來(lái)捕獲它們的地方,并得到它們自身包含的有利于診斷的信息,對(duì)整個(gè)事件提供一個(gè)有節(jié)制的優(yōu)雅的結(jié)論。產(chǎn)生故障的方法不需要來(lái)聲明(異常),上游的調(diào)用方法不需要捕獲它們,方法的實(shí)施細(xì)則被正確的隱藏起來(lái)- 以最低的代碼復(fù)雜度。

新一些的Java API,比如像Spring架構(gòu)和Java Data Ojects類(lèi)庫(kù)對(duì)檢查的異常幾乎沒(méi)有依賴。Hibernate ORM架構(gòu)在3.0版本里重新定義了一些關(guān)鍵功能來(lái)去除對(duì)檢查的異常的使用。這就意味著在這些架構(gòu)舉報(bào)的絕大部分異常都是不可恢復(fù)的,歸咎于錯(cuò)誤的方法調(diào)用代碼,或是類(lèi)似于數(shù)據(jù)庫(kù)服務(wù)器之類(lèi)的底層部件的失敗。特別的,強(qiáng)迫一個(gè)調(diào)用方來(lái)捕獲或聲明這些異常幾乎沒(méi)有任何好處。


設(shè)計(jì)里的故障處理

在你的計(jì)劃里,承認(rèn)你需要去做就邁好了有效處理好故障的第一步。對(duì)那些堅(jiān)信自己能寫(xiě)出無(wú)懈可擊的軟件的工程師們來(lái)說(shuō),承認(rèn)這一點(diǎn)是不容易的。這里是一些有幫助的思考方式。首先,如果錯(cuò)誤俯拾即是,應(yīng)用的開(kāi)發(fā)時(shí)間將很長(zhǎng),當(dāng)然前提是程序員自己的bug自己修理。第二,在Java類(lèi)庫(kù)中,過(guò)度使用檢查的異常來(lái)處理故障情形將迫使你的代碼要應(yīng)對(duì)好故障,即使你的調(diào)用次序完全正確。如果沒(méi)有一個(gè)故障處理的架構(gòu),湊合的異常處理將導(dǎo)致應(yīng)用中的信息丟失。

一個(gè)成功的故障處理架構(gòu)一定要達(dá)到下面的目標(biāo):
  • 減少代碼的復(fù)雜性
  • 捕獲和保存診斷性信息
  • 對(duì)合適的人提醒注意
  • 優(yōu)雅地退出行動(dòng)
故障是應(yīng)用的真實(shí)意圖的干擾。因此,用來(lái)處理它們的代碼應(yīng)盡量的少,理想上,把它們和應(yīng)用的語(yǔ)義算法部分隔離開(kāi)。故障的處理必須滿足那些負(fù)責(zé)改正它們的人的需要。開(kāi)發(fā)人員需要知道故障發(fā)生了,并得到能幫助他們搞清為何發(fā)生的信息。即使一個(gè)故障,在定義上而言,是不可補(bǔ)救的,好的故障處理會(huì)試著優(yōu)雅地結(jié)束引起故障的活動(dòng)。


對(duì)故障情況使用未檢查的異常


在做框架上的決定時(shí),用未檢查的異常來(lái)代表故障情況是有很多原因的。Java的運(yùn)行環(huán)境對(duì)代碼的錯(cuò)誤會(huì)拋出“運(yùn)行時(shí)異常”的子類(lèi),比如,ArithmeticException或ClassCastException。這為你的框架設(shè)了一個(gè)先例。未檢查的異常讓上游的調(diào)用方法不需要為和它們目的不相關(guān)的情況而添加代碼,從而減少了混亂。

你的故障處理策略應(yīng)該認(rèn)識(shí)到Java類(lèi)庫(kù)的方法和其他API可能會(huì)使用檢查的異常來(lái)代表對(duì)你的應(yīng)用而言只可能是故障的情況。在這種情形下,采用設(shè)計(jì)約定來(lái)捕獲API異常,將其以故障來(lái)看待,拋出一個(gè)未檢查的異常來(lái)指示故障的情況和捕獲診斷的信息。

在這種情況下拋出的特定異常類(lèi)型應(yīng)該由你的框架來(lái)定義。不要忘記一個(gè)故障異常的主要目的是傳遞記錄下來(lái)的診斷信息,以便讓人們來(lái)想出出錯(cuò)的原因。使用多個(gè)故障異常類(lèi)型可能有些過(guò),因?yàn)槟愕募軜?gòu)對(duì)它們都一視同仁。多數(shù)情況下,一條好的,描述性強(qiáng)的信息將單一的故障類(lèi)型嵌入就夠用了。使用Java基本的RuntimeException來(lái)代表故障情況是很容易的。截止到Java1.4,RuntimeException,和其他的拋出類(lèi)型一樣,都支持異常的嵌套,這樣你就可以捕獲和報(bào)出導(dǎo)向故障的檢查的異常。

你也許會(huì)為了故障報(bào)告的目的而定義你自己的未檢查的異常。這樣做可能是必要的,如果你使用Java1.3或更早的版本,它們都不支持異常的嵌套。實(shí)施一個(gè)類(lèi)似的嵌套功能來(lái)捕獲和轉(zhuǎn)換你應(yīng)用中構(gòu)成故障的檢查的異常是很簡(jiǎn)單的。你的應(yīng)用在報(bào)錯(cuò)時(shí)可能需要一個(gè)特殊的行為。這可能是你在架構(gòu)中創(chuàng)建RuntimeException子類(lèi)的另一個(gè)原因。


建立一個(gè)故障的屏障

對(duì)你的故障處理架構(gòu)而言,決定拋出什么樣的異常,何時(shí)拋出是重要的決定。同樣重要的是,何時(shí)來(lái)捕獲一個(gè)故障異常,之后再怎么辦。這里的目的是讓你應(yīng)用中的功能性部分不需要處理故障。把問(wèn)題分開(kāi)來(lái)處理通常都是一件好事情,有一個(gè)中央故障處理機(jī)制長(zhǎng)遠(yuǎn)來(lái)看是很有裨益的。

在故障屏障的模式里,任何應(yīng)用組件都可以拋出故障異常,但是只有作為“故障屏障”的組件才捕獲異常。采用此種模式去除了大多數(shù)程序員為了在本地處理故障而插入的復(fù)雜的代碼。故障屏障邏輯上位于調(diào)用棧的上層,這樣在一個(gè)默認(rèn)的行動(dòng)被激發(fā)前,一個(gè)異常向上舉報(bào)的行為就被阻止了。根據(jù)不同的應(yīng)用類(lèi)型,默認(rèn)的行動(dòng)所指也不同。對(duì)一個(gè)獨(dú)立的Java應(yīng)用而言,這個(gè)行動(dòng)指活著的線程被停止。對(duì)一個(gè)位于應(yīng)用服務(wù)器上的Web應(yīng)用而言,這個(gè)行動(dòng)指應(yīng)用服務(wù)器向?yàn)g覽器送出不友好的(甚至令人尷尬的)回應(yīng)。

一個(gè)故障屏障組件的第一要?jiǎng)?wù)就是記錄下故障異常中包含的信息以為將來(lái)所用。到現(xiàn)在為止,一個(gè)應(yīng)用日志是做成此事的首選。異常的嵌套的信息,棧日志,等等,都是對(duì)診斷有價(jià)值的信息。傳遞故障信息最差的地方是通過(guò)用戶界面。把應(yīng)用的使用者卷進(jìn)查錯(cuò)的進(jìn)程對(duì)你,對(duì)你的用戶而言都不好。如果你真的很想把診斷信息放上用戶界面,那可能意味著你的日志策略需要改進(jìn)。

故障屏障的下一個(gè)要?jiǎng)?wù)是以一種可控的方式來(lái)結(jié)束操作。這具體的意義要取決于你應(yīng)用的設(shè)計(jì),但通常包括產(chǎn)生一個(gè)可通用的回應(yīng)給可能正在等待的客戶端。如果你的應(yīng)用是一個(gè)Web service,這就意味著在回應(yīng)中用soap:Server的<faultcode>和通用的失敗信息<faultstring>來(lái)建立一個(gè)SOAP故障元素<fault>。如果你的應(yīng)用于瀏覽器交流,這個(gè)屏障就會(huì)安排好一個(gè)通用的HTML回應(yīng)來(lái)表明需求是不能被處理的。

在一個(gè)Struts的應(yīng)用里,你的故障屏障會(huì)以一種全局異常處理器的形式出現(xiàn),并被配置成處理RuntimeException的任何子類(lèi)。你的故障屏障類(lèi)將延伸org.apache.struts.action.ExceptionHandler類(lèi),必要的話,重寫(xiě)它的方法來(lái)實(shí)施用戶自己的特別處理。這樣就會(huì)處理好不小心產(chǎn)生的故障情況和在處理一個(gè)Struts動(dòng)作時(shí)發(fā)現(xiàn)的故障。圖2顯示的就是應(yīng)變和故障異常。

 


圖2 應(yīng)變和故障異常

如果你使用的是Spring MVC架構(gòu),你可以繼承SimpleMappingExceptionResolver類(lèi),并配置成處理RuntimeException和它的子類(lèi)們,這樣很容易的就建起了故障屏障。通過(guò)重寫(xiě)resolveException的方法,你可以在使用父類(lèi)的方法來(lái)把需求導(dǎo)引到一個(gè)發(fā)出通用錯(cuò)誤提示的view組件之前,加入你需要的用戶化的處理。

當(dāng)你的架構(gòu)包含了故障屏障,程序員都知曉了后,再寫(xiě)出一次性的故障異常的沖動(dòng)就會(huì)銳減。結(jié)果就是應(yīng)用中出現(xiàn)更干凈,更易于維護(hù)的代碼。


架構(gòu)中應(yīng)變的處理



將故障處理交與屏障后,主要組件間的應(yīng)變交流變得容易多了。一個(gè)應(yīng)變代表著與主要返回結(jié)果同等重要的另外一種方法結(jié)果。因此,檢查的異常類(lèi)型是一個(gè)能夠很好地傳遞應(yīng)變情況的存在并提供必要的信息來(lái)與它競(jìng)爭(zhēng)的工具。這個(gè)方式借助于Java編譯器的幫助來(lái)提醒程序員關(guān)于他們所用的API的方方面面以及提供全套的方法輸出的必要性。

僅僅使用方法的返回值類(lèi)型來(lái)傳遞簡(jiǎn)單的應(yīng)變是可能的。比如,返回一個(gè)空引用,而不是一個(gè)具體的對(duì)象,可以意味著對(duì)象由于一個(gè)已定義的原因不能被建立。Java I/O的方法通常返回一個(gè)整數(shù)值-1,而不是字節(jié)的值或字節(jié)的數(shù)來(lái)表示文件的結(jié)尾。如果你的方法的語(yǔ)義簡(jiǎn)單到可以允許的地步,另一種返回值的方法是可以使用的,因?yàn)樗饤壛水惓?lái)的額外的花銷(xiāo)。不足之處是方法的調(diào)用方要檢測(cè)一下返回的值來(lái)判斷是主要結(jié)果,還是應(yīng)變結(jié)果。但是,編譯器沒(méi)有辦法來(lái)保證方法調(diào)用者會(huì)使用這個(gè)判斷。

如果一個(gè)方法有一個(gè)void的返回類(lèi)型,異常是唯一的方法來(lái)表示應(yīng)變發(fā)生了。如果一個(gè)方法返回的是一個(gè)對(duì)象的引用,那么返回值只可能是空或非空(null and non-null)。如果一個(gè)方法返回一個(gè)整數(shù)型,選擇與主要返回值不沖突的,可以表示多種應(yīng)變情況的數(shù)值是可能的。但是這樣的話,我們就進(jìn)入了錯(cuò)誤代碼檢查的世界,而這正式Java異常模式所著力避免的。


提供一些有用的信息


定義不同的故障報(bào)告的異常類(lèi)型是沒(méi)什么道理的,因?yàn)楣收掀琳蠈?duì)所有異常類(lèi)型一視同仁。應(yīng)變異常就有很大的不同,因?yàn)樗鼈兊脑馐且蚍椒ㄕ{(diào)用者傳遞各種情況。你的架構(gòu)可能會(huì)指出這些異常應(yīng)該繼承java.lang.Exception或一個(gè)指定的基類(lèi)。

不要忘記你的異常應(yīng)該是百分百的Java類(lèi)型,你可以用它來(lái)存放為你的特殊目的服務(wù)的特殊字段,方法,甚至是構(gòu)造器。比如,被假想的processCheck()方法拋出的InsufficientFundsException這個(gè)異常類(lèi)型就應(yīng)該包含著一個(gè)OverdraftProtection的對(duì)象,它能夠從另外一個(gè)帳戶里把短缺的資金轉(zhuǎn)過(guò)來(lái)。


日志還是不要日志

記錄下故障異常是有用處的,因?yàn)槿罩镜哪康氖窃谝恍┬枰恼那闆r下,日志可以吸引人們的注意力。但對(duì)應(yīng)變異常而言卻并非如此。應(yīng)變異??赡艽淼闹皇菢O少數(shù)情況,但是在你的應(yīng)用里,每一個(gè)情況還是會(huì)發(fā)生的。它們意味著你的應(yīng)用正在如最初的設(shè)計(jì)般正常工作著。經(jīng)常把日志代碼加進(jìn)應(yīng)變的捕獲塊里會(huì)使你的代碼晦澀難懂,而又沒(méi)有實(shí)際的好處。如果一個(gè)應(yīng)變代表了一重要的事件,在拋出一個(gè)異常應(yīng)變來(lái)警醒調(diào)用者之前,產(chǎn)生一筆日志,記錄下這個(gè)事件可能會(huì)讓這個(gè)方法更好些。

異常的各個(gè)方面

在Aspect Oriented Programming(AOP)的術(shù)語(yǔ)里,故障和應(yīng)變的處理是互相滲透的問(wèn)題。比如,要實(shí)施故障屏障的模式,所有參與的類(lèi)必須遵循通用規(guī)格:
  • 故障屏障方法必須存活在遍歷參與類(lèi)的方法調(diào)用圖的最前端
  • 參與類(lèi)必須使用未檢查的異常來(lái)表示故障情況
  • 參與類(lèi)必須使用故障屏障期望得到的有針對(duì)性的未檢查的異常類(lèi)型
  • 參與類(lèi)必須捕獲并從低端方法中把在執(zhí)行情境下注定的故障轉(zhuǎn)換成檢查的異常
  • 參與類(lèi)不能干擾故障異常被傳遞到故障屏障的過(guò)程
這些問(wèn)題超越了那些本不相干的類(lèi)的邊界。結(jié)果就是少數(shù)零散的故障處理代碼,以及屏障類(lèi)和參與類(lèi)間暗含的耦合(這已經(jīng)比不使用模式進(jìn)步多了?。?。AOP讓故障處理的問(wèn)題被封裝在通用的可以作用到參與類(lèi)的層面上。如AspectJ和Spring AOP這樣的Java AOP架構(gòu)認(rèn)為異常的處理是添加故障處理行為的切入點(diǎn)。這樣,把參與者綁定在故障屏障的模式可以放松些。故障的處理可以存活在一個(gè)獨(dú)立的,不相干的方面里,從而摒棄了屏障方法需要放在方法激活次序的最前頭的要求。

如果在你的架構(gòu)里利用了AOP,故障和應(yīng)變的處理是理想的在應(yīng)用里用到的在方面上的候選。對(duì)故障和應(yīng)變的處理在AOP架構(gòu)下的使用做一個(gè)完整的勘探將是將來(lái)論文里一個(gè)很有意思的題目。


結(jié)論

雖然Java異常模型自它出現(xiàn)以來(lái)就激發(fā)了熱烈的討論,如果使用正確的話,它的價(jià)值還是很大的。作為一個(gè)設(shè)計(jì)師,你的任務(wù)是建立好規(guī)格來(lái)最大限度地利用好這個(gè)模型。以故障和應(yīng)變的方式來(lái)考量異??梢詭椭阕龀稣_的決定。合理使用好Java異常模型可以讓你的應(yīng)用簡(jiǎn)單,易維護(hù),和正確。AOP技術(shù)將故障和應(yīng)變定位為相互滲透的問(wèn)題,這個(gè)方法可能會(huì)對(duì)你的架構(gòu)提供一些幫助。


引用


作者Barry Ruzek被Open Group提名為注冊(cè)IT設(shè)計(jì)師的大師。他有著30多年的開(kāi)發(fā)操作系統(tǒng)和企業(yè)應(yīng)用的經(jīng)驗(yàn)。




Abstract

One of the most important architectural decisions a Java developer can make is how to use the Java exception model. Java exceptions have been the subject of considerable debate in the community. Some have argued that checked exceptions in the Java language are an experiment that failed. This article argues that the fault does not lie with the Java model, but with Java library designers who failed to acknowledge the two basic causes of method failure. It advocates a way of thinking about the nature of exceptional conditions and describes design patterns that will help your design. Finally, it discusses exception handling as a crosscutting concern in the Aspect Oriented Programming model. Java exceptions are a great benefit when they are used correctly. This article will help you do that.

Why Exceptions Matter

Exception handling in a Java application tells you a lot about the strength of the architecture used to build it. Architecture is about decisions made and followed consistently at all levels of an application. One of the most important decisions to make is the way that the classes, subsystems, or tiers within your application will communicate with each other. Java exceptions are the means by which methods communicate alternative outcomes for an operation and therefore deserve special attention in your application architecture.

A good way to measure the skill of a Java architect and the development team's discipline is to look at exception handling code inside their application. The first thing to observe is how much code is devoted to catching exceptions, logging them, trying to determine what happened, and translating one exception to another. Clean, compact, and coherent exception handling is a sign that the team has a consistent approach to using Java exceptions. When the amount of exception handling code threatens to outweigh everything else, you can tell that communication between team members has broken down (or was never there in the first place), and everyone is treating exceptions "their own way."

The results of ad hoc exception handling are very predictable. If you ask team members why they threw, caught, or ignored an exception at a particular point in their code, the response is usually, "I didn't know what else to do." If you ask them what would happen if an exception they are coding for actually occurred, a frown follows, and you get a statement similar to, "I don't know. We never tested that."

You can tell if a Java component has made effective use of Java exceptions by looking at the code of its clients. If they contain reams of logic to figure out when an operation failed, why it failed, and if there's anything to do about it, the reason is almost always because of the component's error reporting design. Flawed reporting produces lots of "log and forget" code in clients and rarely anything useful. Worst of all are the twisted logic paths, nested try/catch/finally blocks, and other confusion that results in a fragile and unmanageable application.

Addressing exceptions as an afterthought (or not addressing them at all) is a major cause of confusion and delay in software projects. Exception handling is a concern that cuts across all parts of a design. Establishing architectural conventions for exceptions should be among the first decisions made in your project. Using the Java exception model properly will go a long way toward keeping your application simple, maintainable, and correct.

Challenging the Exception Canon

What constitutes "proper use" of Java's exception model has been the subject of considerable debate. Java was not the first language to support exception-like semantics; however, it was the first language in which the compiler enforced rules governing how certain exceptions were declared and treated. Compile-time exception checking was seen by many as an aid to precise software design that harmonized nicely with other language features. Figure 1 shows the Java exception hierarchy.

In general, the Java compiler forces a method that throws an exception based on java.lang.Throwable including that exception in the "throws" clause in its declaration. Also, the compiler verifies that clients of the method either catch the declared exception type or specify that they throw that exception type themselves. These simple rules have had far-reaching consequences for Java developers world-wide.

The compiler relaxes its exception checking behavior for two branches of the Throwable inheritance tree. Subclasses of java.lang.Error and java.lang.RuntimeException are exempt from compile-time checking. Of the two, runtime exceptions are usually of greater interest to software designers. The term "unchecked" exception is applied to this group to distinguish it from all other "checked" exceptions.

Java Exception Hierarchy
Figure 1. Java exception hierarchy

I imagine that checked exceptions were embraced by those who also valued strong typing in Java. After all, compiler-imposed constraints on data types encouraged rigorous coding and precise thinking. Compile-time type checking helped prevent nasty surprises at run-time. Compile-time exception checking would work similarly, reminding developers that a method had potential alternate outcomes that needed to be addressed.

Early on, the recommendation was to use checked exceptions wherever possible to take maximum advantage of the help provided by the compiler to produce error-free software. The designers of the Java library API evidently subscribed to the checked exception canon, using these exceptions extensively to model almost any contingency that could occur in a library method. Checked exception types still outnumber unchecked types by more than two to one in the J2SE 5.1 API Specification.

To programmers, it seemed like most of the common methods in Java library classes declared checked exceptions for every possible failure. For example, the java.io package relies heavily on the checked exception IOException. At least 63 Java library packages issue this exception, either directly or through one of its dozens of subclasses.

An I/O failure is a serious but extremely rare event. On top of that, there is usually nothing your code can do to recover from one. Java programmers found themselves forced to provide forIOException and similar unrecoverable events that could possibly occur in a simple Java library method call. Catching these exceptions added clutter to what should be simple code because there was very little that could be done in a catch block to help the situation. Not catching them was probably worse since the compiler required that you add them to the list of exceptions your method throws. This exposes implementation details that good object-oriented design would naturally want to hide.

This no-win situation resulted in most of the notorious exception handling anti-patterns we are warned about today. It also spawned lots of advice on the right ways and the wrong ways to build workarounds.

Some Java luminaries started to question whether Java's checked exception model was a failed experiment. Something failed for sure, but it had nothing to do with including exception checking in the Java language. The failure was in the thinking by the Java API designers that most failure conditions were the same and could be communicated by the same kind of exception.

Faults and Contingencies

Consider a CheckingAccount class within an imaginary banking application. A CheckingAccount belongs to a customer, maintains a current balance, and is able to accept deposits, accept stop payment orders on checks, and process incoming checks. A CheckingAccount object must coordinate accesses by concurrent threads, any of which may alter its state.CheckingAccount's processCheck() method accepts a Check object as an argument and normally deducts the check amount from the account balance. But a check-clearing client that calls processCheck() must be ready for two contingencies. First, the CheckingAccount may have a stop payment order registered for the check. Second, the account may not have sufficient funds to cover the check amount.

So, the processCheck() method can respond to its caller in three possible ways. The nominal response is that the check gets processed and the result declared in the method signature is returned to the invoking service. The two contingency responses represent very real situations in the banking domain that need to be communicated to the check-clearing client. All threeprocessCheck() responses were designed intentionally to model the behavior of a typical checking account.

The natural way to represent the contingency responses in Java is to define two exceptions, say StopPaymentException and InsufficientFundsException. It wouldn't be right for a client to ignore these, since they are sure to be thrown in the normal operation of the application. They help express the full behavior of the method just as importantly as the method signature.

Clients can easily handle both kinds of exception. If payment on a check is stopped, the client can route the check for special handling. If there are insufficient funds, the client can transfer funds from the customer's savings account to cover the check and try again.

The contingencies are expected and natural consequences of using the CheckingAccount API. They do not represent a failure of the software or of the execution environment. Contrast these with actual failures that could arise due to problems related to the internal implementation details of the CheckingAccount class.

Imagine that CheckingAccount maintains its persistent state in a database and uses the JDBC API to access it. Almost every database access method in that API has the potential to fail for reasons unrelated to the implementation of CheckingAccount. For example, someone may have forgotten to turn on the database server, unplugged a network cable, or changed the password needed to access the database.

JDBC relies on a single checked exception, SQLException, to report everything that could possibly go wrong. Most of what could go wrong has to do with configuring the database, the connectivity to it, and the hardware it resides on. There's nothing that the processCheck() method could do to deal with these situations in a meaningful way. That's a shame, becauseprocessCheck() at least knows about its own implementation. Upstream methods in the call stack have an even smaller chance of being able to address problems.

The CheckingAccount example illustrates the two basic reasons that a method execution can fail to return its intended result. They are worthy of some descriptive terms:

Contingency
An expected condition demanding an alternative response from a method that can be expressed in terms of the method's intended purpose. The caller of the method expects these kinds of conditions and has a strategy for coping with them.
Fault
An unplanned condition that prevents a method from achieving its intended purpose that cannot be described without reference to the method's internal implementation.

Using this terminology, a stop payment order and an overdraft are the two possible contingencies for the processCheck() method. The SQL problem represents a possible fault condition. The caller of processCheck() ought to have a way of providing for the contingencies, but could not be reasonably expected to handle the fault, should it occur.

Mapping Java Exceptions

Thinking about "what could go wrong" in terms of contingencies and faults will go a long way toward establishing conventions for Java exceptions in your application architecture.

Condition
Contingency Fault
Is considered to be A part of the design A nasty surprise
Is expected to happen Regularly but rarely Never
Who cares about it The upstream code that invokes the method The people who need to fix the problem
Examples Alternative return modes Programming bugs, hardware malfunctions, configuration mistakes, missing files, unavailable servers
Best Mapping A checked exception An unchecked exception

Contingency conditions map admirably well to Java checked exceptions. Since they are an integral part of a method's semantic contract, it makes sense to enlist the compiler's help to ensure that they are addressed. If you find that the compiler is "getting in the way" by insisting that contingency exceptions be handled or declared when it is inconvenient, it's a sure bet that your design could use some refactoring. That's actually a good thing.

Fault conditions are interesting to people but not to software logic. Those acting in the role of "software proctologist" need information about faults to diagnose and fix whatever caused them to happen. Therefore, unchecked Java exceptions are the perfect way to represent faults. They allow fault notifications to percolate untouched through all methods on the call stack to a level specifically designed to catch them, capture the diagnostic information they contain, and provide a controlled and graceful conclusion to the activity. The fault-generating method is not required to declare them, upstream methods are not required to catch them, and the method's implementation stays properly hidden—all with a minimum of code clutter.

Newer Java APIs such as the Spring Framework and the Java Data Objects library have little or no reliance on checked exceptions. The Hibernate ORM framework redefined key facilities as of release 3.0 to eliminate the use of checked exceptions. This reflects the realization that the great majority of the exception conditions that these frameworks report are unrecoverable, stemming from incorrect coding of a method call, or a failure of some underlying component such as a database server. Practically speaking, there is almost no benefit to be gained by forcing a caller to catch or declare such exceptions.

Fault handling in your architecture

The first step toward handling faults effectively in your architecture is to admit that you need to do it. Coming to this acceptance is difficult for engineers who take pride in their ability to create impeccable software. Here is some reasoning that will help. First, your application will be spending a great deal of time in development where mistakes are commonplace. Providing for programmer-generated faults will make it easier for your team to diagnose and fix them. Second, the (over)use of checked exceptions in the Java library for fault conditions will force your code to deal with them, even if your calling sequences are completely correct. If there's no fault handling framework in place, the resulting makeshift exception handling will inject entropy into your application.

A successful fault handling framework has to accomplish four goals:

  • Minimize code clutter
  • Capture and preserve diagnostics
  • Alert the right person
  • Exit the activity gracefully

Faults are a distraction from your application's real purpose. Therefore, the amount of code devoted to processing them should be minimal and, ideally, isolated from the semantic parts of the application. Fault processing must serve the needs of the people responsible for correcting them. They need to know that a fault happened and get the information that will help them figure out why. Even though a fault, by definition, is not recoverable, good fault handling will attempt to terminate the activity that encountered the fault in a graceful way.

Use unchecked exceptions for fault conditions

There are lots of reasons to make the architectural decision to represent fault conditions with unchecked exceptions. The Java runtime rewards programming mistakes by throwingRuntimeException subclasses such as ArithmeticException and ClassCastException, setting a precedent for your architecture. Unchecked exceptions minimize clutter by freeing upstream methods from the requirement to include code for conditions that are irrelevant to their purpose.

Your fault handling strategy should recognize that methods in the Java library and other APIs may use checked exceptions to represent what could only be fault conditions in the context of your application. In this case, adopt the architectural convention to catch the API exception where it happens, treat it as a fault, and throw an unchecked exception to signal the fault condition and capture diagnostic information.

The specific exception type to throw in this situation should be defined by your architecture. Don't forget that the primary purpose of a fault exception is to convey diagnostic information that will be recorded to help people figure out what went wrong. Using multiple fault exception types is probably overkill, since your architecture will treat them all identically. A good, descriptive message embedded inside a single fault exception type will do the job in most cases. It's easy to defend using Java's generic RuntimeException to represent your fault conditions. As of Java 1.4,RuntimeException, like all throwables, supports exception chaining, allowing you to capture and report a fault-inducing checked exception.

You may choose to define your own unchecked exception for the purpose of fault reporting. This would be necessary if you need to use Java 1.3 or earlier versions that do not support exception chaining. It is simple to implement a similar chaining capability to capture and translate checked exceptions that constitute faults in your application. Your application may have a need for special behavior in a fault reporting exception. That would be another reason to create a subclass of RuntimeException for your architecture.

Establish a fault barrier

Deciding which exception to throw and when to throw it are important decisions for your fault-handling framework. Just as important are the questions of when to catch a fault exception and what to do afterward. The goal here is to free the functional portions of your application from the responsibility of processing faults. Separation of concerns is generally a good thing, and a central facility responsible for dealing with faults will pay benefits down the road.

In the fault barrier pattern, any application component can throw a fault exception, but only the component acting as the "fault barrier" catches them. Adopting this pattern eliminates much of the intricate code that developers insert locally to deal with faults. The fault barrier resides logically toward the top of the call stack where it stops the upward propagation of an exception before default action is triggered. Default action means different things depending on the application type. For a stand-alone Java application, it means that the active thread is terminated. For a Web application hosted by an application server, it means that the application server sends an unfriendly (and embarrassing) response to the browser.

The first responsibility of a fault barrier component is to record the information contained in the fault exception for future action. An application log is by far the best place to do this. The exception's chained messages, stack traces, and so on, are all valuable pieces of information for diagnosticians. The worst place to send fault information is across the user interface. Involving the client of your application in your debugging process is hardly ever good for you or your client. If you are really tempted to paint the user interface with diagnostic information, it probably means that your logging strategy needs improvement.

The next responsibility of a fault barrier is to close out the operation in a controlled manner. What that means is up to your application design but usually involves generating a generic response to a client that may be waiting for one. If your application is a Web service, it means building a SOAP <fault> element into the response with a <faultcode> of soap:Server and a generic<faultstring> failure message. If your application communicates with a Web browser, the barrier would arrange to send a generic HTML response indicating that the request could not be processed.

In a Struts application, your fault barrier can take the form of a global exception handler configured to process any subclass of RuntimeException. Your fault barrier class will extendorg.apache.struts.action.ExceptionHandler, overriding methods as needed to implement the custom processing you need. This will take care of inadvertently generated fault conditions and fault conditions explicitly discovered during the processing of a Struts action. Figure 2 shows contingency and fault exceptions.

Contingency and Fault Exceptions
Figure 2. Contingency and fault exceptions

If you are using the Spring MVC framework, your fault barrier can easily be built by extending SimpleMappingExceptionResolver and configuring it to handle RuntimeException and its subclasses. By overriding the resolveException() method, you can add any custom handling you need before using the superclass method to route the request to a view component that sends a generic error display.

When your architecture includes a fault barrier and developers are made aware of it, the temptation to write one-off fault exception handling code decreases dramatically. The result is cleaner and more maintainable code throughout your application.

Contingency Handling in Your Architecture

With fault processing relegated to the barrier, contingency communication between primary components becomes much simpler. A contingency represents an alternative method result that is just as important as the principal return result. Therefore, checked exception type is a good vehicle to convey the existence of a contingency condition and supply the information needed to contend with it. This practice enlists the help of the Java compiler to remind developers of all aspects of the API they are using and the need to provide for the full range of method outcomes.

It is possible to convey simple contingencies by using a method's return type alone. For example, returning a null reference instead of an actual object can signify that the object could not be created for a defined reason. Java I/O methods typically return an integer value of -1 instead of a byte value or byte count to indicate an end-of-file condition. If your method's semantics are simple enough to allow it, alternative return values may be the way to go, since they eliminate the overhead that comes with exceptions. The downside is that the method caller is responsible for testing the return value to see if it is a primary result or a contingency result. The compiler will not insist that the method caller makes that test, however.

If a method has a void return type, an exception is the only way to indicate that a contingency occurred. If a method is returns an object reference, the vocabulary that the return value can express is limited to two values (null and non-null). If a method returns an integral value, it may be possible to express several contingency conditions by choosing values that are guaranteed not to conflict with the primary return values. But now we have entered the world of error code checking, something the Java exception model was developed to avoid.

Supply something useful

It made little sense to define different fault reporting exception types, since the fault barrier treats them all the same. Contingency exceptions are quite different, because they are meant to convey diverse conditions to method callers. Your architecture would probably specify that these exceptions should all extend java.lang.Exception or a designated base class that does.

Do not forget your exceptions are complete Java types that can accommodate specialized fields, methods, and even constructors that can be shaped for your unique purposes. For example, theInsufficientFundsException type thrown by the imaginary CheckingAccount processCheck() method could include an OverdraftProtection object that is able to transfer funds needed to cover the shortfall from another account whose identity depends on how the checking account is set up.

To log or not to log

Logging fault exceptions makes sense because their purpose is to draw the attention of people to situations that need to be corrected. The same cannot be said for contingency exceptions. They may represent relatively rare events, but every one of them is expected to happen during the life of your application. If anything, they signify that the application is working the way it was designed to work. Routinely adding logging code to contingency catch blocks adds clutter to your code with no actual benefit. If a contingency represents a significant event, it is probably better for a method to generate a log entry recording the event before throwing a contingency exception to alert its caller.

Exception Aspects

In Aspect Oriented Programming (AOP) terms, fault and contingency handling are crosscutting concerns. To implement the fault barrier pattern, for example, all the participating classes must follow common conventions:

  • The fault barrier method must reside at the head of a graph of method calls that traverses the participating classes.
  • They must all use unchecked exceptions to signify fault conditions.
  • They must all use the specific unchecked exception types that the fault barrier is expecting to receive.
  • They all must catch and translate checked exceptions from lower methods that are deemed to be faults in their execution context.
  • They must not interfere with the propagation of fault exceptions on their way to the barrier.

These concerns cut across the boundaries of otherwise unrelated classes. The result is minor bits of scattered fault handling code and implicit coupling between the barrier class and the participants (although still a great improvement over not using a pattern at all!). AOP allows the fault handling concern to be encapsulated in a common Aspect applied to the participating classes. Java AOP frameworks such as AspectJ and Spring AOP recognize exception handling as a join point to which fault handling behavior (or advice) can be attached. In this way, the conventions that bind participants in the fault barrier pattern can be relaxed. Fault processing can now reside within an independent, out-of-line aspect, eliminating the need for a "barrier" method to be placed at the head of a method invocation sequence.

If you are exploiting AOP in your architecture, fault and contingency handling are ideal candidates for aspects that apply throughout an application. A full exploration of how fault and contingency handling could work in the AOP world would make an interesting topic for a future article.

Conclusion

Although the Java exception model has generated spirited discussion during its lifetime, it provides excellent value when it is applied correctly. As an architect, it is up to you to establish conventions that get the most from the model. Thinking of exceptions in terms of faults and contingencies can help you make the right choices. Using the Java exception model properly will keep your application simple, maintainable, and correct. Aspect Oriented Programming techniques may offer some definite advantages for your architecture by recognizing fault and contingency handling as crosscutting concerns.

References

Barry Ruzek has been named a Master Certified IT Architect by the Open Group. He has over 30 years of experience developing operating systems and enterprise applications.

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多