异常分类的最佳实践?构建合理的异常体系

前言

在上一篇文章中,我们阐述了如何优雅地处理异常,从创建、抛出到处理异常的完整链路中提供一种处理方案。在本文中,将尝试以实际开发经验对如何分类异常,构建一个合理的异常体系进行最佳实践。

为什么要自定义异常

在探究异常如何分类时,先思考下为什么要自定义异常类?我认为有以下几点原因

  1. 原始异常提供的信息载体较少。通常仅有Message、Cause可用,自定义用于扩展信息载体,如增加异常码;
  2. 原始异常对用户不友好。通过定义一个业务异常类来说明业务异常,并携带用户友好的提示引导用户执行正确的操作;
  3. 对不同的异常进行针对性的操作。像一些用于提示用户的异常,如密码错误、权限不足等应返回给用户查看,且该类异常并不需要进行额外处理。而对于系统出现的底层异常,如数据库语句执行失败,该类异常需要打印详细的现场日志信息,和对异常携带的信息转换后返回给用户。

系统异常很多开发者没有重视,经常会看到界面错误弹框携带了系统底层信息,如接口参数解析异常

这种情况就会泄露系统底层信息,在信息安全方面,这是一个中危漏洞,归根到底就是没对系统异常进行转换,只是简单地在异常处理器中把原始异常信息返回了。

总得来说,自定义异常对象是为了更好的对异常情况进行针对性的处理,同时提高代码的可读性。

异常如何分类

在系统中,我们通常根据业务场景会细分出认证类、业务类、系统类等异常。但本人认为不管异常怎么细分,都能够归属到两类异常,一是让用户看的,即业务异常;二是让开发人员看到,即系统异常。

业务异常

编写业务代码时,当业务逻辑未按照预定执行时,系统应抛出一个对用户友好的异常给用户,引导用户执行正确的操作,最基础的场景就是登陆时密码错误:

java 复制代码
if(用户密码不匹配){
    throw new BusinessException("密码错误,请重新输入");
}

这类可预期的,用于创建对用户友好提示且能自行处理的异常,我将其称为业务异常。该类异常由统一异常处理器处理后,应将携带的异常信息返回给用户,因为这是业务信息。

系统异常

对于各种用户无法处理的异常,通常是系统错误的运行(BUG),该类异常通常需要开发人员解决。出现该类异常后,应打印详细的异常信息(现场信息),并在必要时进行报警,及时通知开发人员处理,同时携带的异常信息应转换成用户友好的提示,如"系统出错了,请稍后再试",避免向外界透露系统底层信息。

java 复制代码
try {
    //业务代码
}catch (Exception e){
    //必要的错误日志
    log.error("必要的错误日志", e);
    //异常转换
    throw new SysException("系统错误");
}

结合TemplateException异常对象

在上文中,我们声明了TemplateException异常对象,这里我们结合该对象进行异常处理。

  1. 声明业务异常和系统异常
scala 复制代码
/**
 * 该类表示一个业务异常
 * 业务异常通常是用户可解决的错误,通过该类携带一个对用户友好的提示
 */
public class BusinessException extends TemplateException{
    //define
}

/**
 * 该类表示一个系统错误
 * 系统错误通常是用户无法解决异常,通过该类携带必要的现场信息
 */
public class SystemException extends TemplateException{
    //define
}
  1. 执行业务,声明两个接口
java 复制代码
@RequestMapping("/businessException/{flag}")
public String businessException(@PathVariable String flag) {
    //测试业务异常
    String errorMessage = "flag输入错误,当前参数为:" + flag;
    throw new BusinessException(errorMessage);
}

@RequestMapping("/systemException/{flag}")
public String systemException(@PathVariable String flag) {
    //测试系统异常
    try {
        //创建异常
        throw new NullPointerException();
    } catch (Exception e) {
        //携带详细的现场信息
        throw new SystemException("出现无法解决的异常", e)
                .describe("flag: %s", flag)
                .describe("其他描述信息:%s", "其他描述信息");
    }
}
  1. 异常统一处理
java 复制代码
@ExceptionHandler(BusinessException.class)
public String handleBusinessException(BusinessException exception) {
    String exceptionSubject = exception.getSubject();
    log.warn("业务异常:{}", exceptionSubject);
    //响应结果,直接返回业务异常携带的主题
    return exceptionSubject;
}

@ExceptionHandler(SystemException.class)
public String handleSystemException(SystemException exception) {
    List<String> descriptions = exception.getDescriptions();
    Throwable cause = exception.getCause();
    cause = Objects.isNull(cause) ? exception : cause;
    String exceptionSubject = exception.getSubject();
    log.error("系统异常:{}", exceptionSubject);
    for (String description : descriptions) {
        log.error("{}", description);
    }
    log.error("触发异常对象", cause);
    //响应结果,对异常携带的主题进行转换
    return "系统异常";
}
  1. 效果
shell 复制代码
2024-01-24 22:35:49.390  WARN 19260 --- [nio-8888-exec-1] c.x.m.e.handle.GlobalExceptionHandler    : 业务异常:flag输入错误,当前参数为:1
2024-01-24 22:35:57.729 ERROR 19260 --- [nio-8888-exec-5] c.x.m.e.handle.GlobalExceptionHandler    : 系统异常:出现无法解决的异常
2024-01-24 22:35:57.729 ERROR 19260 --- [nio-8888-exec-5] c.x.m.e.handle.GlobalExceptionHandler    : flag: 2
2024-01-24 22:35:57.729 ERROR 19260 --- [nio-8888-exec-5] c.x.m.e.handle.GlobalExceptionHandler    : 其他描述信息:其他描述信息
2024-01-24 22:35:57.731 ERROR 19260 --- [nio-8888-exec-5] c.x.m.e.handle.GlobalExceptionHandler    : 触发异常对象

java.lang.NullPointerException: null
	at com.xiaoyan.monadicapptemplate.controller.CommonController.systemException(CommonController.java:30) ~[classes/:na]

总结

本文中,根据异常情况的特点,将异常归纳为业务异常和系统异常,并结合TemplateException异常对象进行异常处理。系统可以基于这两类对象延伸出细分类,但还是需要根据异常特点进行针对性的处理,即让用户看的,显示异常细节;让开发人员看的,则隐藏异常细节。

相关推荐
考虑考虑4 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261354 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊5 小时前
Java学习第22天 - 云原生与容器化
java
渣哥7 小时前
原来 Java 里线程安全集合有这么多种
java
间彧7 小时前
Spring Boot集成Spring Security完整指南
java
间彧7 小时前
Spring Secutiy基本原理及工作流程
java
Java水解8 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆11 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学11 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole11 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端