异常如何处理

2020/01/01 Java基础 共 2031 字,约 6 分钟

系统工程无论保护措施如何完善,事前预案如何周密,异常现象或多或少、或早或迟地都会发生。

系统发生异常后,往往需要人工介入处理,否则将会扩大异常影响面,或者引发新的异常。

在计算机世界中 , 在运行程序时,发生了意料之外的事件,阻止了程序的正常执行,这种情况被称为程序异常。

处理程序异常,需要解决以下 3 个问题 :哪里发生异常?谁来处理异常?如何处理异常?

哪里发生异常

首先,需要明确在哪里发生异常。在代码中通过 try-catch 来发现异常,但是有些程序员往往将大段代码定义在一个try-catch 块内 , 这样非常不利于定位问题,是一种不负责任的做法。

捕获异常时需要分清稳定代码和非稳定代码 , 稳定代码指的是无论如何都不会出锚的代码,例如 int a= 0。异常捕获是针对非稳定代码的,捕获时要区分异常类型并做相应的处理。比如,当用户输入了错误的用户名,提示用户账号错误,正确的用户名下,错误的密码请重试;重试次数超过限制,则封锁账户等。

谁来处理异常

其次,判断谁来处理异常,在回答这个问题之前,需要明确两个关键字 throw 和throws 的区别,如下是数据层生成订单id的示例代码:

 public Long generateOrderId(Long userId) throws DAOException {
        try {
            return orderIdSequence.nextValue()*100 + (userId*100) + (userId/100);
        } catch (Exception e) {
            throw new DAOException("Sequence error , userId = " + userId, e);
        }
    }

在与数据库交互时可能会发生网络连接不通、数据库锁超时、插入数据失败等异常,向上归一化为 DAOException 异常。

这里的 throw 是方法内部抛出具体异常类对象的关键字, 而 throws 则用在方法 signature 上,表示方法调用者可以通过此方法声明向上抛出异常对象。

如何处理异常?

了解了 throw 和 throws 的作用后,我们再来判断当前被捕获的异常是否需要自己处理。如果异常在当前方法的处理能力范围之内且没有必要对外透出,那么就直接捕获异常并做相应处理;否则向上抛出 , 由上层方法或者框架来处理。

最后,无论采用哪种方式处理异常 , 都严禁捕获异常后什么都不做或打印行日志了事。如果在方法内部处理异常, 需要根据不同的业务场景进行定制处理 , 如重试、回滚等操作。

如果向上抛出异常,如上例所示 , 需要在异常对象中添加上下文参数、局部变量、运行环境等信息,这样有利于排查问题。

异常分类

JDK 中 定义了 套完整的异常机制 , 所有异常都是 Throwable 的子类,分为 Error ( 致命异常)和 Exception (非致命异常)。

Error 是 一 种非常特殊的异常 类型,它的出现标识着系统发生了不可控的错误 , 例如 StackOverflowError、OutOfmemoryError。针对此类错误,程序无法处理,只能人工介入。

Exception 又分为 checked 异常 ( 受检异常)和 unchecked 异常(非受检异常)。

checked 异常是需要在代码中显式处理的异常 , 否则会编译 出 错。如果能自行处理则可以在当前方法中捕获异常,如果无法处理,贝lj继续向调用方抛出异常对象。常见的 checked 异常包括 JDK 中定义的 SQLException ,C lassNotFoundException 等。

unchecked 异常是运行时异常,它们都继承自 RuntimeException ,不需要程序进行显式的捕捉和处理 ,包括NullPointerException ,IndexOutOtBoundsException ,DubboTimeoutException (必须显式处理的异常,不能因服务端的异常导致客户端不可用,此时处理方案可以是重试或者降级处理等 )等。

异常的抛与接

传递异常信息的方式是通过抛出异常对象 , 还是把异常信息转成信号量封装在特定对象中 , 这需要方法提供者和方法调用者之间达成契约,只有大家都照章办事,才不会产出误解。

推荐对外提供的开放接口使用错误码公司内部跨应用远程服务调用优先考虑使用 Result 对象来封装错误码、错误描述信息;而应用内部则推荐直接抛出异常对象

为什么在远程服务调用中推荐使用 Result 对象封装异常信息?如果使用抛异常的返回方式,一旦调用方没有捕获,就会产生运行时错误 ,导致程序中断。此外,如果抛出的异常中不添加栈信息,只是 new 自定义异常并加入自定义的错误信息 , 对于调用端解决问题的帮助不会太大。如果加了栈信息,在频繁调用出错的情况下,信息序列化和传输的性能损耗也是问题。

我们都知道空指针异常(NPE)是程序世界里最常见的异常之一,根据防御式编程理念所以我们推荐方法的返回值可以为 null ,不强制返回空集合或者空对象等,但是必须添加注释充分说明什么情况下会返回 null 值。防止 NPE 定是调用方的责任 , 需要调用方进行事先判断

文档信息

搜索

    Table of Contents