在Java中统一异常码如何设计_Java异常规范化解析

统一异常码必须用枚举,因其具备类型安全、防拼写错误、可集中管理及附带HTTP状态码等优势;业务异常与系统异常须分包隔离,前者封装错误码无堆栈,后者保留原始堆栈;全局处理器应动态设状态码并统一返回Result结构。

统一异常码该不该用枚举

该用,而且必须用。字符串或整数常量定义异常码极易导致拼写错误、重复定义、无法集中管理,IDE 也难做校验。枚举天然具备类型安全、可枚举、可附带描述和 HTTP 状态码等能力。

推荐结构:ErrorCode 枚举实现 ErrorCodeInterface 接口(含 getCode()getMessage()getHttpStatus()),避免后续扩展时被迫改大量 switch。

public enum ErrorCode implements ErrorCodeInterface {
    USER_NOT_FOUND(40001, "用户不存在", HttpStatus.NOT_FOUND),
    PARAM_VALIDATION_ERROR(40002, "参数校验失败", HttpStatus.BAD_REQUEST),
    SYSTEM_BUSY(50001, "系统繁忙,请稍后重试", HttpStatus.INTERNAL_SERVER_ERROR);

    private final int code;
    private final String message;
    private final HttpStatus httpStatus;

ErrorCode(int code, String message, HttpStatus httpStatus) { this.code = code; this.message = message; this.httpStatus = httpStatus; } @Override public int getCode() { return code; } @Override public String getMessage() { return message; } @Override public HttpStatus getHttpStatus() { return httpStatus; } }

业务异常和系统异常要不要分包处理

要分,且必须物理隔离。混在一起会导致日志归因困难、监控指标失真、前端兜底策略混乱。

  • BizException 继承 RuntimeException,只封装 ErrorCode 和可选业务上下文(如订单号),不带堆栈(或显式抑制)
  • SystemException 继承 RuntimeException,用于包装 IOExceptionSQLException 等,保留原始异常堆栈,便于排查基础设施问题
  • 全局异常处理器中:对 BizException 返回 errorCode + message;对 SystemException 记录完整堆栈并返回泛化错误码(如 50099),绝不暴露敏感信息

@ResponseStatus 和统一返回体怎么配合

别用 @ResponseStatus 注解在自定义异常类上。它会强制绑定 HTTP 状态码,但实际中同一个错误码可能对应不同状态(比如 USER_NOT_FOUND 在查询接口返回 404,在创建接口前置校验时可能返回 400)。

正确做法是:所有异常都走统一的 @ControllerAdvice 处理器,根据 ErrorCode.getHttpStatus() 动态设状态码,并统一包装成 Result 结构:

@ExceptionHandler(BizException.class)
public ResponseEntity> handleBizException(BizException e) {
    ErrorCode code = e.getErrorCode();
    return ResponseEntity.status(code.getHttpStatus())
            .body(Result.fail(code.getCode(), code.getMessage()));
}

这样既保持协议层语义清晰,又避免状态码被“写死”在异常类型里,方便灰度或兼容性调整。

错误码前缀要不要加业务域标识

要看规模。单体或小团队初期可省略;微服务多、模块超 5 个、跨团队协作时,强烈建议加前缀,比如 USER_40001ORDER_50002

原因很实际:

  • 避免合并 PR 时无意覆盖他人错误码(整数易冲突,字符串前缀天然隔离)
  • 日志/监控平台按前缀聚合告警更精准(比如只看 ORDER_* 的 5xx 上升趋势)
  • 前端可根据前缀做差异化提示(USER_* 跳登录页,PAY_* 跳支付重试)

注意:前缀应为英文大写下划线风格,不建议用数字或点号(user.400011001_40001 都增加解析负担)。

真正麻烦的不是设计规则,而是让所有人——包括新来的同事、临时支援的测试、第三方对接方——能一眼从错误码反查到定义位置、含义、修复责任人。所以枚举类必须配 Javadoc,CI 流程里加校验:新增枚举值必须有非空 @since@see 指向需求单或 Wiki 页面。