Java开发经验——阿里巴巴编码规范实践解析3
摘要
本文深入解析了阿里巴巴编码规范中关于错误码的制定与管理原则,强调错误码应便于快速溯源和沟通标准化,避免过于复杂。介绍了错误码的命名与设计示例,推荐采用模块前缀、错误类型码和业务编号的结构。同时,探讨了项目错误信息管理机制,建议采用分层但统一的管理方式。强调错误码不能直接作为用户提示信息,使用者应避免随意定义新错误码,业务信息应由错误信息承载而非错误码本身。还讨论了获取第三方服务错误码时的处理方式,以及错误码的宏观分类、与HTTP状态码的关系、对跨文化协作的帮助等。最后,涉及异常体系的设计,包括常见异常分类、统一异常基类设计、统一异常处理器、标准错误返回结构等。
1. 【强制】错误码的制定原则:快速溯源、沟通标准化。 说明:错误码想得过于完美和复杂,就像康熙字典的生僻字一样,用词似乎精准,但是字典不容易随身携带且简单易懂。
正例:错误码回答的问题是谁的错?错在哪?
- 错误码必须能够快速知晓错误来源,可快速判断是谁的问题。
- 错误码必须能够进行清晰地比对(代码中容易 equals)。
- 错误码有利于团队快速对错误原因达到一致认知。
1.1. 错误码的制定原则
快速溯源
- 错误码应该一眼就能知道是哪个系统、模块、接口、哪一类错误。
- 不能出现“模糊”、“需要查源码”、“要问人”的情况。
沟通标准化
- 错误码不仅是给开发看的,也是给测试、运维、客服看的。
- 应避免个性化描述,比如“老王说接口挂了” vs “支付系统-用户认证失败(P100201)”。
不要康熙字典式复杂
- 错误描述越精准、越细分,并不代表越好。
- 错误码是为“快速识别 + 标准沟通”服务的,要简单、可识别、可比对(如 equals)。
1.2. 错误码命名与设计示例
正确示例(推荐结构):
模块前缀 + 错误类型码 + 业务编号
例如:U010001,P100201
错误码 | 模块 | 描述 |
| 用户系统 | 用户未登录 |
| 用户系统 | 用户Token已失效 |
| 支付系统 | 支付渠道认证失败 |
| 订单系统 | 订单不存在 |
| 风控系统 | 命中黑名单规则 |
- 前缀如:
U
=User模块、P
=Payment、O
=Order、R
=Risk - 中间两位如:
01
代表“认证错误”,02
是“资源问题”,03
是“参数非法” - 后三位为具体错误编号
错误示例
错误码 | 问题说明 |
| 没有上下文,完全不知是哪个系统的错 |
| 太长了,不利于代码中 equals 比对 |
| 非结构化格式,难以管理和比对 |
| 没有语义,无法直观看出模块来源 |
2. 【强制】项目错误信息是使用代码的全局统一error类处理还是每一层有自己error处理,还是直接写入代码里面?
推荐采用 “全局统一错误信息管理 + 层内扩展” 的方式,而不是“随便写”或“每层乱管”。
2.1. 最佳实践建议:分层但统一的错误信息管理机制
推荐结构:
- 统一错误码定义(全局 Enum / Code 类)
-
- 定义所有错误码、错误信息(用于开发、测试、运维统一查阅)
- 每个模块一个子枚举或前缀,方便归类
- 按模块/层封装异常类(可继承通用异常)
-
- 各层有自己的异常类型,但共享统一的错误码体系
- 每层处理只关心与其职责相关的错误(解耦)
- 异常中间层(统一异常处理器)
-
- 如 Spring 中的
@ControllerAdvice
+ExceptionHandler
- 负责把业务异常转换为 API 规范响应格式(例如统一返回 JSON 包含 code/message)
- 如 Spring 中的
2.2. 三种常见做法对比
做法 | 优点 | 缺点 / 风险 | 推荐程度 |
✅ 统一 error 定义 + 分层封装 | 清晰、可追踪、 标准化,便于维护 | 初期定义需要一定规划 | 强烈推荐 |
⚠️ 每一层自己定义 error 信息 | 模块清晰 | 错误码不统一,可能重复、歧义、难排查 | 不推荐 |
❌ 直接写死在代码中(写字符串) | 快捷开发 | 混乱、无法比对、无法维护、代码耦合严重 | 禁止使用 |
2.3. 推荐的错误管理设计示例
2.3.1. 全局错误码定义(推荐用 Enum)
public enum ErrorCode {SUCCESS("000000", "成功"),USER_NOT_LOGIN("U010001", "用户未登录"),USER_TOKEN_EXPIRED("U010002", "用户Token已过期"),ORDER_NOT_FOUND("O020301", "订单不存在"),SYSTEM_ERROR("S999999", "系统异常,请联系管理员");private final String code;private final String message;// 构造、getter略
}
2.3.2. 定义通用业务异常类
public class BizException extends RuntimeException {private final String code;public BizException(ErrorCode errorCode) {super(errorCode.getMessage());this.code = errorCode.getCode();}public String getCode() {return code;}
}
2.3.3. 统一异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(BizException.class)public ResponseEntity<ApiResult> handleBizException(BizException ex) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResult.fail(ex.getCode(), ex.getMessage()));}@ExceptionHandler(Exception.class)public ResponseEntity<ApiResult> handleException(Exception ex) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResult.fail(ErrorCode.SYSTEM_ERROR));}
}
2.4. 项目中error信息打印总结
要点 | 建议 |
错误码管理 | 全局统一 |
每层处理异常 | 封装业务异常类,使用统一错误码 |
对外返回 | 使用统一响应结构,统一异常处理器输出 |
禁止 | 直接在代码中写 |
3. 【强制】错误码不能直接输出给用户作为提示信息使用。
说明:堆栈(stack_trace)、错误信息(error_message) 、错误码(error_code)、提示信息(user_tip)是一个有效关联并互相转义的和谐整体,但是请勿互相越俎代庖。
3.1. 规则深度理解
系统中的异常响应,通常包含这些字段:
字段 | 作用 | 使用对象 |
| 错误编号,唯一定位错误来源 | 开发、测试、运维 |
| 系统级说明,记录异常详细信息 | 开发排查 |
| 调试信息,展示调用栈 | 仅开发环境调试时使用 |
| 提示给用户看的友好语言 | 最终用户 / 客户 |
这四者应彼此关联,但不得混用。
例如不能把 "U010001" 或 "NullPointerException" 直接显示给用户!
3.2. 异常返回错误示例(越俎代庖)
{"error_code": "U010001","message": "用户未登录,请登录系统","stack_trace": "...NullPointerException at...","user_tip": "U010001" ←❌ 错:直接暴露错误码给用户
}
或者:
{"error_code": "500","message": "java.lang.IllegalStateException: user is null", ←❌ 直接暴露底层异常信息"user_tip": "user is null" ←❌ 不友好,普通用户看不懂
}
3.3. 异常返回正确示例(职责分离)
{"error_code": "U010001","error_message": "用户未登录,token为空","stack_trace": null, // 生产环境不展示"user_tip": "登录已过期,请重新登录"
}
- error_code:给技术人员看,一眼知道是用户系统的问题
- error_message:可写入日志,便于问题溯源
- stack_trace:仅调试使用,生产环境屏蔽
- user_tip:简洁友好的提示,告诉用户“该做什么”
4. 【强制】错误码使用者避免随意定义新的错误码。 说明:尽可能在原有错误码附表中找到语义相同或者相近的错误码在代码中使用即可。
❗不要每次遇到错误就随便新定义一个错误码,而是优先复用已有语义相近的错误码。
原因:
- 控制错误码数量:错误码越多,管理成本越高,排查难度越大。
- 提高语义一致性:同一种错误使用同一个错误码,减少团队歧义。
- 避免重复定义:很多错误其实是“变种”,可以共用一个错误码。
4.1. 错误示例:重复造码
// 登录校验失败
throw new BizException("U010001", "用户未登录");// 注册校验失败
throw new BizException("U010005", "未登录用户无法注册");
实际上这两种情况都是“用户未登录”,本质一样,却用了两个不同的错误码,属于“重复造码”。
4.2. 正确示例:共用语义相近错误码
// 统一使用已定义的“用户未登录”错误码
throw new BizException(ErrorCode.USER_NOT_LOGIN);
登录页、注册页、敏感操作,都可以复用 U010001
。
5. 【推荐】错误码之外的业务信息由 error_message 来承载,而不是让错误码本身涵盖过多具体业务属性。
5.1. 规则理解
不要让错误码承担太多含义,它的职责只是:标识错误的“类型”或“分类”
而像:
- 错误发生时的具体数据(如“用户ID”、“订单号”)
- 具体错误描述(如“商品【123】库存不足,仅剩余1件”)
这些内容都不应该写入错误码本身,而是通过 error_message
或其他字段承载。
5.2. 错误码 vs 错误信息职责分工
字段 | 作用 | 示例 | 特性 |
| 错误类别编号 |
(支付失败) | 稳定、可比对 |
| 具体业务场景的解释 |
| 动态、可读 |
| 面向用户的友好提示 |
| 本地化、用户可见 |
5.3. 反例:错误码“负载过多信息”
{"error_code": "P10001_OUT_OF_STOCK_ITEM_12345","error_message": "商品12345库存不足"
}
❌ 错误码中包含了具体商品ID,这是 动态业务属性,不利于归类、比对和复用。
5.4. 正例:错误码用于分类,信息分离
{"error_code": "P10001","error_message": "商品 [12345] 库存不足,仅剩余 [1] 件","user_tip": "库存不足,请减少购买数量"
}
✅ 错误码 P10001
表示“通用库存不足”
✅ error_message
根据具体情况动态拼接,便于日志排查
✅ user_tip
可做国际化提示给用户
5.5. 实际工程建议
项目做法 | 建议 |
错误码只承载语义分类 | 如:参数错误、鉴权失败、业务失败等 |
不携带动态 ID、金额、字段名等 | 这些应放在 message |
message 和 tip 支持模板填充 | 例如 |
使用枚举或错误码常量类 | 限制错误码的随意性 |
6. 【推荐】在获取第三方服务错误码时,向上抛出允许本系统转义,由 C 转为 B, 并且在错误信息上带上原 有的第三方错误码。
当你调用第三方系统(C系统),它报错了,你不能直接把它的错误码暴露给用户或前端(B系统),而是要在自己系统内“转义”一层,抛出你本系统标准的错误码,并将原始错误码保留下来用于日志排查或内部分析。
6.1. 规则理解
场景角色:
- C系统:第三方接口,例如支付平台、银行接口、OCR识别服务等。
- B系统:你的调用方,比如前端、业务方、APP。
- 你自己:中间的服务系统(通常是网关 / 后端接口 / 业务中台)。
规则拆解:
- 不要让第三方系统的错误码直接向上冒泡(因为对方的码可能不规范、不稳定、无法识别)。
- 要“转义”:用你系统的统一错误码替代它
- 同时,原始的第三方错误码 + 错误信息保留用于排查或打日志,不让它丢失。
6.2. 错误示例:直接抛出第三方错误码
{"error_code": "4003", ← 这是支付平台的错误码"error_message": "签名无效"
}
问题:
- 前端和用户不知道“4003”代表啥
- 错误码不属于你系统的体系,破坏统一性
- 后续如果支付平台换服务、换码,前端也得改
6.3. 正确示例:本系统转义后抛出
{"error_code": "P100201", ← 本系统定义:支付认证失败"error_message": "支付渠道认证失败,请稍后重试","extra_info": {"third_error_code": "4003","third_error_msg": "签名无效"}
}
处理方式:
throw new BizException(ErrorCode.PAY_CHANNEL_AUTH_FAIL)
.withExtra("third_error_code", response.getCode())
.withExtra("third_error_msg", response.getMsg());
优势:
- 前端拿到的
P100201
可读可识别,统一标准 - 原始错误码保留,方便你打日志、出问题溯源
6.4. 实际应用建议
场景 | 建议做法 |
调用第三方支付、银行、OCR等 | 不要直接返回对方错误码 |
把第三方错误码转成系统内错误码 | 使用枚举或错误码映射 |
原始错误信息不要丢 | 记录在日志、extra字段、监控平台 |
抛出时用系统统一 BizException | 方便统一处理和返回结构 |
6.5. 进阶:错误码映射表(推荐)
你可以为每个对接的第三方准备一张 错误码映射表:
第三方错误码 | 本系统错误码 | 含义 |
|
| 支付签名无效 |
|
| 商户余额不足 |
|
| 授权过期 |
当第三方接口返回错误时,从映射表中找出对应的“本地错误码”抛出。
7. 【参考】错误码分为一级宏观错误码、 二级宏观错误码、 三级宏观错误码。
说明: 在无法更加具体确定的错误场景中, 可以直接使用一级宏观错误码, 分别是: A0001(用户端错误) 、 B0001(系统执行出错) 、 C0001(调用第三方服务出错)。
正例:调用第三方服务出错是一级,中间件错误是二级,消息服务出错是三级。
8. 【参考】错误码的后三位编号与 HTTP 状态码没有任何关系。
9. 【参考】错误码有利于不同文化背景的开发者进行交流与代码协作。
说明:英文单词形式的错误码不利于非英语母语国家(如阿拉伯语、希伯来语、俄罗斯语等)之间的开发者互相协作。
10. 【参考】错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类。
说明:数字是一个整体,每位数字的地位和含义是相同的。
反例:一个五位数字 12345,第 1 位是错误等级,第 2 位是错误来源,345 是编号,人的大脑不会主动地拆开并分辨每位数字的不同含义。
10.1. 规则理解
纯数字错误码(如 12345
)虽然简洁,但不具备“人性化、可读性、可记忆性”,也不利于团队协作、语义理解和错误分类。
10.2. 推荐做法:半结构化 + 可读的错误码设计
使用【字符 + 分类 + 数字】的组合模式,让错误码更易识别和传播。
[A][B][NNNNN]
字段 | 含义 | 示例 | 说明 |
A | 业务域缩写(系统模块) |
| 用户系统 |
B | 错误级别 / 类型标识 |
| 1 代表“校验类错误” |
NNNN | 错误编号(可读、递增) |
| 同类错误内递增编号 |
错误码 | 含义说明 |
| 用户未登录 |
| 用户权限不足 |
| 支付失败,余额不足 |
| OCR识别失败,图像模糊 |
这种格式即使不看文档,也能凭直觉猜出模块与大概的错误方向。
11. Exception(异常)体系的设计
在一个中大型项目中,设计一套**清晰、统一、可扩展的异常体系(Exception Architecture)**是非常关键的,它决定了:
- 错误信息的可追踪性 ✅
- 用户提示的友好性 ✅
- 系统稳定性与可维护性 ✅
- 日志告警与监控的精确性 ✅
异常体系设计目标
目标 | 说明 |
职责单一 | 每类异常只处理一类问题 |
分级处理 | 明确哪些是业务异常,哪些是系统异常 |
统一格式 | 所有错误响应结构一致 |
便于扩展 | 后续新模块、新错误类型可无缝接入 |
可追踪溯源 | 包括错误码、日志、traceId、堆栈等信息 |
11.1. 常见异常分类(强烈推荐)
11.1.1. BizException
(业务异常)
- 用户操作不符合业务规则,如余额不足、权限不足
- 是可预期、需要提示用户的异常
throw new BizException("BALANCE_NOT_ENOUGH", "余额不足");
11.1.2. SystemException
(系统异常)
- 系统内部出错,如数据库连接失败、服务不可用
- 不直接提示用户,返回通用友好提示即可
throw new SystemException("数据库连接失败: " + e.getMessage(), e);
11.1.3. ParamException
/ ValidationException
(参数异常)
- 请求参数格式错误、缺失、越界等
- 返回明确提示,前端能快速改正
throw new ParamException("手机号不能为空");
11.1.4. RemoteCallException
(远程服务异常)
- 第三方/下游系统异常
- 支持带入原始返回码和消息
throw new RemoteCallException("ALIPAY_ERROR_001", "支付宝支付失败", aliPayResponse);
11.1.5. 衍生类建议:
如需更细分:
DbException
CacheException
ThirdPartyException
11.2. 统一异常基类设计
public abstract class BaseException extends RuntimeException {private final String errorCode;private final String errorMessage;public BaseException(String code, String msg) {super(msg);this.errorCode = code;this.errorMessage = msg;}public String getErrorCode() { return errorCode; }public String getErrorMessage() { return errorMessage; }
}
所有异常都继承自 BaseException
,便于统一处理。
11.3. 统一异常处理器(Spring)
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(BizException.class)public ResponseEntity<ErrorResponse> handleBiz(BizException e) {return ResponseEntity.ok(ErrorResponse.fail(e.getErrorCode(), e.getErrorMessage()));}@ExceptionHandler(ParamException.class)public ResponseEntity<ErrorResponse> handleParam(ParamException e) {return ResponseEntity.badRequest().body(ErrorResponse.fail(e.getErrorCode(), e.getErrorMessage()));}@ExceptionHandler(SystemException.class)public ResponseEntity<ErrorResponse> handleSystem(SystemException e) {log.error("系统异常", e);return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResponse.fail("SYS_ERROR", "系统开小差了,请稍后再试"));}@ExceptionHandler(Exception.class)public ResponseEntity<ErrorResponse> handleOther(Exception e) {log.error("未知异常", e);return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ErrorResponse.fail("UNKNOWN_ERROR", "系统错误,请联系管理员"));}
}
11.4. 标准错误返回结构(统一响应体)
@Data
@AllArgsConstructor(staticName = "fail")
public class ErrorResponse {private String errorCode;private String errorMessage;
}
⚠️ 可以进一步扩展 userTip
, traceId
, timestamp
, debugMessage
等字段。
11.5. 异常体系设计总结
模块 | 内容 |
异常分类 |
|
统一基类 |
|
统一响应结构 | 统一返回 |
全局拦截器 |
|
统一错误码系统 | 错误码与异常结合使用,便于识别与追踪 |
11.6. 异常体系设计图(示意)
┌────────────────────┐│ BaseException │└────────┬───────────┘│┌────────────────────┼──────────────────────┐│ │ │
▼ ▼ ▼
BizException SystemException ParamException
(业务异常) (系统异常) (参数异常)↓GlobalExceptionHandler↓ ↓ ↓errorCode errorMessage userTip
博文参考
《阿里巴巴java规范》