COLA异常处理最佳实践:BizException与SysException的精准应用

COLA异常处理最佳实践:BizException与SysException的精准应用

【免费下载链接】COLA 🥤 COLA: Clean Object-oriented & Layered Architecture 【免费下载链接】COLA 项目地址: https://gitcode.com/gh_mirrors/col/COLA

引言:为什么标准异常模型在复杂业务中失效?

你是否还在为系统中混乱的异常处理逻辑而头疼?当业务异常与系统异常混杂在一起,当错误码体系失去管控,当异常日志淹没关键信息——这不仅增加了排查问题的难度,更可能导致线上故障的扩大化。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";
    // 构造函数省略...
}

核心差异对比表

特性维度BizExceptionSysException
本质属性业务规则校验失败系统运行时环境异常
可预测性完全可预期(编码时已知)不可预期(运行时突发)
重试价值无(重复请求仍会失败)有(环境恢复后可能成功)
处理策略友好提示用户技术告警+内部处理
日志级别WARN(生产环境)ERROR(必须记录堆栈)
错误码前缀B_XXX(如B_ORDER_ILLEGAL)S_XXX(如S_DB_CONN_FAILED)

异常状态流转模型

mermaid

异常创建标准化: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());
        }
        // 未知异常处理...
    }
}

异常处理流程时序图

mermaid

统一响应格式定义

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
}

最佳实践与避坑指南

异常抛出的黄金原则

  1. 单一职责原则:一个方法只抛出一类相关异常,避免异常类型混乱
  2. 最小知识原则:异常信息只包含必要内容,避免暴露系统实现细节
  3. 显式声明原则:业务接口应通过JavaDoc声明可能抛出的BizException
  4. 根因保留原则:系统异常必须传递原始异常(如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框架的异常处理体系通过严格区分异常类型标准化创建流程统一拦截转换三大支柱,构建起清晰、可维护的异常处理机制。核心价值在于:

  1. 提升代码质量:强制异常分类,避免混乱的异常处理逻辑
  2. 加速问题定位:结构化错误码和日志,快速定位故障点
  3. 改善用户体验:业务异常的友好提示,系统异常的稳定降级
  4. 降低维护成本:统一的异常处理策略,减少团队协作成本

进阶实践方向:

  • 实现错误码的集中管理和国际化支持
  • 构建异常监控告警平台,基于错误码设置告警阈值
  • 开发IDE插件,提供错误码自动补全和校验功能
  • 结合APM工具,实现异常链追踪和影响范围分析

通过本文介绍的最佳实践,你可以在COLA框架下构建起专业、规范的异常处理体系,让系统更加健壮、可维护,同时为用户提供更友好的服务体验。记住:优秀的异常处理不是事后弥补,而是架构设计阶段就应纳入考量的关键环节。

【免费下载链接】COLA 🥤 COLA: Clean Object-oriented & Layered Architecture 【免费下载链接】COLA 项目地址: https://gitcode.com/gh_mirrors/col/COLA

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值