?? exception.txt
字號:
一個未檢查的異常
應變情況恰如其分地匹配給了Java檢查的異常。因為它們是方法的語義算法合同中不可缺少的一部分,在這里借助于編譯器的幫助來確保它們得到解決是很有道理的。如果你發現編譯器堅持應變的異常必須要處理或者在不方便的時候必須要聲明會給你帶來些麻煩,你在設計上幾乎肯定要做些重構了。這其實是件好事。
出現故障的情況對開發人員而言是蠻有意思的,但對軟件邏輯而言卻并非如此。那些軟件”消化問題“的專家們需要關于故障的信息以便來解決問題。因此,未檢查的異常是表示故障的很好方式。他們讓故障的通知原封不動地從調用棧上所有的方法濾過,到達一個專門來捕獲它們的地方,并得到它們自身包含的有利于診斷的信息,對整個事件提供一個有節制的優雅的結論。產生故障的方法不需要來聲明(異常),上游的調用方法不需要捕獲它們,方法的實施細則被正確的隱藏起來- 以最低的代碼復雜度。
新一些的Java API,比如像Spring架構和Java Data Ojects類庫對檢查的異常幾乎沒有依賴。Hibernate ORM架構在3.0版本里重新定義了一些關鍵功能來去除對檢查的異常的使用。這就意味著在這些架構舉報的絕大部分異常都是不可恢復的,歸咎于錯誤的方法調用代碼,或是類似于數據庫服務器之類的底層部件的失敗。特別的,強迫一個調用方來捕獲或聲明這些異常幾乎沒有任何好處。
設計里的故障處理
在你的計劃里,承認你需要去做就邁好了有效處理好故障的第一步。對那些堅信自己能寫出無懈可擊的軟件的工程師們來說,承認這一點是不容易的。這里是一些有幫助的思考方式。首先,如果錯誤俯拾即是,應用的開發時間將很長,當然前提是程序員自己的bug自己修理。第二,在Java類庫中,過度使用檢查的異常來處理故障情形將迫使你的代碼要應對好故障,即使你的調用次序完全正確。如果沒有一個故障處理的架構,湊合的異常處理將導致應用中的信息丟失。
一個成功的故障處理架構一定要達到下面的目標:
減少代碼的復雜性
捕獲和保存診斷性信息
對合適的人提醒注意
優雅地退出行動
故障是應用的真實意圖的干擾。因此,用來處理它們的代碼應盡量的少,理想上,把它們和應用的語義算法部分隔離開。故障的處理必須滿足那些負責改正它們的人的需要。開發人員需要知道故障發生了,并得到能幫助他們搞清為何發生的信息。即使一個故障,在定義上而言,是不可補救的,好的故障處理會試著優雅地結束引起故障的活動。
對故障情況使用未檢查的異常
在做框架上的決定時,用未檢查的異常來代表故障情況是有很多原因的。Java的運行環境對代碼的錯誤會拋出“運行時異常”的子類,比如,ArithmeticException或ClassCastException。這為你的框架設了一個先例。未檢查的異常讓上游的調用方法不需要為和它們目的不相關的情況而添加代碼,從而減少了混亂。
你的故障處理策略應該認識到Java類庫的方法和其他API可能會使用檢查的異常來代表對你的應用而言只可能是故障的情況。在這種情形下,采用設計約定來捕獲API異常,將其以故障來看待,拋出一個未檢查的異常來指示故障的情況和捕獲診斷的信息。
在這種情況下拋出的特定異常類型應該由你的框架來定義。不要忘記一個故障異常的主要目的是傳遞記錄下來的診斷信息,以便讓人們來想出出錯的原因。使用多個故障異常類型可能有些過,因為你的架構對它們都一視同仁。多數情況下,一條好的,描述性強的信息將單一的故障類型嵌入就夠用了。使用Java基本的RuntimeException來代表故障情況是很容易的。截止到Java1.4,RuntimeException,和其他的拋出類型一樣,都支持異常的嵌套,這樣你就可以捕獲和報出導向故障的檢查的異常。
你也許會為了故障報告的目的而定義你自己的未檢查的異常。這樣做可能是必要的,如果你使用Java1.3或更早的版本,它們都不支持異常的嵌套。實施一個類似的嵌套功能來捕獲和轉換你應用中構成故障的檢查的異常是很簡單的。你的應用在報錯時可能需要一個特殊的行為。這可能是你在架構中創建RuntimeException子類的另一個原因。
建立一個故障的屏障
對你的故障處理架構而言,決定拋出什么樣的異常,何時拋出是重要的決定。同樣重要的是,何時來捕獲一個故障異常,之后再怎么辦。這里的目的是讓你應用中的功能性部分不需要處理故障。把問題分開來處理通常都是一件好事情,有一個中央故障處理機制長遠來看是很有裨益的。
在故障屏障的模式里,任何應用組件都可以拋出故障異常,但是只有作為“故障屏障”的組件才捕獲異常。采用此種模式去除了大多數程序員為了在本地處理故障而插入的復雜的代碼。故障屏障邏輯上位于調用棧的上層,這樣在一個默認的行動被激發前,一個異常向上舉報的行為就被阻止了。根據不同的應用類型,默認的行動所指也不同。對一個獨立的Java應用而言,這個行動指活著的線程被停止。對一個位于應用服務器上的Web應用而言,這個行動指應用服務器向瀏覽器送出不友好的(甚至令人尷尬的)回應。
一個故障屏障組件的第一要務就是記錄下故障異常中包含的信息以為將來所用。到現在為止,一個應用日志是做成此事的首選。異常的嵌套的信息,棧日志,等等,都是對診斷有價值的信息。傳遞故障信息最差的地方是通過用戶界面。把應用的使用者卷進查錯的進程對你,對你的用戶而言都不好。如果你真的很想把診斷信息放上用戶界面,那可能意味著你的日志策略需要改進。
故障屏障的下一個要務是以一種可控的方式來結束操作。這具體的意義要取決于你應用的設計,但通常包括產生一個可通用的回應給可能正在等待的客戶端。如果你的應用是一個Web service,這就意味著在回應中用soap:Server的<faultcode>和通用的失敗信息<faultstring>來建立一個SOAP故障元素<fault>。如果你的應用于瀏覽器交流,這個屏障就會安排好一個通用的HTML回應來表明需求是不能被處理的。
在一個Struts的應用里,你的故障屏障會以一種全局異常處理器的形式出現,并被配置成處理RuntimeException的任何子類。你的故障屏障類將延伸org.apache.struts.action.ExceptionHandler類,必要的話,重寫它的方法來實施用戶自己的特別處理。這樣就會處理好不小心產生的故障情況和在處理一個Struts動作時發現的故障。圖2顯示的就是應變和故障異常。
圖2 應變和故障異常
如果你使用的是Spring MVC架構,你可以繼承SimpleMappingExceptionResolver類,并配置成處理RuntimeException和它的子類們,這樣很容易的就建起了故障屏障。通過重寫resolveException的方法,你可以在使用父類的方法來把需求導引到一個發出通用錯誤提示的view組件之前,加入你需要的用戶化的處理。
當你的架構包含了故障屏障,程序員都知曉了后,再寫出一次性的故障異常的沖動就會銳減。結果就是應用中出現更干凈,更易于維護的代碼。
架構中應變的處理
將故障處理交與屏障后,主要組件間的應變交流變得容易多了。一個應變代表著與主要返回結果同等重要的另外一種方法結果。因此,檢查的異常類型是一個能夠很好地傳遞應變情況的存在并提供必要的信息來與它競爭的工具。這個方式借助于Java編譯器的幫助來提醒程序員關于他們所用的API的方方面面以及提供全套的方法輸出的必要性。
僅僅使用方法的返回值類型來傳遞簡單的應變是可能的。比如,返回一個空引用,而不是一個具體的對象,可以意味著對象由于一個已定義的原因不能被建立。Java I/O的方法通常返回一個整數值-1,而不是字節的值或字節的數來表示文件的結尾。如果你的方法的語義簡單到可以允許的地步,另一種返回值的方法是可以使用的,因為它摒棄了異常帶來的額外的花銷。不足之處是方法的調用方要檢測一下返回的值來判斷是主要結果,還是應變結果。但是,編譯器沒有辦法來保證方法調用者會使用這個判斷。
如果一個方法有一個void的返回類型,異常是唯一的方法來表示應變發生了。如果一個方法返回的是一個對象的引用,那么返回值只可能是空或非空(null and non-null)。如果一個方法返回一個整數型,選擇與主要返回值不沖突的,可以表示多種應變情況的數值是可能的。但是這樣的話,我們就進入了錯誤代碼檢查的世界,而這正式Java異常模式所著力避免的。
提供一些有用的信息
定義不同的故障報告的異常類型是沒什么道理的,因為故障屏障對所有異常類型一視同仁。應變異常就有很大的不同,因為它們的原意是要向方法調用者傳遞各種情況。你的架構可能會指出這些異常應該繼承java.lang.Exception或一個指定的基類。
不要忘記你的異常應該是百分百的Java類型,你可以用它來存放為你的特殊目的服務的特殊字段,方法,甚至是構造器。比如,被假想的processCheck()方法拋出的InsufficientFundsException這個異常類型就應該包含著一個OverdraftProtection的對象,它能夠從另外一個帳戶里把短缺的資金轉過來。
日志還是不要日志
記錄下故障異常是有用處的,因為日志的目的是在一些需要改正的情況下,日志可以吸引人們的注意力。但對應變異常而言卻并非如此。應變異常可能代表的只是極少數情況,但是在你的應用里,每一個情況還是會發生的。它們意味著你的應用正在如最初的設計般正常工作著。經常把日志代碼加進應變的捕獲塊里會使你的代碼晦澀難懂,而又沒有實際的好處。如果一個應變代表了一重要的事件,在拋出一個異常應變來警醒調用者之前,產生一筆日志,記錄下這個事件可能會讓這個方法更好些。
異常的各個方面
在Aspect Oriented Programming(AOP)的術語里,故障和應變的處理是互相滲透的問題。比如,要實施故障屏障的模式,所有參與的類必須遵循通用規格:
故障屏障方法必須存活在遍歷參與類的方法調用圖的最前端
參與類必須使用未檢查的異常來表示故障情況
參與類必須使用故障屏障期望得到的有針對性的未檢查的異常類型
參與類必須捕獲并從低端方法中把在執行情境下注定的故障轉換成檢查的異常
參與類不能干擾故障異常被傳遞到故障屏障的過程
這些問題超越了那些本不相干的類的邊界。結果就是少數零散的故障處理代碼,以及屏障類和參與類間暗含的耦合(這已經比不使用模式進步多了!)。AOP讓故障處理的問題被封裝在通用的可以作用到參與類的層面上。如AspectJ和Spring AOP這樣的Java AOP架構認為異常的處理是添加故障處理行為的切入點。這樣,把參與者綁定在故障屏障的模式可以放松些。故障的處理可以存活在一個獨立的,不相干的方面里,從而摒棄了屏障方法需要放在方法激活次序的最前頭的要求。
如果在你的架構里利用了AOP,故障和應變的處理是理想的在應用里用到的在方面上的候選。對故障和應變的處理在AOP架構下的使用做一個完整的勘探將是將來論文里一個很有意思的題目。
結論
雖然Java異常模型自它出現以來就激發了熱烈的討論,如果使用正確的話,它的價值還是很大的。作為一個設計師,你的任務是建立好規格來最大限度地利用好這個模型。以故障和應變的方式來考量異常可以幫助你做出正確的決定。合理使用好Java異常模型可以讓你的應用簡單,易維護,和正確。AOP技術將故障和應變定位為相互滲透的問題,這個方法可能會對你的架構提供一些幫助。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -