|
概述
一般情況下,企業(yè)級應(yīng)用都對應(yīng)著復(fù)雜的業(yè)務(wù)邏輯,為了保證系統(tǒng)的健壯,必然需要面對各種系統(tǒng)業(yè)務(wù)異常和運(yùn)行時(shí)異常。
不好的異常處理方式容易造成應(yīng)用程序邏輯混亂,脆弱而難于管理。應(yīng)用程序中充斥著零散的異常處理代碼,使程序代碼晦澀難懂、可讀性差,并且難于維護(hù)。
一個(gè)好的異常處理框架能為應(yīng)用程序的異常處理提供統(tǒng)一的處理視圖,把異常處理從程序正常運(yùn)行邏輯分離出來,以至于提供更加結(jié)構(gòu)化以及可讀性的程序架構(gòu)。另外,一個(gè)好的異常處理框架具備可擴(kuò)展性,很容易根據(jù)具體的異常處理需求,擴(kuò)展出特定的異常處理邏輯。
另外,異常處理框架從一定程度上依賴并體現(xiàn)系統(tǒng)架構(gòu)層次。系統(tǒng)架構(gòu)決定了系統(tǒng)中各個(gè)子系統(tǒng),各個(gè)層次之間的交互,而異常處理框架則統(tǒng)一體現(xiàn)這種架構(gòu)中的各種交互所發(fā)生的錯(cuò)誤、異常。因此,異常處理框架是系統(tǒng)架構(gòu)時(shí)就應(yīng)該考慮的問題。
本文將對異常相關(guān)方面做一些討論,并進(jìn)而探討一些關(guān)于構(gòu)建穩(wěn)健且可擴(kuò)展的異常處理框架方面的視角或設(shè)計(jì)原則。由于本文引入一部分 Java 語言中異常相關(guān)的概念,因此本文假設(shè)您熟悉 Java 相關(guān)基礎(chǔ)知識以及了解 Java EE 和 EJB 相關(guān)技術(shù)。
Java 異常基本概念
在 Java 程序設(shè)計(jì)語言中,使用一種異常處理的錯(cuò)誤捕獲機(jī)制。當(dāng)程序運(yùn)行過程中發(fā)生一些異常情況,程序有可能被中斷、或?qū)е洛e(cuò)誤的結(jié)果出現(xiàn)。在這種情況下,程序不會(huì)返回任何值,而是拋出封裝了錯(cuò)誤信息的對象。Java 語言提供了專門的異常處理機(jī)制去處理這些異常。如圖 1 所示為 Java 異常體系結(jié)構(gòu):
圖 1. Java 異常體系結(jié)構(gòu)
檢查 (Checked) 異常與非檢查 (Unchecked) 異常
Java 語言規(guī)范將派生于 Error 類或 RuntimeException 類的所有異常都稱為非檢查異常。除“非檢查異常”以外的所有異常都稱為檢查異常。檢查異常對方法調(diào)用者來說屬于必須處理的異常,當(dāng)一個(gè)應(yīng)用系統(tǒng)定義了大量或者容易產(chǎn)生很多檢查異常的方法調(diào)用,程序中會(huì)有很多的異常處理代碼。
如果一個(gè)異常是致命的且不可恢復(fù)并且對于捕獲該異常的方法根本不知如何處理時(shí),或者捕獲這類異常無任何益處,筆者認(rèn)為應(yīng)該定義這類異常為非檢查異常,由頂層專門的異常處理程序處理;像數(shù)據(jù)庫連接錯(cuò)誤、網(wǎng)絡(luò)連接錯(cuò)誤或者文件打不開等之類的異常一般均屬于非檢查異常。這類異常一般與外部環(huán)境相關(guān),一旦出現(xiàn),基本無法有效地處理。
而對于一些具備可以回避異常或預(yù)料內(nèi)可以恢復(fù)并存在相應(yīng)的處理方法的異常,可以定義該類異常為檢查異常。像一般由輸入不合法數(shù)據(jù)引起的異常或者與業(yè)務(wù)相關(guān)的一些異常,基本上屬于檢查異常。當(dāng)出現(xiàn)這類異常,一般可以經(jīng)過有效處理或通過重試可以恢復(fù)正常狀態(tài)。
由于檢查異常屬于必須處理的異常,在存在大量的檢查異常的程序中,意味著很多的異常處理代碼。另外,檢查異常也導(dǎo)致破壞接口方法。如果一個(gè)接口上的某個(gè)方法已被多處使用,當(dāng)為這個(gè)方法添加一個(gè)檢查異常時(shí),導(dǎo)致所有調(diào)用此方法的代碼都需要修改處理該異常。當(dāng)然,存在合適數(shù)量的檢查異常,無疑是比較優(yōu)雅的,有助于避免許多潛在的錯(cuò)誤。
到底何時(shí)使用檢查異常,何時(shí)使用非檢查異常,并沒有一個(gè)絕對的標(biāo)準(zhǔn),需要依具體情況而定。很多情況,在我們的程序中需要將檢查異常包裝成非檢查異常拋給頂層程序統(tǒng)一處理;而有些情況,需要將非檢查異常包裝成檢查異常統(tǒng)一拋出。
多視角理解異常
從應(yīng)用系統(tǒng)最終用戶的角度來看,用戶所面對的是系統(tǒng)中所提供的各種業(yè)務(wù)功能以及系統(tǒng)本身的管理功能。用戶并不理解系統(tǒng)內(nèi)部是如何實(shí)現(xiàn)以及如何運(yùn)行的,與系統(tǒng)開發(fā)者存在天然的鴻溝,系統(tǒng)運(yùn)行對用戶來說如同一個(gè)黑盒一樣。對用戶而言,系統(tǒng)所出現(xiàn)的任何異常或錯(cuò)誤,都屬于系統(tǒng)運(yùn)行時(shí)異常。對于這些異常,有些異常是用戶可以理解并能解決的;而另外一些異常是用戶無法理解和解決的。當(dāng)一個(gè)系統(tǒng)錯(cuò)誤出現(xiàn)時(shí),系統(tǒng)本身需要反饋給用戶一種可理解的業(yè)務(wù)相近的信息,從而用戶可以根據(jù)這些信息去盡可能解決問題。另一方面,有一類錯(cuò)誤屬于系統(tǒng)內(nèi)部運(yùn)行異常或錯(cuò)誤,用戶對此類錯(cuò)誤根本無能為力。而這類異常同樣需要提供足夠詳細(xì)的信息,系統(tǒng)管理員可根據(jù)這類異常盡可能解決。一般情況下,如果異常面向系統(tǒng)用戶,以系統(tǒng)異常呈現(xiàn)更好。
從系統(tǒng)開發(fā)者角度來看,更多的是從系統(tǒng)內(nèi)部邏輯來看異常。有一部分異常需要內(nèi)部截獲處理,而另外一部分異常對于異常產(chǎn)生源而言無法進(jìn)行有效處理,從而需要向外拋出異常以待合適的調(diào)用者進(jìn)行處理。對于開發(fā)者而言,需要預(yù)見異常,并且需要考慮何時(shí)處理異常,何時(shí)拋出異常,必要時(shí)以某種方式記錄或通知異常。總而言之,開發(fā)者需要通過對系統(tǒng)運(yùn)行時(shí)可能出現(xiàn)的異常盡可能地處理以保證系統(tǒng)的正常運(yùn)行,并對于無法處理的異常以一種合適的方式記錄、通知、呈現(xiàn)以便找到發(fā)生異常的原因,從而解決或避免異常。
圖 2. 異常視圖
異常管理與異常框架
基本異常處理結(jié)構(gòu)
如圖 3 所示,為一個(gè)常見的一般性異常處理代碼結(jié)構(gòu)。其中,try 語句塊代表要運(yùn)行的代碼并受異常監(jiān)控,其中代碼發(fā)生異常時(shí),會(huì)創(chuàng)建一個(gè)異常對象并拋出。
catch 語句塊會(huì)捕獲 try 代碼塊中發(fā)生的異常,并與自己的異常類型進(jìn)行匹配,若匹配,則在其 catch 代碼塊中進(jìn)行異常處理。catch 語句塊可以有多個(gè),當(dāng) try 語句塊中拋出一個(gè)異常時(shí),會(huì)針對每個(gè) catch 塊進(jìn)行匹配,一旦與某個(gè) catch 塊匹配,就進(jìn)入該 catch 塊處理并不再與其他 catch 塊匹配。
finally 語句塊是緊跟 catch 語句后的語句塊,該語句塊總會(huì)在方法返回前執(zhí)行,無論 try 語句塊是否發(fā)生異常。
圖 3. 異常處理代碼結(jié)構(gòu)
前面說過,一般當(dāng)程序發(fā)生異常時(shí),通常異常處理可能需要做一些通用處理,如異常日志記錄、異常通知,重定向到一個(gè)統(tǒng)一的錯(cuò)誤頁面(如 Web 應(yīng)用)等。如果這些通用異常處理放置于 catch 塊中,將導(dǎo)致大量的重復(fù)代碼,從而可能引起日志冗余、同一異常的實(shí)現(xiàn)多樣化等問題。另外,大量異常處理程序放置于 catch 塊中造成程序的高耦合性。為了解決此類問題,有必要分離出異常處理程序、統(tǒng)一異常處理風(fēng)格、降低耦合性、增強(qiáng)異常處理模塊的復(fù)用程度。通常的異常處理模式包括業(yè)務(wù)委托模式(Business Delegate)、前端控制器模式(Front Controller)、攔截過濾器模式(Intercepting Filter)、AOP 模式、模板方法模式等。
一般性異常處理框架
為了解決基本異常處理結(jié)構(gòu)所帶來的問題,不妨把異常相關(guān)處理委托給一個(gè)專用 Service 代理,從而分離出異常處理業(yè)務(wù),以一種統(tǒng)一的方式和邏輯進(jìn)行處理,如圖 4 所示。異常 Service 主要處理兩個(gè)方面:一方面是要按照實(shí)際系統(tǒng)要求調(diào)用通用處理程序處理異常,如日志記錄、異常界面展示、異常通知等;另外一方面,需要通過過濾所接受到的異常類型,找到定制的異常處理程序進(jìn)行異常處理。對于異常 Service 的應(yīng)用一般可以通過在系統(tǒng)的頂層進(jìn)行異常自動(dòng)攔截(一般多層系統(tǒng)中尤為普遍,如放置于前端控制器 Front Controller),或者主動(dòng)調(diào)用異常 Service 進(jìn)行處理。
圖 4. 一般性異常處理框架
如圖 5 所示類圖顯示了一個(gè)具體的異常處理框架:
圖 5. 異常處理框架類圖樣例
該框架主要包括三部分:異常 Service、異常處理過濾器、系統(tǒng)異常層次定義。
異常 Service:整個(gè)異常框架的核心,通常用于主動(dòng)攔截異常或被動(dòng)調(diào)用處理異常。根據(jù)具體業(yè)務(wù)需要,調(diào)用通用服務(wù)程序進(jìn)行一般化異常處理,如日志服務(wù)、異常消息通知服務(wù)等;另外,異常 Service 最主要目的用于攔截并處理異常,其需要維護(hù)定制的異常處理器鏈,用于特定類型異常的特定處理。
異常處理過濾器:維護(hù)系統(tǒng)中各種異常處理器,包括增加異常處理器、刪除異常處理器、查找異常處理器操作等。其中最主要的功能是接收特定異常并找到與之匹配的異常處理器進(jìn)行處理。異常處理過濾器具體實(shí)現(xiàn)可以通過一個(gè)配置文件維護(hù)所有異常與異常處理器的映射,另外可以通過另外一個(gè)配置文件維護(hù)系統(tǒng)中所有已定義的異常處理器。從而,異常處理過濾器可以通過配置文件進(jìn)行初始化操作。
異常層次定義:異常層次定義應(yīng)用系統(tǒng)的異常基礎(chǔ)結(jié)構(gòu),是異常處理過濾器所處理的目標(biāo)異常類型集合。
異常層次定義
異常層次結(jié)構(gòu)應(yīng)該以一種普遍通用的原則定義。為此,我們可以利用面向?qū)ο笳Z言具備多態(tài)的性質(zhì),隱藏異常的實(shí)際實(shí)現(xiàn)。對于異常 Service 而言,只需要捕獲最基本的應(yīng)用程序異常 AppException,異常處理過濾器會(huì)自動(dòng)過濾實(shí)際異常類型并找到相應(yīng)的異常處理器。另外,在方法的 throws 語句中勿需放入大量的檢查異常;對方法調(diào)用者也不會(huì)出現(xiàn)混亂的 catch 塊,最多可能只存在一個(gè)用于處理基本應(yīng)用程序異常 AppException(委托給異常 Service 處理)。
前面的章節(jié)講過,應(yīng)用系統(tǒng)異常可以從用戶和開發(fā)者兩個(gè)視角去考慮。因此,我們可以把異常劃分為業(yè)務(wù)操作異常和系統(tǒng)內(nèi)部運(yùn)行時(shí)異常兩種類型。拋出業(yè)務(wù)級異常或系統(tǒng)運(yùn)行時(shí)異常的決策,需要與應(yīng)用系統(tǒng)本身的架構(gòu)層次相結(jié)合,考慮所要處理異常的層次。如圖 6 所示為一個(gè)典型的異常層次結(jié)構(gòu):
圖 6. 異常層次類圖樣例
其中,BussinessException 屬于基本業(yè)務(wù)操作異常,所有業(yè)務(wù)操作異常都繼承于該類。例如,通常 UI 層或 Web 層是由系統(tǒng)最終用戶執(zhí)行業(yè)務(wù)操作驅(qū)動(dòng),因此最好拋出業(yè)務(wù)類異常。ServiceException 一般屬于中間服務(wù)層異常,該層操作引起的異常一般包裝成基本 ServiceException。DAOException 屬于數(shù)據(jù)訪問相關(guān)的基本異常。
對于多層系統(tǒng),每一層都有該層的基本異常。在層與層之間的信息傳遞與方法調(diào)用時(shí)候,一旦在某層發(fā)生異常,傳遞到上一層的時(shí)候,一般包裝成該層異常,直至與用戶最接近的 UI 層,從而轉(zhuǎn)化成用戶友好的錯(cuò)誤信息。
異常轉(zhuǎn)譯以及異常鏈
前面關(guān)于檢查異常和非檢查異常的論述中提到,在存在大量的檢查異常的程序中,意味著很多的異常處理代碼,導(dǎo)致晦澀的異常處理,并且檢查異常容易破壞接口方法。為了解決檢查異常帶來的缺陷,我們可以利用異常轉(zhuǎn)譯的方法,將檢查異常轉(zhuǎn)化為非檢查異常,由異常 Service 攔截處理。
異常轉(zhuǎn)譯就是將一種異常轉(zhuǎn)換為另一種異常。異常轉(zhuǎn)譯針對所有繼承 Throwable 超類的異常類而言的。如下圖 7 中代碼所示展示了異常轉(zhuǎn)譯的一個(gè)例子:
圖 7. 異常轉(zhuǎn)譯代碼樣例
對于任何一個(gè)應(yīng)用系統(tǒng)而言,系統(tǒng)運(yùn)行過程中所發(fā)生的任何異常或錯(cuò)誤都應(yīng)該以合適的方式通知用戶或記錄;由于異常源可能來自很多方面,其所拋出的異常大多不能為系統(tǒng)用戶所理解,此時(shí)就必須將各種類型的異常轉(zhuǎn)化成各種用戶可理解的異常。這也是異常框架所需要關(guān)注和解決的方面。
在異常的層層轉(zhuǎn)譯過程中,就形成一個(gè)異常鏈。整個(gè)異常鏈保存了每一層的異常原因。通過遞歸調(diào)用 getCause() 方法可以遍歷所有的異常原因。需要注意的是,在形成異常鏈的過程中,會(huì)消耗較多的資源,導(dǎo)致系統(tǒng)性能降低。這里涉及異常原理,在此不必多說,有興趣可查閱相關(guān)資料。在本文提出的異常框架中,異常 service 可以截獲來自系統(tǒng)各層的異常,而勿需異常層層轉(zhuǎn)譯。
結(jié)束語
本文首先簡要介紹了異常的基本概念以及 Java 語言中基本異常體系結(jié)構(gòu),重點(diǎn)介紹了 Java 異常中的檢查 (checked) 異常和非檢查 (unchecked) 異常兩個(gè)概念。然后,著重介紹了對于一個(gè)應(yīng)用系統(tǒng)從用戶和開發(fā)者兩個(gè)角度如何去看應(yīng)用系統(tǒng)所發(fā)生的異常;通過多視角看應(yīng)用系統(tǒng)異常對于設(shè)計(jì)一個(gè)合理的系統(tǒng)異常框架可以提供較好的設(shè)計(jì)原則。最后介紹了一個(gè)通用可擴(kuò)展的異常處理框架,包括設(shè)計(jì)原則,異常層次結(jié)構(gòu)的定義以及異常轉(zhuǎn)譯方面的考慮。
尤其對于比較大的軟件系統(tǒng),異常處理框架是軟件系統(tǒng)體系結(jié)構(gòu)需要考量的很重要的一方面。好的異常處理結(jié)構(gòu)既能條理清晰地處理異常,又能保證異常處理的可擴(kuò)展性與可用性,最后還需要保證系統(tǒng)的性能不受額外的損失。
關(guān)于作者
王建光為 IBM 一名軟件工程師,目前正參與開發(fā) IBM System Director 產(chǎn)品。他還曾經(jīng)領(lǐng)導(dǎo)過大型連鎖系統(tǒng)項(xiàng)目,開發(fā)過 C++、Java 產(chǎn)品。目前對軟件架構(gòu)、軟件設(shè)計(jì)模式等領(lǐng)域非常感興趣。
it知識庫:異常以及異常處理框架探析,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時(shí)間聯(lián)系我們修改或刪除,多謝。