17 一月 2020

前言

对异常我们一般有这么几个处理方式:

  • 我处理掉, 让上层无感知.

  • 对异常进行封装, 封装成一个新的异常, 加入我的解释, 帮上层理解异常.

  • 不做处理.

如何判断是否要处理异常

if (当前代码在抛出可预期异常) { (1)
    处理异常()
} else if (根据我的分析, 这段代码会发生潜在异常) { (2)
    if (需要关心这种异常) { (3)
        处理异常()
    }
} else {
    // 再发生异常时就是我预测不到的, 我统称为 bug.  (4)
}
1 众所周知, java 中异常分为可预期(excepted)异常和 runtime 异常. 为什么会有可预期异常这种东西? 帮助经验不足 java 程序员知道这段代码有发生哪些异常
2 比如有网络请求, 有参数校验等, 我知道这段代码可能会在某种可以想到的情况下发生异常
3 如何判断是否需要关心? 我当前的程序不需要较好的健壮性, 这个异常也不会影响到别人, 没有人因为这个异常没处理会感到疑惑或者背地里骂我, 那么我就可以不关心
4 我想象到不到会出现什么异常. 当然, 如果发生线上问题, 就要背锅交学费了

如何处理异常

判断完是否要处理异常, 如果需要处理, 就要看看如何处理异常:

function 处理异常(ex) {
    if (我在非业务逻辑中) { (1)
        pass:q[**处理非业务异常(ex)**]
    } else if (我在业务逻辑中) {
        处理业务异常(ex)
    }
}
1 什么叫非业务异常? 跟业务逻辑无关的中立代码. 比如 发送 HTTP 请求的工具类, 初始化项目基础设施的代码

处理非业务异常

非业务代码中的异常, 一般是无法原地处理的, 都是要抛给上层, 让真正知道业务的地方去判断出了这种问题该如何处理.

function 处理非业务异常(ex) {

        if (我很确定重试后成功的概率很高) { (1)
            do: 考虑尝试 1-2 次
            如果成功就 ok, return
        }

        do: 抛出特定异常, 说明错误原因, 带上原始异常. 最好带上特定 异常 code, 带有结构化的数据能够对异常做详细说明 (2)
}
1 一般都是不能抢救的. 例外的, 比如创建链接超时这种不确定异常, 可以重试下, 很多 HTTP 请求框架也是有这么做的.
2 一个包含了足够信息的异常. 一个更有表现里的异常

处理业务异常

业务逻辑中处理异常会稍微复杂一些, 到这里已经不是一个技术问题, 而是个业务思维问题了.

先看流程吧:

function 处理异常(ex) {
    if (业务逻辑不需要回滚) {
        if (这种异常不需要处理, 或者可以被自动处理) {
            do: warn 日志|错误计数
            return
        } else {
            do: error 日志
            return
        }
    }

    if (不符合业务逻辑规则, 我可以抛给用户, 可以让用户知道发生了什么, 并且能够理解的) { // 非 bug
        do: 抛出 ServiceException, 带上错误 tip. 在用户接口层对这个错误进行处理, 返回提示, 打印 warn 日志, 或进行审计计数.
        return
    } else if (不符合业务逻辑规则, 但是也没有办法通知用户到用户的) {
        return
    } else if (系统内部运行不符合预期的) { // bug 或 问题
        do: 抛出 ServiceErrorException, tip: 系统错误. 统一 catch 输出异常提示, 打 error 日志
        return
    }
}

<1> <2> 通常是我不能处理的, 因为我不知道这个异常对于整个业务逻辑有着什么影响, 所以一般都是不能抢救的. 例外的, 对于创建链接超时这种不确定异常, 可以重试下, 很多 HTTP 请求框架也是有这么做的.

  • warn 日志 - 不太需要关注, 通常可以自动恢复

  • error 日志 - 错误, 需要报警关注

  • 异常收集计数 report

    1. 通常不太需要关注, 但是达到某种阈值会变成问题.

    2. 需要定期例行关注下.