該如何良好的實踐Java中的Exception機(jī)制
首先,我先聲明一點,我討論的僅限于互聯(lián)網(wǎng)數(shù)據(jù)產(chǎn)品,當(dāng)然可能會涉及到一些其他的抽象,但是所有的結(jié)論不代表能復(fù)用到所有場景。
幾乎每個Java程序員都清楚知道Java的異常和錯誤機(jī)制,無論是在面試過程中,還是在學(xué)習(xí)中,你看到Exception,無非就是了解一下繼承關(guān)系、子類、和Error的關(guān)系等等。當(dāng)然這些知識點是基礎(chǔ),那么在實踐中,用到了嗎?你確定你使用Exception時沒有偷懶?我的經(jīng)驗告訴我,良好的使用Exception能讓你的程序bug更少,或者至少能保證你的程序更容易被理解和跟蹤。
先回到老的知識點吧——Java的異常機(jī)制,我們知道Java里的異常是完全繼承Throwable的,正如java doc里注釋的,無論你throw的還是JVM throw的,抑或是你想catch的,都必須繼承Throwable。我這里幫助大家糾正的第一個點就是:java.lang.Throwable是一個class,不是一個interface,千萬別被名字欺騙。優(yōu)秀的程序員有好的命名習(xí)慣,當(dāng)Java程序員默認(rèn)認(rèn)為帶有-able后綴的都該是接口時, Josh Bloch給大家上了一課——Throwable就是類。回到正題,Throwable有兩大子類,一個是java.lang.Error,一個是java.lang.Excpetion,Error是錯誤,一般多用于系統(tǒng)異常和底層錯誤,Error拋出就會導(dǎo)致程序終止;而Exception是異常,有程序引起,又分為受檢的checked和非受檢的unchecked(不知道哪本書這么翻譯的來著),受檢的異常是普通異常,就是你需要catch的來打日志或者補(bǔ)救的(這種做法叫吞掉異常),非受檢的多數(shù)是RuntimeException,就是運行時異常,這類異常不建議catch,因為這個是程序bug,需要被人發(fā)現(xiàn),所以建議拋出引起程序終止。Blah~blah~ 這些都是老生常談的話題,需要深入的點是有幾個:
第一,Error是建議到系統(tǒng)級別的錯誤的,包括虛擬機(jī)的,我們常見的就是java.lang.VirtualMachineError,這是一個JVM級別的抽象Error,如果你覺得沒見過?那么不用奇怪,它的兩個兒子你肯定見過:OutOfMemoryError和StackOverflowError。Error其實真沒好說的,一般情況不建議捕獲,程序員也用的較少,但是你看很多基礎(chǔ)框架或者系統(tǒng)軟件,都是有自定義Error的,所以當(dāng)你的工作層次或者范圍你能確定比較底層時,其實是可以自定義一些Error來控制程序的錯誤的。我這樣說可能也不是很好理解,換個簡單的話:你的架構(gòu)設(shè)計中需要考慮到異常的處理,那么首先要對異常定級別,如果有可能有偏底層的異常時,或者是本不該出現(xiàn)且不建議用戶(多數(shù)是依賴你的庫進(jìn)行開發(fā)的其他程序員)捕獲時,定義為Error是個不錯的選擇。當(dāng)然也不是說做上層開發(fā)的程序員就不能使用Error,只要你設(shè)計合理,你可以在必要時拋出Error來終止程序——比如提醒你的老板再不加薪就Error到死:)
第二,Exception分兩類這事幾乎人人皆知,受檢的異常往往是web后端開發(fā)或者服務(wù)開發(fā)自定義的業(yè)務(wù)異常比如BizServiceException或者常見的DAOException,這些異常在開發(fā)定義時總是直接extends Exception,而忽視了究竟這些異常我們對它們的期望是什么,這里我想強(qiáng)調(diào)一點,我們在業(yè)務(wù)系統(tǒng)架構(gòu)中考慮到異常機(jī)制,自定義的異常不是為了有異常而定義異常,一定對它本身是有期望的。我們對異常的一個基本期望是異常究竟該被誰捕獲,如果被你的服務(wù)下游捕獲,那么這必須是一個受檢的異常,如果是系統(tǒng)自身需要,那么這個我個人認(rèn)為是分階段設(shè)計的,在初期,也就是未發(fā)布狀態(tài),這些Exception應(yīng)該總是被拋出的,因為這樣可以快速的讓測試和質(zhì)量控制人員發(fā)現(xiàn)系統(tǒng)崩潰的點。在發(fā)布階段,異常可能需要被內(nèi)部消化,這時受檢異常就要提供給業(yè)務(wù)系統(tǒng),讓業(yè)務(wù)開發(fā)自行捕獲異常。當(dāng)然,好的系統(tǒng)架構(gòu)可能會把Exception作為一個內(nèi)部可見外部不可見的內(nèi)容,而基于此完全封裝一套error code對外,這應(yīng)該算是比較友好的做法了,也是很多API設(shè)計時的標(biāo)準(zhǔn)規(guī)范。畢竟對外部透明,不要讓用戶看到你的Exception,這是非常友好的做法。
第三,關(guān)于catch,就針對上面的第二點講,吞異常這事不是沒人干過,我們往往擔(dān)心系統(tǒng)錯誤而一個try catch 捕獲所有Exception,有的甚至不夠,還升一級,捕獲Throwable,這應(yīng)該是最糟糕的代碼設(shè)計(但不幸的是在我現(xiàn)在開發(fā)的系統(tǒng)和曾經(jīng)開發(fā)過的業(yè)務(wù)系統(tǒng)中,這類代碼非常普遍)。開發(fā)人員不應(yīng)該因為時間緊、趕進(jìn)度等接口而忽視Exception,就拿最上層的業(yè)務(wù)開發(fā)舉例,開發(fā)可能會調(diào)用各類服務(wù)、訪問數(shù)據(jù)庫、訪問緩存和文件系統(tǒng)等等,而這些服務(wù)必然包含了各種異常,而catch一個Exception,試圖通過吞噬異常保護(hù)系統(tǒng)或者頁面正常訪問,而打日志到后臺,通過分析日志來偷偷的解決bug……說起來真是汗毛倒豎。我的觀點:如果有錯誤,那么讓它盡早暴露出來,我們應(yīng)該通過盡量多的測試和優(yōu)化來避免錯誤,而不是偷偷的隱藏。事實也證明,日志里大量的NPE或者其他RuntimeException存在,但是無人問津,“系統(tǒng)不是好好的嗎?”,“頁面不是沒問題嗎”這樣的說辭可以讓開發(fā)看起來毫無責(zé)任,但是這為系統(tǒng)長期的維護(hù)和后續(xù)的擴(kuò)展都帶來了無盡的煩惱和坑。
綜上,我個人的經(jīng)驗告訴我?guī)c對待Exception的方法:
1,花時間了解涉及到的每個服務(wù)和方法所可能拋出的異常。事實往往是理解異常的關(guān)系和機(jī)制其實不花時間,了解后再開發(fā),你會比別人知道更多的異常點,這能保證你程序的健壯性;
2,無論你在服務(wù)開發(fā)還是服務(wù)使用層級,都要嘗試或者想到封裝異常,提供友好錯誤設(shè)計的方案,最簡單的是自定義一個業(yè)務(wù)Exception來封裝。
3,不要在你的方法開始try,結(jié)束時catch,這防御性太強(qiáng)了,很美品位。
4,前三點可能都是錯的,因為我自己也沒有完全實踐:)
posted on 2015-02-26 15:19 changedi 閱讀(7558) 評論(1) 編輯 收藏 所屬分類: Java技術(shù)