COLA异常处理最佳实践:BizException与SysException的精准应用
引言:为什么标准异常模型在复杂业务中失效?
你是否还在为系统中混乱的异常处理逻辑而头疼?当业务异常与系统异常混杂在一起,当错误码体系失去管控,当异常日志淹没关键信息——这不仅增加了排查问题的难度,更可能导致线上故障的扩大化。COLA(Clean Object-oriented & Layered Architecture,清晰面向对象分层架构)框架提供了一套优雅的异常处理解决方案,通过BizException(业务异常) 与SysException(系统异常) 的精准区分,构建起层次分明、职责清晰的异常处理体系。
读完本文你将掌握:
- 如何严格区分业务异常与系统异常的边界条件
- 基于ExceptionFactory的异常标准化创建流程
- Assert工具在参数校验中的高效应用技巧
- 全局异常拦截与统一响应转换的实现方案
- 符合DDD架构思想的异常处理最佳实践
异常体系核心设计:两种异常的本质区别
异常类型的明确定位
COLA框架将异常划分为两大阵营,每种类型都有其明确的适用场景和处理策略:
// 业务异常:已知的、可预期的业务规则违反
public class BizException extends BaseException {
private static final String DEFAULT_ERR_CODE = "BIZ_ERROR";
// 构造函数省略...
}
// 系统异常:不可预期的基础设施或外部依赖故障
public class SysException extends BaseException {
private static final String DEFAULT_ERR_CODE = "SYS_ERROR";
// 构造函数省略...
}
核心差异对比表
| 特性维度 | BizException | SysException |
|---|---|---|
| 本质属性 | 业务规则校验失败 | 系统运行时环境异常 |
| 可预测性 | 完全可预期(编码时已知) | 不可预期(运行时突发) |
| 重试价值 | 无(重复请求仍会失败) | 有(环境恢复后可能成功) |
| 处理策略 | 友好提示用户 | 技术告警+内部处理 |
| 日志级别 | WARN(生产环境) | ERROR(必须记录堆栈) |
| 错误码前缀 | B_XXX(如B_ORDER_ILLEGAL) | S_XXX(如S_DB_CONN_FAILED) |
异常状态流转模型
异常创建标准化:ExceptionFactory的最佳实践
COLA提供ExceptionFactory工具类,封装了异常对象的创建逻辑,确保异常实例的一致性和规范性。
异常创建的代码示例
// 标准业务异常创建(带错误码)
if (orderAmount <= 0) {
throw ExceptionFactory.bizException(
"B_ORDER_AMOUNT_ILLEGAL",
"订单金额必须大于零,当前值: " + orderAmount
);
}
// 简化业务异常创建(使用默认错误码)
if (user.getStatus() != UserStatus.ACTIVE) {
throw ExceptionFactory.bizException("用户账号未激活");
}
// 系统异常创建(带根因异常)
try {
// 数据库操作
} catch (SQLException e) {
throw ExceptionFactory.sysException(
"S_DB_ACCESS_FAILED",
"数据库访问失败",
e // 传递根异常,保留完整堆栈
);
}
错误码体系设计建议
一个规范的错误码体系应该包含:
- 层级结构:[类型前缀][业务模块][具体错误]
- 自解释性:通过错误码能大致判断错误类型和位置
- 扩展性:预留模块编号区间,避免冲突
推荐的错误码命名规范示例:
| 错误码类型 | 示例 | 说明 |
|---|---|---|
| 业务通用错误 | B_COMMON_PARAM_NULL | 参数为空 |
| 订单模块错误 | B_ORDER_NOT_FOUND | 订单不存在 |
| 用户模块错误 | B_USER_AUTH_FAILED | 用户认证失败 |
| 系统通用错误 | S_COMMON_RESOURCE_EXHAUSTED | 资源耗尽 |
| 数据库错误 | S_DB_CONNECTION_REFUSED | 数据库连接拒绝 |
| 缓存错误 | S_CACHE_TIMEOUT | 缓存操作超时 |
参数校验利器:Assert工具的高效应用
COLA的Assert工具类将参数校验逻辑简化为一行代码,自动抛出标准化的BizException,大幅提升代码可读性。
常用断言方法速查表
| 方法签名 | 使用场景 | 示例 |
|---|---|---|
isTrue(expression, errorCode, message) | 验证布尔条件 | Assert.isTrue(amount > 0, "B_AMOUNT_ILLEGAL", "金额必须为正数") |
notNull(object, errorCode, message) | 验证对象非空 | Assert.notNull(user, "B_USER_NULL", "用户信息不能为空") |
notEmpty(collection, errorCode, message) | 验证集合非空 | Assert.notEmpty(items, "B_ITEMS_EMPTY", "订单项不能空") |
isFalse(expression, errorCode, message) | 验证条件为假 | Assert.isFalse(order.isPaid(), "B_ORDER_PAID", "订单已支付") |
断言工具在业务中的应用
public void createOrder(OrderCreateCmd cmd) {
// 1. 参数基本校验
Assert.notNull(cmd, "B_ORDER_CMD_NULL", "订单创建命令不能为空");
Assert.notEmpty(cmd.getItems(), "B_ORDER_ITEMS_EMPTY", "订单项列表不能为空");
// 2. 业务规则校验
Assert.isTrue(cmd.getTotalAmount().compareTo(BigDecimal.ZERO) > 0,
"B_ORDER_AMOUNT_ILLEGAL", "订单总金额必须大于零");
// 3. 状态校验
Assert.isFalse(cmd.getItems().stream().anyMatch(item -> item.getQuantity() <= 0),
"B_ORDER_ITEM_QTY_ILLEGAL", "订单项数量必须大于零");
// 业务逻辑处理...
}
与传统if-else校验的对比优势
传统方式(8行代码):
if (user == null) {
log.warn("用户信息为空");
throw new BizException("B_USER_NULL", "用户信息不能为空");
}
if (user.getAge() < 18) {
log.warn("用户年龄不足,当前:{}", user.getAge());
throw new BizException("B_USER_AGE_LIMIT", "用户年龄必须大于18岁");
}
Assert方式(2行代码):
Assert.notNull(user, "B_USER_NULL", "用户信息不能为空");
Assert.isTrue(user.getAge() >= 18, "B_USER_AGE_LIMIT", "用户年龄必须大于18岁");
全局异常处理:CatchLogAspect的拦截机制
COLA通过AOP切面实现异常的统一拦截与处理,确保所有异常都能被标准化转换为API响应。
异常拦截核心代码解析
@Aspect
@Slf4j
@Order(1)
public class CatchLogAspect {
@Pointcut("@within(CatchAndLog) && execution(public * *(..))")
public void pointcut() {} // 拦截标记@CatchAndLog的类
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
long startTime = System.currentTimeMillis();
logRequest(joinPoint); // 记录请求参数
Object response = null;
try {
response = joinPoint.proceed(); // 执行目标方法
} catch (Throwable e) {
response = handleException(joinPoint, e); // 异常处理
} finally {
logResponse(startTime, response); // 记录响应结果
}
return response;
}
private Object handleException(ProceedingJoinPoint joinPoint, Throwable e) {
// 根据异常类型分别处理
if (e instanceof BizException) {
log.warn("BIZ EXCEPTION : {}", e.getMessage()); // 业务异常WARN级别
if (log.isDebugEnabled()) {
log.error(e.getMessage(), e); // Debug模式记录堆栈
}
// 转换为统一响应格式
return ResponseHandlerFactory.get().handle(returnType,
((BizException) e).getErrCode(), e.getMessage());
} else if (e instanceof SysException) {
log.error("SYS EXCEPTION: {}", e.getMessage(), e); // 系统异常ERROR级别
return ResponseHandlerFactory.get().handle(returnType,
((SysException) e).getErrCode(), e.getMessage());
}
// 未知异常处理...
}
}
异常处理流程时序图
统一响应格式定义
COLA的ResponseHandler会将所有异常转换为统一的响应格式:
// 单对象响应
public class SingleResponse<T> extends Response {
private T data;
// getters/setters
}
// 分页响应
public class PageResponse<T> extends Response {
private List<T> data;
private int pageSize;
private int pageIndex;
private long total;
// getters/setters
}
异常响应示例:
{
"success": false,
"errCode": "B_ORDER_AMOUNT_ILLEGAL",
"errMessage": "订单金额必须大于零,当前值: -100",
"data": null
}
最佳实践与避坑指南
异常抛出的黄金原则
- 单一职责原则:一个方法只抛出一类相关异常,避免异常类型混乱
- 最小知识原则:异常信息只包含必要内容,避免暴露系统实现细节
- 显式声明原则:业务接口应通过JavaDoc声明可能抛出的BizException
- 根因保留原则:系统异常必须传递原始异常(如
new SysException(..., e))
典型错误案例分析
反例1:异常类型混用
// 错误:将业务异常误用为系统异常
if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new SysException("订单金额不合法"); // 应该用BizException
}
反例2:异常信息过载
// 错误:暴露SQL细节和敏感信息
throw new SysException("数据库查询失败: SELECT * FROM users WHERE id=" + userId);
// 正确做法:使用通用消息,原始异常通过构造函数传递
throw ExceptionFactory.sysException("S_DB_QUERY_FAILED", "用户数据查询失败", e);
反例3:捕获异常后不处理
// 错误:吞噬异常,导致问题无法追踪
try {
// 关键业务操作
} catch (BizException e) {
// 无任何处理
}
与DDD架构的结合应用
在DDD分层架构中,异常应遵循逐层封装原则:
- 领域层:只抛出原始业务异常,不关心技术实现
- 应用层:可包装领域异常为更具体的应用异常
- 接口层:统一拦截并转换为标准响应格式
// 领域层异常(纯业务含义)
public class InsufficientBalanceException extends RuntimeException {
private final BigDecimal balance;
private final BigDecimal required;
// 构造函数和getter
}
// 应用层转换为标准BizException
public void transferMoney(TransferCmd cmd) {
try {
accountDomainService.transfer(cmd.getFromId(), cmd.getToId(), cmd.getAmount());
} catch (InsufficientBalanceException e) {
throw ExceptionFactory.bizException(
"B_ACCOUNT_INSUFFICIENT_BALANCE",
String.format("余额不足,当前: %s, 所需: %s", e.getBalance(), e.getRequired())
);
}
}
总结与进阶方向
COLA框架的异常处理体系通过严格区分异常类型、标准化创建流程、统一拦截转换三大支柱,构建起清晰、可维护的异常处理机制。核心价值在于:
- 提升代码质量:强制异常分类,避免混乱的异常处理逻辑
- 加速问题定位:结构化错误码和日志,快速定位故障点
- 改善用户体验:业务异常的友好提示,系统异常的稳定降级
- 降低维护成本:统一的异常处理策略,减少团队协作成本
进阶实践方向:
- 实现错误码的集中管理和国际化支持
- 构建异常监控告警平台,基于错误码设置告警阈值
- 开发IDE插件,提供错误码自动补全和校验功能
- 结合APM工具,实现异常链追踪和影响范围分析
通过本文介绍的最佳实践,你可以在COLA框架下构建起专业、规范的异常处理体系,让系统更加健壮、可维护,同时为用户提供更友好的服务体验。记住:优秀的异常处理不是事后弥补,而是架构设计阶段就应纳入考量的关键环节。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



