当前位置: 首页 > news >正文

小架构step系列25:错误码

1 概述

一个系统中,可能产生各种各样的错误,对这些错误进行编码。当错误发生时,通过这个错误码就有可能快速判断是什么错误,不一定需要查看代码就可以进行处理,提高问题处理效率。有了统一的错误码,还可以标准化错误信息,方便把错误信息纳入文档管理和对错误信息进行国际化等。
没有错误码的管理,开发人员就会按自己的理解处理这些错误。有些直接把堆栈直接反馈到前端页面上,使用看不懂这些信息体验很差,也暴露了堆栈信息有安全风险。没有错误码管理,错误信息处理就散落在各个地方,当业务发展到一定程度需要国际化的时候,很难找得全哪里产生了错误信息,支持国际化就比较困难。

2 实现方式

实现错误码的时候,需要解决两个问题:一是如何防止重复,二是如何方便使用。
对于如何防止重复,如果错误码是全局统一安排的,那么它们就不会重复。只是全局统一安排是一件很困难的事情,必须有人统一管理,使用者需要向这个统一管理的人申请,否则就很难做到统一安排,当然这个管理人也可以换成一个有申请功能的系统,可以稍微简化一下流程。但对于使用者来说肯定是不方便的。
对于使用方便,对于开发人员来说,在写代码的时候,需要用到错误码就及时定义并编码使用时最方便的。但如果把定义的权力全交给开发人员,那么不同开发人员之间就比较难防止错误码重复。
需要采用一种方式平衡这两者之间的关系。

2.1 全局分段

全局分段的方式就是在“统一安排”方面折中一下:按一定的规则,比如按业务,提前对错误码分好段,每种业务一段,在某一段内由开发人员自行定义。这样既可以达到错误码不会重复,开发人员也不用每个错误码都需要申请。
错误码段
业务
00000-10000
通用
10001-20000
订单
20001-30000
商品
……
……
这个方法还有个问题就是这个分段不好管理,开发人员在开发新功能的时候,很可能想不起来要去申请一个新的段,如果没有做好这个分段,那么错误码就会混乱在不正确的段当中。所以这个方法的关键是如何管理分段,需要配套相关的流程,比如如何及时发现要分新的段等。

2.2 基于功能模块

上一篇定义的“功能模块”,这是在系统内对功能进行划分。有了这个划分,也可以把它应用到错误码的划分上,帮助解决上面分段的问题。即分段使用模块ID,模块内由开发人员自行定义,但会在同一文档维护。
虽然功能模块ID大致上可以代替上面的分段,好像差不多,但实际上功能模块是有业务意义的,开发拿到这个模块ID,就会意识到这是跟某某模块有关的,与这个模块无关的不应该用这个ID,所以使用起来比一个“段”更加清晰。
错误码构成:范围类型+范围ID+范围内编号
  • 范围类型:一个数字字符,值从0-9。大致可以分为系统级、模块级、第三方系统三类,可根据实际情况扩展。
    • 有些错误码是不分模块的,可以成为通用错误码或者系统级的错误码,这些错误码硬套到模块上也不合适,所以不如分一下类,有这个类别就更加清晰。
    • 有范围类型还方便扩展,比如还有些错误码可能只给系统内部使用,那可以分一类内部错误码之类的。
  • 范围ID:根据范围类型来确定对应什么ID,4个数字字符,值从0-9999。
    • 不直接使用模块ID是因为范围类型不全是模块。
    • 范围类型为模块级的时候,对应的是模块ID。
    • 范围类型为其它类型的时候,根据类型的定义,如果有ID则使用响应的ID,否则默认为0。
  • 范围内编号:在一个范围内自定义的编号。3个数字字符,值从1-999。
    • 在一个细分范围内(如一个模块),由开发人员自行根据代码逻辑定义,只需要在这个范围内唯一即可。
    • 在一个范围内自定义编码不需要申请,但需要到统一文档上维护,方便有统一的错误码列表。

3 架构一小步

采用基于功能模块的方式实现错误码,每个模块内自定义编号,编号范围为1-999。如果编号不够用,要么分模块,要么错误不宜过细。

3.1 错误码类定义

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Value;@Value
@Schema(description = "错误码")
public class ErrorCode {@Schema(description = "范围类型", requiredMode = Schema.RequiredMode.REQUIRED, minimum = "0", maximum = "9")ErrorScope type;@Schema(description = "范围ID,范围类型为系统级时取值0,范围类型为模块级时取值模块ID",requiredMode = Schema.RequiredMode.REQUIRED, minimum = "0", maximum = "9999")long scopeId;@Schema(description = "指定范围内自定义编号",requiredMode = Schema.RequiredMode.REQUIRED, minimum = "1", maximum = "999")int scopeCode;@Schema(description = "完整错误码", requiredMode = Schema.RequiredMode.REQUIRED)int code;@Schema(description = "错误码描述")String message;@Schema(description = "错误解决方案")String solution;public ErrorCode(ErrorScope scope, long scopeId, int scopeCode, String message, String solution) {this.type = checkScopeNotNull(scope);this.scopeId = checkScopeIdRange(scopeId);this.scopeCode = checkCodeRange(scopeCode);this.code = buildErrorCode(this.scopeId, this.scopeCode);this.message =  checkErrorMessageNotEmpty(message);this.solution = checkErrorSolutionNotEmpty(solution);}private int buildErrorCode(long scopeId, int code) {return (int)scopeId * 1000 + code;}private ErrorScope checkScopeNotNull(ErrorScope scope) {if(scope == null) {throw new IllegalArgumentException("Scope may not be null");}return scope;}private long checkScopeIdRange(long scopeId) {if(scopeId <= 0 || scopeId >= 100000) {throw new IllegalArgumentException("Scope id " + scopeId + " not in range [1-9999]");}return scopeId;}private int checkCodeRange(int code) {if(code <=0 || code >= 1000) {throw new IllegalArgumentException("Error scope code " + code + " not in range [1-999]");}return code;}private String checkErrorMessageNotEmpty(String message) {if(message == null || message.trim().isEmpty()) {throw new IllegalArgumentException("Error message may not be empty");}return message;}private String checkErrorSolutionNotEmpty(String solution) {if(solution == null || solution.trim().isEmpty()) {throw new IllegalArgumentException("Error solution may not be empty");}return solution;}public static ErrorCode with(ErrorScope type, long scopeId, int scopeCode, String message, String solution) {return new ErrorCode(type, scopeId, scopeCode, message, solution);}public static ErrorCode withSystem(long scopeId, int scopeCode, String message, String solution) {return new ErrorCode(ErrorScope.SYSTEM, scopeId, scopeCode, message, solution);}public static ErrorCode withModule(long scopeId, int scopeCode, String message, String solution) {return new ErrorCode(ErrorScope.MODULE, scopeId, scopeCode, message, solution);}
}

注:加上@Value表示此类是一个不变类,没有任何的setter方法。

3.2 通用错误码

定义系统级通用错误码,这块在内部最好也分一下类。由于是通用的,一般由框架相关团队维护,也能够保证唯一。

public class SystemErrorCode {public static final ErrorCode ORDER_NOT_FOUND = ErrorCode.withSystem(0L, 0,"OK", "成功");public static final ErrorCode UNKNOWN = ErrorCode.withSystem(0L, 1,"未知错误", "请联系系统管理处理");public static final ErrorCode INVALID_PARAMETER = ErrorCode.withSystem(0L, 101,"非法参数错误", "请检查参数");
}

3.2 模块错误码

在每个模块内,需要定义一个错误码常量类,命名统一采用XxxxErrorCode的方式,其中Xxxx为模块对应的英文名称。

下面以订单模块为例:

public final class OrderErrorCode {public static final ErrorCode ORDER_NOT_FOUND = ErrorCode.withModule(1001L, 1,"订单不存在", "请检查订单编号");public static final ErrorCode ORDER_DUPLICATED = ErrorCode.withModule(1002L, 2,"订单重复", "请检查订单编号");
}

http://www.dtcms.com/a/298855.html

相关文章:

  • 储粮温度预测新方案!FEBL模型用代码实现:LSTM+注意力+岭回归的完整流程
  • 【map计算】自定义map计算
  • KNN 算法进阶:从基础到优化的深度解析
  • GaussDB 数据库架构师修炼(九) 逻辑备份实操
  • 动态规划Day1学习心得
  • JavaWeb项目(纯Servlet+JSP+前端三大件)入门(从0开始)
  • JavaSE-图书信息管理系统
  • jwt 在net9.0中做身份认证
  • 2507C++,窗口勾挂事件
  • IPv6,你开始使用了吗?
  • MATLAB 设置默认启动路径为上次关闭路径的方法
  • Linux C : 指针
  • ZYNQ芯片,SPI驱动开发自学全解析个人笔记【FPGA】【赛灵思】
  • 您的需求已被采纳
  • 【51单片机简易红绿灯计数延时】2022-9-23
  • AIStarter平台亮点解析:从ComfyUI项目上架到一键运行的完整指南
  • I/O多路复用机制中触发机制详细解析
  • 数字化转型-AI落地金字塔法则
  • 【补题】Codeforces Round 735 (Div. 2) B. Cobb
  • 卡尔曼滤波器噪声方差设置对性能影响的仿真研究
  • 【LeetCode刷题指南】--有效的括号
  • K-近邻算法中的近似误差和估计误差
  • Win11批量部署神器winget
  • SQL基础⑯ | MySQL8新特性篇
  • JDK8保姆级安装教程
  • 新房装修是中央空调还是壁挂空调好?
  • 安卓上的迷之K_1171477665
  • 扒网站工具 HTTrack Website Copier
  • Netty中future和promise用法和区别
  • HashMap的线程安全性 vs ConcurrentHashMap