buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

Clean Code/代码简洁性Good-Practice:使用统一异常捕获来取代错误处理

通过自定义异常集中处理,将繁琐的参数校验代码转化为清晰、简洁且可维护的艺术。


在Java开发中,参数校验、业务校验等错误情况处理是保证程序健壮性和数据完整性的第一道防线。我们常常会看到代码中充斥着大量的 if条件判断语句来进行参数校验,每个校验逻辑后通常还伴随着类似的错误处理和日志记录。这种重复不仅使代码变得臃肿冗长,还极大地降低了可读性和可维护性。

本文将以一个具体的参数校验代码改造为例,探讨如何利用统一异常处理机制,显著提升代码的简洁性与优雅度。

一、原始代码:冗余的校验与重复的处理

观察下面代码,不难发现每个参数校验都遵循着相同的模式:

if (ObjectUtils.isEmpty(funCode)) {
    log.info("商户API前置请求参数错误返回【接口编码为空】,reqId={}", reqId);
    resMap.setResCode(Constant.ERROR_6001);
    resMap.setResMsg("【参数错误】接口编码为空");
    response.getWriter().write(JsonUtils.toJson(resMap));
    return;
}

if (isReqOverLimit(merId, funCode)) {
    log.info("商户API前置请求,接口次数超限!接口={},商户号={},reqId={}", funCodeEnum.name(), merId, reqId);
    resMap.setResCode(Constant.ERROR_6042);
    resMap.setResMsg(Constant.ERROR_MSG_6042);
    response.getWriter().write(JsonUtils.toJson(resMap));
    return;
}

// ... 其他多个类似的参数校验块

这种传统处理方式存在几个明显问题

  1. 代码重复严重:相同的错误码设置、响应写入和日志记录逻辑在每个条件判断中重复出现。
  2. 关注点混淆:业务逻辑(参数校验)与技术细节(响应构建、日志记录)紧密耦合。
  3. 可维护性差:如果需要修改错误响应格式或日志记录方式,必须修改多处代码,容易遗漏且繁琐。

这种代码结构违反了 DRY(Don't Repeat Yourself)原则,是代码优化中首要解决的问题。

二、改造方案:统一异常处理的威力

改造的核心思想是:将参数校验错误视为一种异常情况,通过抛出特定的异常来中断正常流程,并在一处集中处理这些异常

1. 改造校验逻辑——抛出异常

将原有的 if 条件判断改为抛出异常,代码立刻变得清晰简洁:

try {
    String funCode = requestMessage.getFunCode();
    if (ObjectUtils.isEmpty(funCode)) {
        throw BizException.build(Constant.ERROR_6001, "【参数错误】接口编码为空");
    }
    if (ObjectUtils.isEmpty(requestMessage.getReqData())) {
        throw BizException.build(Constant.ERROR_6001, "【参数错误】请求数据为空");
    }
    String merId = requestMessage.getMerId();
    if (ObjectUtils.isEmpty(merId)) {
        throw BizException.build(Constant.ERROR_6001, "【参数错误】商户号为空");
    }
    if (ObjectUtils.isEmpty(requestMessage.getVersion())) {
        throw BizException.build(Constant.ERROR_6001, "【参数错误】版本信息为空");
    }
    if (ObjectUtils.isEmpty(requestMessage.getSign())) {
        throw BizException.build(Constant.ERROR_6001, "【参数错误】签名为空");
    }

    if (isReqOverLimit(merId, funCode)) {
        log.info("商户API前置请求,接口次数超限!接口={},商户号={},reqId={}", funCodeEnum.name(), merId, reqId);
        throw BizException.build(Constant.ERROR_6042, Constant.ERROR_MSG_6042);
    }
    
    // ... 其他多个类似的参数校验块
    
} catch (BizException e) {
    log.error("商户API前置请求业务失败,reqId={},{}-{}", reqId, e.getCode(), e.getMessage());
    resMap.setResCode(e.getCode());
    resMap.setResMsg(e.getMessage());
    response.getWriter().write(JsonUtils.toJson(resMap));
    return;
}

2. 自定义业务异常

BizException是一个简单的业务异常类,封装错误码和错误信息:

public class BizException extends RuntimeException {
    @Getter
    private String code;

    private BizException(String code, String message) {
        super(msg, null, true, false); // 将第4个参数 writableStackTrace 设置为false,规避使用自定义异常所带来的性能损耗
        this.code = code;
    }
    
    public static BizException build(String code, String message) {
        return new BizException(code, message);
    }
}

这样改造的主要优势

  • 校验逻辑纯净了if 语句只关心检查条件并抛出异常,职责单一。
  • 消除了重复代码:错误处理逻辑只在一处(catch 块)实现,符合DRY原则。
  • 代码可读性提高:业务主线更加清晰,不会被大量的错误处理代码所淹没。

三、为何这样能提升代码简洁性?

  1. 遵守 DRY 原则(Don't Repeat Yourself):通过将重复的错误处理逻辑抽取到一处(自定义异常),消除了代码冗余。
  2. 关注点分离(Separation of Concerns):业务代码只关注是否校验通过,异常处理则专注如何应对校验错误。这种分离使代码结构更清晰,更易于理解和维护。
  3. 使用异常控制流程:对于像参数错误这类"异常"情况,利用 Java 的异常机制来中断当前流程是更自然、更清晰的做法,避免了多层嵌套的 if-else 语句。
  4. 提升可读性:代码的主干逻辑(校验通过后要做什么)变得更加突出,不会被大量的错误处理代码所淹没。

四、总结

通过统一异常处理自定义异常机制,我们能够将参数校验这类重复且繁琐的代码转化为清晰、简洁且易于维护的形式。

关键的优化步骤包括

  1. 定义代表业务错误的自定义异常。
  2. 将分散的校验错误处理改为抛出异常。
  3. 在一处集中捕获和处理这些异常,构造响应并记录日志。

这种优化不仅减少了代码量,更大幅提升了代码的可读性、可维护性和健壮性,是迈向高质量Java代码的重要一步。这种基于纯Java语言的简单而有效的改造,无需依赖特定框架,在任何Java项目中都能立即应用并带来收益。

希望本文提供的思路和示例能对您编写clean code有所启发。

当然,凡事有利就有弊。毋庸置疑的是,使用异常捕获会导致系统性能下降。因此,对于高频调用、性能敏感且错误可预见的场景(如API参数校验),使用条件判断来直接返回错误,更适合。对于那些性能不极端敏感的场景(如定时任务、后台业务处理),可在追求代码整洁的原则下使用统一错误处理。

posted on 2025-09-18 23:28  buguge  阅读(29)  评论(1)    收藏  举报