使用異常枚舉類讓if( condition ){ throw Exception }變的簡潔點吧

現在大多數的Web接口項目,對於一些業務異常處理,都是直接以Exception 的方式向外面拋出,再定義一個全局異常捕獲器(ControllerAdvice) 進行統一的處理

背景

先來看一段非空判斷拋出異常的代碼

// 查詢用戶id是否有效
	User user = userService.findUserById(1);
	if(user==null){
		throw new GenericBusinessException(ResponseErrorEnum.USER_NOT_EXIST);
	}
	//如果用戶存在,嘗試修改token...
	// 其它有關用戶操作,在此省略....

相信在項目中,這種類似判斷的代碼有很多,思考以下,如果很多地方都要判斷用戶id是否有效,那這段代碼應該要寫很多遍吧.

爲了做一名積極向上的搬運工,我覺得還是要想辦法解決下這個問題.

知識點提前知曉

接下去的實現方法裏,涉及到:

  • JDK8 引入的interface 裏可以編寫default 默認方法 .emm ,要知道以前接口裏是沒有方法體的 , 每個接口實現類都必須要重寫接口定義的全部方法,即使,有些接口你都沒想法去實現,這樣,你也還是要寫個空方法體哇哇哇.
  • 由Spring出品的spring-core裏的Assert 類,直接看notNull的源碼吧,很簡單
    public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }

斷言: 可以看作是異常處理的一種高級形式。

解決辦法

核心就是參考Assert ,讓異常枚舉類也有斷言的功能.

先看下最後效果:

	//ResponseEnum.CAN_NOT_IS_NULL : 枚舉類
   @Test
    public void testAssert() throws Exception {
        User user = null;
        ResponseEnum.CAN_NOT_IS_NULL.assertNotNull(user);
    }    

看下控制檯的日誌:
在這裏插入圖片描述
開始寫代碼

  • (1) Assert 基類 : 規定一些基本特性
public interface MyAssert {
    //具體拋出什麼類型的異常,由子類重寫
    Exception newException(Object... args);
    //共通判斷-對象空指針
    default void assertNotNull(Object obj) throws Exception {
        if (obj == null) {
            throw newException(obj);
        }
    }
    //共通判斷-集合不爲空
    default void assertNotListEmpty(List<?> list) throws Exception {
        if (list == null || list.isEmpty()) {
            throw newException(list);
        }
    }
}
  • (2) 枚舉接口 : 爲後續的 業務Assert 類服務 . 提供錯誤信息 (code + message)
public interface IResponseEnum {
    int getCode(); // 異常code
    String getMessage();//異常message
}
  • (3) 業務Assert 類 : 處理一些非共通的異常 , 並 自定義Exception
public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {
    //jdk1.8特性:
    // (1)實現類不需要自己再重寫default方法,會自動繼承該方法 。按照1.8之前的,接口裏的每個方法,實現類都是需要重寫的。
    //  (2)同時帶來的多繼承中的一個二義性問題:如果一個類實現了兩個接口(可以看做是“多繼承”),這兩個接口又同時都包含了一個名字相同的default方法,那麼會發生什麼情況? 在這樣的情況下,編譯器會報錯。
    default GeneraBusinessException newException(Object... args) {
        //getMessage() 調用的是 IResponseEnum
        String msg = MessageFormat.format(this.getMessage(), args);
        return new GeneraBusinessException(this, args, msg);
    }

    //這裏可以進行拓展, 更具體化的業務異常判斷
    default void assertUserIsValid(User user) throws GeneraBusinessException {
        if (user.getAge() <= 0 || StringUtils.isEmpty(user.getUsername())) {
            throw newException(user.getAge());
        }
    }

    //每個assert方法都是對應每個ResponseEnum 的邏輯判斷,通過這種方式,主要是爲了減少業務代碼裏的很多if(和枚舉相關的判斷)
    //所以,枚舉類型越多,這裏的assert方法也就越多
}
  • (4) 異常枚舉類 : 最亮的點 ,繼承了BusinessExceptionAssert 接口,卻只要實現IResponseEnum的兩個方法, 因爲其它方法,接口已提供了default 方法. 強大的JDK8 .
@Getter
@AllArgsConstructor
public enum ResponseEnum implements BusinessExceptionAssert {
    //{x}佔位符
    CAN_NOT_IS_NULL(1001, "不能爲空"),
    ;
    /**
     * 異常碼
     */
    private int code;
    /**
     * 異常消息
     */
    private String message;
}
  • (5) 單元測試
   @Test
    public void testAssert() throws Exception {
        User user = null;
        ResponseEnum.CAN_NOT_IS_NULL.assertNotNull(user);
    }  

思考優缺點

優點:
(1) 高頻率出現的判斷邏輯都放到Assert 裏 , 降低了和其它業務邏輯的耦合性. 假如判斷邏輯突然發生了改變, 這樣 只需要修改 Assert 裏的判斷即可 . 按照最初的 if 寫法, 可能修改代碼的地方不止1處啊啊啊啊啊;
(2) 代碼看起來更簡潔, 畢竟少寫了幾行 或者 幾十行的 if (…){…} , 心情應該不是特別差吧;

缺點:
(1) emm…我覺得 可能就是可讀性不是特別好, 大多數的人第一次看到這種寫法應該想罵人吧~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章