1. 程式人生 > >Java Exception最佳實踐(轉)

Java Exception最佳實踐(轉)

理解 異常 resource 開發 lock 結束線程 用戶名 文檔 each

https://www.dubby.cn/detail.html?id=9033

  • 1.異常介紹
  • 2.Java中的異常介紹
  • 3.自定義異常
  • 4.幾個建議
    • 1)不要生吞異常
    • 2)申明具體的異常
    • 3)盡可能的捕獲具體異常
    • 4)永遠不要捕獲Throwable
    • 5)不要丟失異常信息
    • 6)日誌和上拋不可兼得
    • 7)不要在finally裏拋異常
    • 8)不要為了捕獲而捕獲
    • 9)不要使用printStackTrace()或類似的語句
    • 10)不一定要catch
    • 11)“Throw early catch late”
    • 12)記得用finall善後
    • 13)上拋信息明確的異常
    • 14)永遠不要使用異常來做流程控制
    • 15)盡早校驗輸入
    • 16)一條信息打印異常
    • 17)讓你的異常信息更充實
    • 18)如果線程被interrupted一定要結束線程
    • 19)使用模板來減少重復的try-catch代碼
    • 20)文檔中加上異常說明

本篇文章主要給大家介紹一些眾所周知的異常處理原則,但是也有部分鮮為人知,但也很有用的原則,希望能引發各位對異常處理的思考,以及在開發過程中,寫出更優美的代碼。

1.異常介紹

大致可以把異常分成三種情況下的異常(不正常情況):

  1. 代碼錯誤引發的異常:比如數組越界,空指針等。
  2. 客戶端錯誤調用引發的異常:比如用戶名最長只允許32,客戶端傳了100;方法參數不能為空,客戶端傳了空等。
  3. 資源錯誤引發的異常:比如網絡錯誤,硬盤故障,文件被刪等。

2.Java中的異常介紹

無圖言屌,下面就給出異常的繼承關系圖:

技術分享圖片

主要說說這幾個概念:

  • Checked exceptions:這種異常在代碼層面必須要捕獲或者在簽名處申明這個異常。這種異常是Java強制你必須捕獲,因為這些一般一般是不可避免的,比如:網絡,文件系統等不可控因素。
  • Unchecked exceptions:這種異常不會強制捕獲或者在簽名處申明。這類異常一般是由於代碼問題產生的,比如:數組越界,空指針等。
  • Errors:這類錯誤,一般是在軟件層面不可恢復的。比如:OutOfMemoryError, LinkageError, 還有StackOverflowError。這種錯誤一般會是的程序(或者程序的一部分)不可用。針對這類錯誤,一定要有一個良好的日誌習慣,不然很難定位。

3.自定義異常

一般我們想要自定義異常的目的都是為了讓異常信息更豐富,比如:輸入名稱不合法,我們可能希望有一個UsernameInvalidException。本人在代碼中曾經這麽幹過,一個類似的用法,好處很明顯,在我們的內部監控系統中,對異常統計界面可以很清晰的反映出是什麽問題,但是也帶來一個問題,那就是異常數量很多。

在這裏我先擺幾個大師的意見吧:

  • 不要使用自定義異常:
    Java已經給我們提供了很多很多異常,盡量復用這些異常,好處有:減少我們的代碼量,也就減少了維護的成本和精力,不至於讓代碼中出現很多只用過一次或幾次的異常,最後異常數量爆炸(這就是我遇到的);使用通用異常,也可以減少別人閱讀我們的代碼,使用我們的接口時,更輕松,畢竟多一個類我們就需要理解這個類存在的意義。這裏給幾個經常可以用到的異常:
    1. IllegalStateException
    2. UnsupportedOperationException
    3. IllegalArgumentException
    4. NoSuchElementException
    5. NullPointerException
  • 如果不得不自定義異常,那就寫個通用異常:
    如果自己不得不寫的話,那就寫的詳細一下,不要只有個String來傳達信息,那完全可以用通用的異常來替代,給個包含詳細信息的例子:
  1. public class OutOfRangeException
  2. extends IllegalArgumentException {
  3. private final long value, min, max;
  4. public OutOfRangeException(long value, long min, long max) {
  5. super("Value " + value + " out of range " +
  6. "[" + min + ".." + max + "]");
  7. this.value = value;
  8. this.min = min;
  9. this.max = max;
  10. }
  11. public long getValue() {
  12. return value;
  13. }
  14. public long getMin() {
  15. return min;
  16. }
  17. public long getMax() {
  18. return max;
  19. }
  20. }

4.幾個建議

1)不要生吞異常

  1. catch (NoSuchMethodException e) {
  2. return null;
  3. }

這樣做會讓這個異常信息永遠的丟失,你將無法知道這個異常的原因,怎麽去解決這個異常,甚至你不知道有這個異常的存在。

2)申明具體的異常

  1. public void foo() throws Exception { //不正確的做法
  2. }

這樣除了告訴調用方我可能會有異常之外沒有任何其他信息,而事實是我們本可以提供更具體的信息,建議這樣做:

  1. public void foo() throws SpecificException1, SpecificException2 { //正確的做法
  2. }

3)盡可能的捕獲具體異常

  1. try {
  2. someMethod();
  3. } catch (Exception e) {
  4. LOGGER.error("method has failed", e);
  5. }

這麽做的問題是,如果你調用的方法中多了一個新的異常,他本來的目的是希望你處理這個新的異常,可是因為你在這裏捕獲了所有的異常,你可能會忽略這個提醒,而忘記捕獲。

4)永遠不要捕獲Throwable

這樣會捕獲本來不該有我們來處理的錯誤,包括一些我們的代碼無法處理的錯誤。

5)不要丟失異常信息

  1. catch (NoSuchMethodException e) {
  2. throw new MyServiceException("Some information"); //不正確
  3. }

這樣做會完全丟失異常信息。

  1. catch (NoSuchMethodException e) {
  2. throw new MyServiceException("Some information: " + e.getMessage()); //不正確
  3. }

這種做法會丟失堆棧信息,建議:

  1. catch (NoSuchMethodException e) {
  2. throw new MyServiceException("Some information: " , e); //正確
  3. }

6)日誌和上拋不可兼得

  1. catch (NoSuchMethodException e) {
  2. LOGGER.error("Some information", e);
  3. throw e;
  4. }

這樣會導致一個問題,就是一個異常會有多份日誌,因為上層可能也會記一次日誌。所以要麽上拋,要麽記日誌,不要都做。

7)不要在finally裏拋異常

  1. try {
  2. someMethod(); //Throws exceptionOne
  3. } finally {
  4. cleanUp(); //If finally also threw any exception the exceptionOne will be lost forever
  5. }

這樣的問題是,如果finally裏也拋異常,就會導致真正的異常信息丟失,你只會收到finally裏拋的異常。

8)不要為了捕獲而捕獲

  1. catch (NoSuchMethodException e) {
  2. throw e; //Avoid this as it doesn‘t help anything
  3. }

這段代碼沒有任何有意義,你可以直接上拋。

9)不要使用printStackTrace()或類似的語句

這種輸出沒有任何意義,而且不確定輸出路徑,對定位問題沒有幫助。

10)不一定要catch

  1. try {
  2. someMethod(); //Method 2
  3. } finally {
  4. cleanUp(); //do cleanup here
  5. }

如果你只是想要finally來做善後,那就只用它就可以了,不要用catch。

11)“Throw early catch late”

這句話我不想翻譯,因為我希望你能看到這句話,以後你也會見到這句話的。Throw early catch late。在底層拋異常,在信息足夠的時候來捕獲並處理。

12)記得用finall善後

比如數據庫連接,一定要用finally關閉連接。當然你也可以用try-with-resource的方式。

13)上拋信息明確的異常

如果這個方法是解析文件,那麽FileNotFoundException就比NullPointException更明確。

14)永遠不要使用異常來做流程控制

  1. public void useExceptionsForFlowControl() {
  2. try {
  3. while (true) {
  4. increaseCount();
  5. }
  6. } catch (MaximumCountReachedException ex) {
  7. }
  8. //Continue execution
  9. }
  10. public void increaseCount()
  11. throws MaximumCountReachedException {
  12. if (count >= 5000)
  13. throw new MaximumCountReachedException();
  14. }

算我求你了,不要這麽幹!

15)盡早校驗輸入

很多異常都是由不合法的輸入引起的,所以盡可能早的校驗輸入。

16)一條信息打印異常

  1. LOGGER.debug("Using cache sector A");
  2. LOGGER.debug("Using retry sector B");

何必呢?而且這樣也容易誤導其他人,建議:

  1. LOGGER.debug("Using cache sector A, using retry sector B");

17)讓你的異常信息更充實

包括堆棧和其他提示信息。

18)如果線程被interrupted一定要結束線程

  1. while (true) {
  2. try {
  3. Thread.sleep(100000);
  4. } catch (InterruptedException e) {} //Don‘t do this
  5. doSomethingCool();
  6. }

這段代碼很cool,但是一般會interrupt線程,要麽是超時了,要麽是線程池被關閉了,所以你應該盡可能的去結束線程。

19)使用模板來減少重復的try-catch代碼

  1. class DBUtil{
  2. public static void closeConnection(Connection conn){
  3. try{
  4. conn.close();
  5. } catch(Exception ex){
  6. //Log Exception - Cannot close connection
  7. }
  8. }
  9. }

使用這個來減少每次都try一遍。

20)文檔中加上異常說明

  1. /**
  2. * This method does something extremely useful ...
  3. *
  4. * @param input
  5. * @throws MyBusinessException if ... happens
  6. */
  7. public void doSomething(String input) throws MyBusinessException { ... }

前人種樹後人乘涼,你可能也可以乘涼。

Java Exception最佳實踐(轉)