当前位置: 首页 > 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.lryc.cn/news/599865.html

相关文章:

  • Haproxy七层代理及配置
  • 数据结构2-集合类ArrayList与洗牌算法
  • 在Word和WPS文字中添加的拼音放到文字右边
  • JS与Go:编程语言双星的碰撞与共生
  • 初识opencv04——图像预处理3
  • ModelWhale+数据分析 消费者行为数据分析实战
  • 判断子序列-leetcode
  • 广州 VR 安全用电技术:工作原理、特性及优势探析​
  • CTF-Web题解:“require_once(‘flag.php‘); assert(“$i == $u“);”
  • Linux系统基本配置以及认识文件作用
  • 双非上岸985!专业课140分经验!信号与系统考研专业课140+上岸中南大学,通信考研小马哥
  • 20分钟学会TypeScript
  • 本地内网IP映射到公网访问如何实现?内网端口映射外网工具有哪些?
  • VUE2 学习笔记6 vue数据监测原理
  • 局域网 IP地址
  • Linux tcpdump 抓取udp 报文
  • 开源语音TTS与ASR大模型选型指南(2025最新版)(疯聊AI提供)
  • 动态规划:从入门到精通
  • 中国开源Qwen3 Coder与Kimi K2哪个最适合编程
  • 电子电子架构 --- 软件项目的开端:裁剪
  • 【IDEA】IDEA中如何通过分支/master提交git?
  • Hadoop 之 Yarn
  • 【软件工程】构建软件合规防护网:双阶段检查机制的实践之道
  • 【AJAX】Promise详解
  • HashMap的线程安全性 vs ConcurrentHashMap
  • 机器学习中knn的详细知识点
  • 基于springboot的候鸟监测管理系统
  • 100条常用SQL语句大全
  • 用毫秒级视频回传打造稳定操控闭环之远程平衡控制系统技术实践
  • LE AUDIO CIS/BIS音频传输时延计算方法