如何定义错误码

Posted 编号94530

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何定义错误码相关的知识,希望对你有一定的参考价值。

好久没有写文章了,搞得不知道写啥,最近遇到了一个问题,决定把东西总结一下,记录下来。

1. 背景

最近开发,发现遇到了一个问题,那就是关于如何定义错误码。写代码写着写着迷糊了,突然发现不知道如何定义错误码,错误码对我们来说,到底有什么用?今天我结合自己的实现,来说一下如何定义错误码

2. 处理错误的方式

在Spring时代,spring提供的ControllerAdvice,RestControllerAdvice给我们集中处理异常提供了一种很好的解决方案。我们也经常用这样的方式来解决方案。但是,结合我们一部分常用的也给我带来了一部分烦恼,我来说一下为什么。

2.1 处理方案

在我们自己的代码中,经常会定义一种通用的返回结果,大多数都叫Result。代码如下:

@Data
@AllArgsConstructor
public class Result<T> implements Serializable 
    /**
     * 状态码
     */
    private Object code;

    /***
     * 说明信息
     */
    private String message;

    /**
     * 是否成功
     */
    private boolean success;

    /**
     * 返回数据
     */
    private T data;

我们经常会用这个对象当做接口的公共返回,所以理所当然的,在定义公共异常处理逻辑的时候,也会写成如下的样子。代码如下:

@RestControllerAdvice
@Slf4j
public class DefaultExceptionHandler 

    /**
     * 业务异常统一捕获
     *
     * @param req 请求
     * @param e 业务异常
     * @return 对应结果
     */
    @ExceptionHandler(value = BizException.class)
    public Result<String> businessExceptionHandler(HttpServletRequest req, BizException e) 
        // 已知异常不打印堆栈,避免多余的日志IO输出
        return new Result<>(e.getCode(), e.getMessage());
    

这是一个处理公共异常的类,里面也理所当然的返回了Result对象,看起来似乎都是天衣无缝,没什么问题,但是,也给我带来了一些困惑。

2.2 我的困惑

当我们遇到异常的时候,会写下如下的代码:

throw new BizException(errorCode, errorMessage);

错误码是我们自己定义的,例如:errorCode=400, errorMsg=“session timeout”,然后这个异常会理所当然的被我们的公共异常处理器捕获,然后返回给前端一个公共的Result,前端拿到状态码来处理相应的逻辑,例如:跳转到登录页面。这一切看起来都是正常的,但是,有个但是,如果我们返回的状态码不是400的时候呢? 或者有其他的状态码的时候呢?600, 601,602 ,前端都要一一处理吗?这个说实话,看起来怪怪的,处理的要求太多了,前端的改动也太大了。还有一个问题是每个请求都返回的HttpStatus都是200,给测试也带来了一定的烦恼,测试需要认真仔细,一一的看你的接口返回的 内容,来判断你的返回内容是都正确, 这无疑是增加了许多的工作量。看起来状态码似乎也没带来一个好的效果。

2.3 我的思考

就我目前遇到的情况来说,我觉得错误码是一个可有可无的东西,因为我只需要判断success是否为True就好了,因为当success为false的时候,都需要抛出message来展示。那我是不是可以随意定义错误码了呢?如果想要用上错误码,怎么用最合适,最有价值呢?错误码用来做错误接口提示可以吗?

3. 解开迷惑

对错误码理解少,或者说当错误直接提示message,或者说想在发生异常的时候,通过链路id或者其他方式找异常的我们都太天真了。我顿悟是在一张图上,图上的内容大概如下。

接口错误时处理方式
A前端不做处理,不允许阻塞下单流程
F前端不做处理,不允许阻塞下单流程
G前端不做处理,不允许阻塞下单流程
B前端做兜里逻辑
W前端做兜里逻辑
C需要给出错误提示

看到这个图的第一反应,我觉得需要定义好多错误码,好麻烦,能不能给出一种公共的,然后让前端好处理一些。并且我想改变我的代码,不想让测试费劲的看哪个接口出问题,便于我自己找问题,也便于测试。而且我还想前端基于目前的代码,不要有太大的改动,就算有改动,没覆盖到的地方,在以后的迭代中进行修改。

思前想后,想到一个这样的处理逻辑,一下子处理的当前的困境。

3.1 如何定义错误码

想来想去,觉得这样的定义错误码蛮合适的,给人一种靠谱的感觉。

错误码公式:系统编码 + 业务编码 + 错误码 + 接口编码

3.1.1 系统编码

系统编码:系统编码就是对每个系统进行编码,如有三个系统A,B,C,然后我进行了编号,分别为: 01,02,03,通过这几个数字,我就可以找到对应的系统。

3.1.2 业务编码

业务编码:业务编码就是对应具体的业务,如:我的系统中涉及到了D,E,F三个业务,然后我对三个业务也进行了编码,分别是100,200,300,通过这几个数字,我也可以找对对应的业务。

3.1.3 错误码

错误码就是要根据具体的情况来了,如下。

public enum SystemStatusEnum implements BaseEnum<Integer> 
    /**
     * 接口熔断
     */
    HYSTRIX_RETURN_CODE(602, "接口熔断"),

    /**
     * 业务异常
     */
    BUSINESS_ERROR(600, "业务异常"),

这些就要根据具体的情况,来定义一些编码,便于知道当出现这些异常的时候,需要如果做处理。

3.1.4 接口编码

接口编码,就是对每个接口进行编码,如:D业务的接口有:m,n两个接口。定义一个接口编码如下:

/**
 * <br>接口编码</br>
 *
 * @author fattyca1
 * @since 1.0
 */
public class InterfaceCode 
    public interface M 
        String GET_NAME = "01";
    

    public interface B 
        String SAY_HELLO = "01";
    

通过对具体的接口进行错误码定义,就可以通过错误码找到对应的接口。

3.2 如果使用错误码

​ 我们光定义好了错误码还是没有太大作用的,当生产环境出现问题的时候,不方便我们第一时间找出出现问题的地方,所以我们需要前端配合起来将错误码展示出来。

​ 如何展示呢? 其实很简单,就是通过在msg后边,把错误代码括号起来。给一个demo:系统繁忙,请稍后再试(000160012) ,我们通过前端展示的错误码000160012 就可以知道出现的问题是:00号系统,01号业务,600(系统异常,是兜底,还是放过),01号业务的12号接口,这样,我们就可以快速定位到具体的代码中去,在配合日志,很快就可以揪出bug出现的地方,是不是很方便?

​ 哈哈, 不知道大家发现一个问题没? 这样虽然解决了快速定位接口错误的问题,但是没有给测试带来收益,因为目前来说,接口的返回状态都是200,这样就无法区分出哪一个接口是具体的错误,所以我们还要针对这一种情况进行处理。处理的代码如下:

    /**
     * 业务异常统一捕获
     *
     * @param req 请求
     * @param e 业务异常
     * @return 对应结果
     */
    @ExceptionHandler(value = BizException.class)
    public ResponseEntity<Result<?>> businessExceptionHandler(HttpServletRequest req, BizException e) 
        String errorCode = e.getCode() + "";
      	// 处理会话超时情况
        if (SystemStatusEnum.SESSION_TIMEOUT.getCode().equals(errorCode)) 
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Result.fail(errorCode, e.getMessage()));
        
      	// 属于业务异常的系统异常
        if (SystemStatusEnum.DEFAULT_ERR_CODE.getCode().equals(errorCode)) 
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Result.fail(errorCode, e.getMessage()));
        
      	// 其他异常
        errorCode = StringUtils.isBlank(errorCode) ? SystemStatusEnum.DEFAULT_ERR_CODE.getCode() : errorCode;
        return ResponseEntity.ok(Result.fail(errorCode, e.getMessage()));
    

我这个处理业务的方法,先说一下为什么要这么做。

  1. 因为是业务异常,是我们自己抛的,所以不用打印多余的堆栈日志。
  2. 处理登录超时,可以给出HttpStatus=401,让前端请求一目了然,可以针对HttpStatus做出具体处理
  3. 当系统异常的时候,需要把此次请求HttpStatus=500,前端一目了然。

通过返回ResponseEntity而不是简单的Result,就解决了测试困难的情况。 不要通过固定思维,返回Result,局限了自己,致使每次返回的HttpStatus = 200, 使测试情况复杂化。

4. 总结

这一次,自己通过总结,算是理解了错误码的具体用法,分享出来,有问题,欢迎大家讨论呀!

以上是关于如何定义错误码的主要内容,如果未能解决你的问题,请参考以下文章

ListView 自定义适配器对错误的项 OnScroll 应用操作

网页错误页提示码汇总及使用示例

如何定义错误码

jQuery + Gravity Forms:对错误的验证执行 jQuery

机器视觉 HDevelop语言基础-错误处理

对错误码的设计思考