如何定义错误码

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

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. 总结

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容