自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

瞧瞧別人家的異常處理,那叫一個(gè)優(yōu)雅

開發(fā) 前端
在我們?nèi)粘9ぷ髦?,?jīng)常會(huì)遇到一些異常,比如:NullPointerException、NumberFormatException、ClassCastException等等。那么問題來了,我們?cè)撊绾翁幚懋惓?,讓代碼變得更優(yōu)雅呢?

前言

在我們?nèi)粘9ぷ髦?,?jīng)常會(huì)遇到一些異常,比如:NullPointerException、NumberFormatException、ClassCastException等等。

那么問題來了,我們?cè)撊绾翁幚懋惓?,讓代碼變得更優(yōu)雅呢?

1.不要忽略異常

不知道你有沒有遇到過下面這段代碼:

反例:

Long id = null;
try {
   id = Long.parseLong(keyword);
} catch(NumberFormatException e) {
  //忽略異常
}

用戶輸入的參數(shù),使用Long.parseLong方法轉(zhuǎn)換成Long類型的過程中,如果出現(xiàn)了異常,則使用try/catch直接忽略了異常。

并且也沒有打印任何日志。

如果后面線上代碼出現(xiàn)了問題,有點(diǎn)不太好排查問題。

建議大家不要忽略異常,在后續(xù)的工作中,可能會(huì)帶來很多麻煩。

正例:

Long id = null;
try {
   id = Long.parseLong(keyword);
} catch(NumberFormatException e) {
  log.info(String.format("keyword:{} 轉(zhuǎn)換成Long類型失敗,原因:{}",keyword , e))
}

后面如果數(shù)據(jù)轉(zhuǎn)換出現(xiàn)問題,從日志中我們一眼就可以查到具體原因了。

2.使用全局異常處理器

有些小伙伴,經(jīng)常喜歡在Service代碼中捕獲異常。

不管是普通異常Exception,還是運(yùn)行時(shí)異常RuntimeException,都使用try/catch把它們捕獲。

反例:

try {
  checkParam(param);
} catch (BusinessException e) {
  return ApiResultUtil.error(1,"參數(shù)錯(cuò)誤");
}

在每個(gè)Controller類中都捕獲異常。

在UserController、MenuController、RoleController、JobController等等,都有上面的這段代碼。

顯然這種做法會(huì)造成大量重復(fù)的代碼。

我們?cè)贑ontroller、Service等業(yè)務(wù)代碼中,盡可能少捕獲異常。

這種業(yè)務(wù)異常處理,應(yīng)該交給攔截器統(tǒng)一處理。

在SpringBoot中可以使用@RestControllerAdvice注解,定義一個(gè)全局的異常處理handler,然后使用@ExceptionHandler注解在方法上處理異常。

例如:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 統(tǒng)一處理異常
     *
     * @param e 異常
     * @return API請(qǐng)求響應(yīng)實(shí)體
     */
    @ExceptionHandler(Exception.class)
    public ApiResult handleException(Exception e) {
        if (e instanceof BusinessException) {
            BusinessException businessException = (BusinessException) e;
            log.info("請(qǐng)求出現(xiàn)業(yè)務(wù)異常:", e);
            return ApiResultUtil.error(businessException.getCode(), businessException.getMessage());
        } 
        log.error("請(qǐng)求出現(xiàn)系統(tǒng)異常:", e);
        return ApiResultUtil.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服務(wù)器內(nèi)部錯(cuò)誤,請(qǐng)聯(lián)系系統(tǒng)管理員!");
    }

}

有了這個(gè)全局的異常處理器,之前我們?cè)贑ontroller或者Service中的try/catch代碼可以去掉。

如果在接口中出現(xiàn)異常,全局的異常處理器會(huì)幫我們封裝結(jié)果,返回給用戶。

3.盡可能捕獲具體異常

在你的業(yè)務(wù)邏輯方法中,有可能需要去處理多種不同的異常。

你可能你會(huì)覺得比較麻煩,而直接捕獲Exception。

反例:

try {
   doSomething();
} catch(Exception e) {
  log.error("doSomething處理失敗,原因:",e);
}

這樣捕獲異常太籠統(tǒng)了。

其實(shí)doSomething方法中,會(huì)拋出FileNotFoundException和IOException。

這種情況我們最好捕獲具體的異常,然后分別做處理。

正例:

try {
   doSomething();
} catch(FileNotFoundException e) {
  log.error("doSomething處理失敗,文件找不到,原因:",e);
} catch(IOException e) {
  log.error("doSomething處理失敗,IO出現(xiàn)了異常,原因:",e);
}

這樣如果后面出現(xiàn)了上面的異常,我們就非常方便知道是什么原因了。

4.在finally中關(guān)閉IO流

我們?cè)谑褂肐O流的時(shí)候,用完了之后,一般需要及時(shí)關(guān)閉,否則會(huì)浪費(fèi)系統(tǒng)資源。

我們需要在try/catch中處理IO流,因?yàn)榭赡軙?huì)出現(xiàn)IO異常。

反例:

try {
    File file = new File("/tmp/1.txt");
    FileInputStream fis = new FileInputStream(file);
    byte[] data = new byte[(int) file.length()];
    fis.read(data);
    for (byte b : data) {
        System.out.println(b);
    }
    fis.close();
} catch (IOException e) {
    log.error("讀取文件失敗,原因:",e)
}

上面的代碼直接在try的代碼塊中關(guān)閉fis。

假如在調(diào)用fis.read方法時(shí),出現(xiàn)了IO異常,則可能會(huì)直接拋異常,進(jìn)入catch代碼塊中,而此時(shí)fis.close方法沒辦法執(zhí)行,也就是說這種情況下,無法正確關(guān)閉IO流。

正例:

FileInputStream fis = null;
try {
    File file = new File("/tmp/1.txt");
    fis = new FileInputStream(file);
    byte[] data = new byte[(int) file.length()];
    fis.read(data);
    for (byte b : data) {
        System.out.println(b);
    } 
} catch (IOException e) {
    log.error("讀取文件失敗,原因:",e)
} finally {
   if(fis != null) {
      try {
          fis.close();
          fis = null;
      } catch (IOException e) {
          log.error("讀取文件后關(guān)閉IO流失敗,原因:",e)
      }
   }
}

在finally代碼塊中關(guān)閉IO流。

但要先判斷fis不為空,否則在執(zhí)行fis.close()方法時(shí),可能會(huì)出現(xiàn)NullPointerException異常。

需要注意的地方時(shí),在調(diào)用fis.close()方法時(shí),也可能會(huì)拋異常,我們還需要進(jìn)行try/catch處理。

5.多用try-catch-resource

前面在finally代碼塊中關(guān)閉IO流,還是覺得有點(diǎn)麻煩。

因此在JDK7之后,出現(xiàn)了一種新的語法糖try-with-resource。

上面的代碼可以改造成這樣的:

File file = new File("/tmp/1.txt");
try (FileInputStream fis = new FileInputStream(file)) {
    byte[] data = new byte[(int) file.length()];
    fis.read(data);
    for (byte b : data) {
        System.out.println(b);
    }
} catch (IOException e) {
    e.printStackTrace();
    log.error("讀取文件失敗,原因:",e)
}

try括號(hào)里頭的FileInputStream實(shí)現(xiàn)了一個(gè)AutoCloseable接口,所以無論這段代碼是正常執(zhí)行完,還是有異常往外拋,還是內(nèi)部代碼塊發(fā)生異常被截獲,最終都會(huì)自動(dòng)關(guān)閉IO流。

我們盡量多用try-catch-resource的語法關(guān)閉IO流,可以少寫一些finally中的代碼。

而且在finally代碼塊中關(guān)閉IO流,有順序的問題,如果有多種IO,關(guān)閉的順序不對(duì),可能會(huì)導(dǎo)致部分IO關(guān)閉失敗。

而try-catch-resource就沒有這個(gè)問題。

6.不在finally中return

我們?cè)谀硞€(gè)方法中,可能會(huì)有返回?cái)?shù)據(jù)。

反例:

public int divide(int dividend, int divisor) {
    try {
        return dividend / divisor;
    } catch (ArithmeticException e) {
        // 異常處理
    } finally {
        return -1;
    }
}

上面的這個(gè)例子中,我們?cè)趂inally代碼塊中返回了數(shù)據(jù)-1。

這樣最后在divide方法返回時(shí),會(huì)將dividend / divisor的值覆蓋成-1,導(dǎo)致正常的結(jié)果也不對(duì)。

我們盡量不要在finally代碼塊中返回?cái)?shù)據(jù)。

正解:

public int divide(int dividend, int divisor) {
    try {
        return dividend / divisor;
    } catch (ArithmeticException e) {
        // 異常處理
        return -1;
    }
}

如果dividend / divisor出現(xiàn)了異常,則在catch代碼塊中返回-1。

7.少用e.printStackTrace()

我們?cè)诒镜亻_發(fā)中,喜歡使用e.printStackTrace()方法,將異常的堆棧跟蹤信息輸出到標(biāo)準(zhǔn)錯(cuò)誤流中。

反例:

try {
   doSomething();
} catch(IOException e) {
  e.printStackTrace();
}

這種方式在本地確實(shí)容易定位問題。

但如果代碼部署到了生產(chǎn)環(huán)境,可能會(huì)帶來下面的問題:

  1. 可能會(huì)暴露敏感信息,如文件路徑、用戶名、密碼等。
  2. 可能會(huì)影響程序的性能和穩(wěn)定性。

正解:

try {
   doSomething();
} catch(IOException e) {
  log.error("doSomething處理失敗,原因:",e);
}

我們要將異常信息記錄到日志中,而不是保留給用戶。

8.異常打印詳細(xì)一點(diǎn)

我們?cè)诓东@了異常之后,需要把異常的相關(guān)信息記錄到日志當(dāng)中。

反例:

try {
   double b = 1/0;
} catch(ArithmeticException e) {
    log.error("處理失敗,原因:",e.getMessage());
}

這個(gè)例子中使用e.getMessage()方法返回異常信息。

但執(zhí)行結(jié)果為:

doSomething處理失敗,原因:

這種情況異常信息根本沒有打印出來。

我們應(yīng)該把異常信息和堆棧都打印出來。

正例:

try {
   double b = 1/0;
} catch(ArithmeticException e) {
    log.error("處理失敗,原因:",e);
}

執(zhí)行結(jié)果:

doSomething處理失敗,原因:
java.lang.ArithmeticException: / by zero
 at cn.net.susan.service.Test.main(Test.java:16)

將具體的異常,出現(xiàn)問題的代碼和具體行數(shù)都打印出來。

9.別捕獲了異常又馬上拋出

有時(shí)候,我們?yōu)榱擞涗浫罩?,可能?huì)對(duì)異常進(jìn)行捕獲,然后又拋出。

反例:

try {
  doSomething();
} catch(ArithmeticException e) {
  log.error("doSomething處理失敗,原因:",e)
  throw e;
}

在調(diào)用doSomething方法時(shí),如果出現(xiàn)了ArithmeticException異常,則先使用catch捕獲,記錄到日志中,然后使用throw關(guān)鍵拋出這個(gè)異常。

這個(gè)騷操作純屬是為了記錄日志。

但最后發(fā)現(xiàn)日志記錄兩次。

因?yàn)樵诤罄m(xù)的處理中,可能會(huì)將這個(gè)ArithmeticException異常又記錄一次。

這樣就會(huì)導(dǎo)致日志重復(fù)記錄了。

10.優(yōu)先使用標(biāo)準(zhǔn)異常

在Java中已經(jīng)定義了許多比較常用的標(biāo)準(zhǔn)異常,比如下面這張圖中列出的這些異常:

反例:

public void checkValue(int value) {
    if (value < 0) {
        throw new MyIllegalArgumentException("值不能為負(fù)");
    }
}

自定義了一個(gè)異常表示參數(shù)錯(cuò)誤。

其實(shí),我們可以直接復(fù)用已有的標(biāo)準(zhǔn)異常。

正例:

public void checkValue(int value) {
    if (value < 0) {
        throw new IllegalArgumentException("值不能為負(fù)");
    }
}

11.對(duì)異常進(jìn)行文檔說明

我們?cè)趯懘a的過程中,有一個(gè)好習(xí)慣是給方法、參數(shù)和返回值,增加文檔說明。

反例:

/*  
 *  處理用戶數(shù)據(jù)
 *  @param value 用戶輸入?yún)?shù)
 *  @return 值 
 */
public int doSomething(String value) 
     throws BusinessException {
     //業(yè)務(wù)邏輯
     return 1;
}

這個(gè)doSomething方法,把方法、參數(shù)、返回值都加了文檔說明,但異常沒有加。

正解:

/*  
 *  處理用戶數(shù)據(jù)
 *  @param value 用戶輸入?yún)?shù)
 *  @return 值
 *  @throws BusinessException 業(yè)務(wù)異常
 */
public int doSomething(String value) 
     throws BusinessException {
     //業(yè)務(wù)邏輯
     return 1;
}

拋出的異常,也需要增加文檔說明。

12.別用異??刂瞥绦虻牧鞒?/span>

我們有時(shí)候,在程序中使用異常來控制了程序的流程,這種做法其實(shí)是不對(duì)的。

反例:

Long id = null;
try {
   id = Long.parseLong(idStr);
} catch(NumberFormatException e) {
   id = 1001;
}

如果用戶輸入的idStr是Long類型,則將它轉(zhuǎn)換成Long,然后賦值給id,否則id給默認(rèn)值1001。

每次都需要try/catch還是比較影響系統(tǒng)性能的。

正例:

Long id = checkValueType(idStr) ? Long.parseLong(idStr) : 1001;

我們?cè)黾恿艘粋€(gè)checkValueType方法,判斷idStr的值,如果是Long類型,則直接轉(zhuǎn)換成Long,否則給默認(rèn)值1001。

13.自定義異常

如果標(biāo)準(zhǔn)異常無法滿足我們的業(yè)務(wù)需求,我們可以自定義異常。

例如:

/**
 * 業(yè)務(wù)異常
 *
 * @author 蘇三
 * @date 2024/1/9 下午1:12
 */
@AllArgsConstructor
@Data
public class BusinessException extends RuntimeException {

    public static final long serialVersionUID = -6735897190745766939L;

    /**
     * 異常碼
     */
    private int code;

    /**
     * 具體異常信息
     */
    private String message;

    public BusinessException() {
        super();
    }

    public BusinessException(String message) {
        this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
        this.message = message;
    }
}

對(duì)于這種自定義的業(yè)務(wù)異常,我們可以增加code和message這兩個(gè)字段,code表示異常碼,而message表示具體的異常信息。

BusinessException繼承了RuntimeException運(yùn)行時(shí)異常,后面處理起來更加靈活。

提供了多種構(gòu)造方法。

定義了一個(gè)序列化ID(serialVersionUID)。

責(zé)任編輯:姜華 來源: 蘇三說技術(shù)
相關(guān)推薦

2025-04-22 08:20:51

2024-11-12 08:20:31

2025-04-08 08:20:33

2022-12-12 08:14:47

2024-12-02 00:59:30

Spring

2025-03-06 08:21:02

判空entity對(duì)象

2025-03-11 08:20:58

2025-02-28 08:21:00

2020-11-03 16:00:33

API接口微服務(wù)框架編程語言

2015-09-24 09:22:16

nodejs頁面始末

2017-11-12 21:32:52

戴爾

2016-01-08 09:49:19

DockerDocker案例云應(yīng)用開發(fā)

2020-11-17 09:34:31

API接口后端

2017-09-22 13:22:59

大數(shù)據(jù)南京大學(xué)宿舍

2023-12-30 20:04:51

MyBatis框架數(shù)據(jù)

2021-07-14 06:31:08

京東互聯(lián)網(wǎng)加薪

2021-01-20 05:42:27

RabbitMQMQ vhost

2017-06-13 14:15:51

戴爾協(xié)同計(jì)算汽車神話

2019-06-11 09:35:50

戴爾

2020-03-16 17:20:02

異常處理Spring Boot
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)