如何用断言思路优雅地处理代码异常

目录

1、Assert(断言)

2、如何自定义断言

2.1 自定义断言接口

2.2 自定义响应枚举接口

2.3 自定义响应枚举接口

2.4 基础异常信息封装

2.5 业务异常信息封装

2.6 业务断言封装

2.7 测试类


1、Assert(断言)

Assert大家都应该很熟悉了,比如在Spring项目中,单元测试经常会使用到 org.springframework.util.Assert包。

断言处理异常的作用:让编码异常处理更优雅。

以优雅的 Assert方式来校验业务异常情况,可以更多的让开发人员只关注业务逻辑,而不必花费大量精力写冗余的if else和 try catch 代码块。通过Assest的方式可以消灭 90% 以上的 try catch 代码块。

看看下面两段代码,你觉得哪个更丝滑?

 public void testOrder2() {
       // ... 省略
       Order order = orderDao.selectById(orderId);
       Assert.notNull(order, "订单不存在.");
 }
public void testOrder2() {
    // ... 省略
    Order order = orderDao.selectById(orderId);
    if (order == null) {
     throw new IllegalArgumentException("订单不存在.");
    }
 }

第一种判定非空的写法相对比较优雅,而第二种写法则用 if {...} 代码块,显的冗余拖沓。那么 Assert.notNull() 底层的是怎么实现的呢?

public abstract class Assert {
    public Assert() {
    }
   public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }
}

可以看到,Assert 其实就是帮我们把 if {...} 封装了一下。虽然简单,但不可否认的是,编码体验会上升很多。

基于这样的思路,我们不妨模仿org.springframework.util.Assert写一个断言类,不过断言失败后抛出的异常不是 IllegalArgumentException 这些内置异常,而是我们自己定义的异常。

2、如何自定义断言

2.1 自定义断言接口

自定义断言接口,封装异常类型。

package com.example.demo.exception;

/**
 * 自定义断言
 */
public interface CustomAssert {

    BaseException newException();

    /**
     * 创建异常
     *
     * @param args
     * @return
     */
    BaseException newException(Object... args);

    /**
     * 创建异常
     *
     * @param t
     * @param args
     * @return
     */
    BaseException newException(Throwable t, Object... args);
    /**
     * 断言对象obj非空。如果对象obj为空,则抛出异常
     */
    default void assertNotNull() {
        throw newException();
    }

    /**
     * 断言对象obj非空。如果对象obj为空,则抛出异常
     *
     * @param obj 待判断对象
     */
    default void assertNotNull(Object obj) {
        if (obj == null) {
            throw newException(obj);
        }
    }

    /**
     * 断言对象obj非空。如果对象obj为空,则抛出异常
     * 异常信息message支持传递参数方式,避免在判断之前进行字符串拼接操作
     * @param obj  待判断对象
     * @param args message占位符对应的参数列表
     */
    default void assertNotNull(Object obj, Object... args) {
        if (obj == null) {
            throw newException(args);
        }
    }
}

当断言失败后,抛出的异常不是具体的某个异常,而是交由 2 个 newException 接口方法提供。因为业务逻辑中出现的异常基本都是对应特定的场景,比如根据订单id 获取订单信息,查询结果为null,此时抛出的异常可能为 OrderNotFoundException,并且有特定的异常码(比如 100)和异常信息“订单信息不存在”。

所以具体抛出什么异常,由 CustomAssert 的实现类决定。

2.2 自定义响应枚举接口

定义统一的返回异常码和异常信息。将code和message定位为枚举类型,便于异常属性统一管理和参数传递。​​​​​​​


package com.example.demo.exception;

/**
 * 响应信息枚举值模板
 **/
public interface IResponseEnum {
    /**
     * 错误编码
     * @return 错误编码
     */
    int getCode();

    /**
     * 错误信息
     * @return 错误信息
     */
    String getMessage();

}

2.3 自定义响应枚举接口

后续业务基于这个枚举实现扩展所需异常类型即可。​​​​​​​

package com.example.demo.exception;

/**
 * 可扩展异常枚举
 */
public enum ResponseEnum implements BusinessExceptionAssert{

    /** 系统异常 **/
    SYSTEM_EXCEPTION(100, "系统异常"),
    /** 对象不能为空 **/
    OBJECT_IS_NULL(200, "{0}")
    ;

    /**
     * 返回码
     */
    private int code;
    /**
     * 返回消息
     */
    private String message;

    ResponseEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public int getCode() {
        return this.code;
    }

    @Override
    public String getMessage() {
        return this.message;
    }
}

2.4 基础异常信息封装

统一返回异常类。

package com.example.demo.exception;

/**
 * 基础异常信息封装
 */
public class BaseException extends RuntimeException {
    /**
     * 错误码
     */
    private int errorCode;

    /**
     * 错误信息
     */
    private String errorMessage;

    /**
     * 传参
     */
    private Object errorResult;

    public BaseException(IResponseEnum iResponseEnum, String msg) {
        super(msg);
        this.errorCode = iResponseEnum.getCode();
        this.errorMessage = iResponseEnum.getMessage();
        this.errorResult = msg;
    }

    public BaseException(IResponseEnum iResponseEnum, Object[] args, String msg) {
        super(msg);
        this.errorCode = iResponseEnum.getCode();
        this.errorMessage = iResponseEnum.getMessage();
        this.errorResult = msg;
    }

    public BaseException(IResponseEnum iResponseEnum, Object[] args, String msg, Throwable t) {
        super(msg);
        this.errorCode = iResponseEnum.getCode();
        this.errorMessage = iResponseEnum.getMessage();
        this.errorResult = msg;
    }

    public BaseException(IResponseEnum iResponseEnum) {
        super(iResponseEnum.getMessage());
        this.errorCode = iResponseEnum.getCode();
        this.errorMessage = iResponseEnum.getMessage();
    }

    public int getErrorCode() {
        return errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public Object getErrorResult() {
        return errorResult;
    }
}

2.5 业务异常信息封装

业务异常实现类。

package com.example.demo.exception;

/**
 * 业务异常
 **/
public class BusinessException extends BaseException {
    private static final long serialVersionUID = 1L;
    public BusinessException(IResponseEnum resp, Object[] args, String msg, Throwable t) {
        super(resp, null);
    }
    public BusinessException(IResponseEnum resp, String os) {
        super(resp, os);
    }
    public BusinessException(BusinessExceptionAssert businessExceptionAssert, Object[] args, String msg) {
        super(businessExceptionAssert, args, msg);
    }
    public BusinessException(BusinessExceptionAssert businessExceptionAssert, Object[] args, String msg, Throwable t) {
        super(businessExceptionAssert, args, msg, t);
    }
}

2.6 业务断言封装

package com.example.demo.exception;

import java.text.MessageFormat;

public interface BusinessExceptionAssert extends IResponseEnum, CustomAssert {

    @Override
    default BaseException newException() {
        return new BusinessException(this, this.getMessage());
    }

    @Override
    default BaseException newException(Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);
        return new BusinessException(this, args, msg);
    }

    @Override
    default BaseException newException(Throwable t, Object... args) {
        String msg = MessageFormat.format(this.getMessage(), args);
        return new BusinessException(this, args, msg, t);
    }

}

2.7 测试类

package com.example.demo.exception;
/**
 * 测试方法
 */
public class TestMain {
    public static void main(String[] args) {
//        ResponseEnum.SYSTEM_EXCEPTION.assertNotNull(); // 系统异常
        ResponseEnum.OBJECT_IS_NULL.assertNotNull(null,"订单对象不能为空"); //对象不为空
    }
}

总结:我们可以通过上面的例子看到,实际业务代码基本不需要冗余太多的try catch块了。以上自定义断言主要参考了Spring中Assets的实现方式,对业务中经常使用的异常处理方式进行了很友好的封装。另外对于不同业务场景的异常来说,统一通过异常枚举类型来管理,也体现了很好的扩展性。

关注公众号,免费领取全部面试资料!  

猜你喜欢

转载自blog.csdn.net/wangyongfei5000/article/details/125796241