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