• Java异常处理


    异常的分层处理

    • adaptor
      • 统一拦截异常,返回ServiceData
    • app
      • 不做异常处理
    • infrastructure
      • 不做处理

    Result vs Exception

    Application层只返回DTO,可以直接抛异常,不用统一处理。所有调用到的服务也都可以直接抛异常,除非需要特殊处理,否则不需要刻意捕捉异常。

    异常的好处是能明确的知道错误的来源,堆栈等,在Interface层统一捕捉异常是为了避免异常堆栈信息泄漏到API之外,但是在Application层,异常机制仍然是信息量最大,代码结构最清晰的方法,避免了Result的一些常见且繁杂的Result.isSuccess判断。所以在Application层、Domain层,以及Infrastructure层,遇到错误直接抛异常是最合理的方法。

    异常使用原则

    1.只针对异常情况才使用异常,不应该用于控制流的逻辑判断

    如果类有"状态相关"的方法,应该提供一个"状态检测"方法,而不是强迫客户端为了正常的控制流而使用异常.

    • 提供专门的状态检测方法

    比如,Iterator接口的hasNext方法就是"状态检测"方法,它的next方法就是"状态相关方法".

    如果缺少hasNext方法

    try{
        Iterator<Foo> i = collection.iterator();
        while(true) {
            Foo foo = i.next();
            ...
        }
    } catch(NoSuchElementException) {
    }
    

    状态检测之后的做法:

    Iterator<Foo> i = collection.iterator();
    while(i.hasNext()) {
        Foo foo = i.next();
            ...
    }
    

    就无需关注异常的处理,代码可读性强.

    但是这种方式需要注意并发的问题,因为状态检测和执行方法不是原子的操作,在"状态检测"和"状态相关"方法之间,可能存在检测完状态后,状态被并发修改的情况.这时可以使用可识别的返回值来做状态检测.

    • 返回可识别的返回值

    如果"状态相关"方法返回一个标识值,比如null,表示处于不正确的状态中,那么就当作异常处理

    while(true) {
        String status = i.next();
        if(ERROE_STATUS.equlas(value) {
            // 执行其他操作
        }
    }
    

    相应地,这种方式的代码可读性差一点,如果忘记做状态校验,就会有bug.

    2.对于可恢复的异常用受检(checked)异常,对编程错误使用运行时异常(RuntimeException)

    • throwable可抛出结构
      • checked exception 受检异常
      • unchecked exception 非受检异常
        • runtime exception 运行时异常
        • error 错误

    说明一下这几种异常:

    • 受检异常
      • 如果期望调用这能够适当地恢复,应该使用受检异常.强迫调用者处理.
      • 因为受检异常往往指明了可恢复的条件,所以在设计API的时候,最好针对这个异常情况,提供一些辅助方法,调用者可以方便地获取有助于恢复的信息.
      • 使用受检异常的原则:
        • 正确地使用API不能阻止这种异常条件的产生
        • 一旦产生异常,客户端程序员可以立即采取有效措施
      • 不要过分使用受检异常,会给客户端增添负担-->可以用上文提到的"状态检测"方法,把受检异常变为非受检异常.但要注意缺少同步时的并发问题
    • 非受检异常
      • RuntimeException:用来表明编程错误.比如校验入参
        • 可预测的异常:如边界越界,空指针,这种异常不应该产生或抛出.要在代码里做好边界检查,空指针校验.
        • 需要捕捉的异常:比如RPC调用超时,这类异常客户端必须显示处理,不能因为服务端的异常导致客户端不可用,一般处理方式是重试或者降级处理
        • 可透出异常:比如框架的异常,程序无需关心
      • error:往往被JVM保留标识资源不足,或者其他使程序无法进行的错误.最好不要实现它的子类

    3.优先使用标准的异常

    原因很简单,因为大多数程序员都认识这些异常.

    4.抛出与抽象相对应的异常

    底层的异常被抛到高层,需要根据高层的的抽象含义做异常转译,能够被高层的使用者了解含义.

    推荐的做法是根据当前场景定义具有业务含义的异常.

    比如:对于i.next就是NoSuchElementException,而对于get方法就是IndexOutOfBoundsException

    public E get(int index) {
       ListIterator<E> i = listIterator(index);
       try {
       		return i.next();   
       } catch(NoSuchElementException e){
           throw new IndexOutOfBoundsException("Index:"+index);
       }
    }
    

    5.每个方法抛出的异常都要有文档

    Javadoc说明什么情况下抛出异常

    6.在细节消息中包含能捕获失败的信息

    7.努力使失败保持原子性

    失败的方法调用应该使对象保持再被调用之前的状态.调用方可能会执行一些回滚操作.

    8.不要忽略异常

    永远不要什么都不做地catch异常

    try {
        doSomehing();
    } catch(Exception e) {
        // do nothing
    }
    

    9.try-catch-finally

    加锁的情况,下面的代码,在try块内进行加锁,如果加锁失败,lock.unlock()就会报错.所以要再try块之前调用loc()方法,避免由于加索失败导致finally调用unlock()抛出异常.

    Lock lock = new XxxLock();
    preDo();
    try{
        // 无论加锁是否成功,unlock都会执行
        lock.lock();
        doSomething();
    } finally {
        lock.unlock();
    }
    
    

    10.错误码or抛出受检异常?

    • 对外提供的开放接口用错误码
    • 公司内服务之间用统一的Result封装错误码和错误信息
      • 如果使用异常,一旦客户端没有处理,就会产生运行时错误,导致程序中断
    • 应用内部推荐直接抛出异常
  • 相关阅读:
    学习笔记之pandas
    学习笔记之Nearest-Neighbour Searching with PostGIS
    学习笔记之Gurobi
    python基础之装饰器
    python作业
    python的位置参数、默认参数、关键字参数、可变参数区别
    python文件处理
    python基础之文件处理
    python基础之条件和循环
    python基础之函数
  • 原文地址:https://www.cnblogs.com/SimonZ/p/14923340.html
Copyright © 2020-2023  润新知