关于springboot:Spring-Boot工程中如何优雅地处理异常

58次阅读

共计 3955 个字符,预计需要花费 10 分钟才能阅读完成。

转自: https://taosha.club/topic/61a…

对于异样解决, 有几个准则

应用异样而非返回码

在很久以前,许多语言都不反对异样。这些语言解决和汇报谬误的伎俩都无限。你要么设置一个谬误标识,要么返回给调用者查看的错误码。以下代码展现了这些伎俩

public class DeviceController {public void sendShutDown() {DeviceHandle handle = getHandle(DEV1);
        // Check the state of the device
        if (handle != DeviceHandle.INVALID) {
            // Save the device status to the record field
            retrieveDeviceRecord(handle);
            // If not suspended, shut down
            if (record.getStatus() != DEVICE_SUSPENDED) {pauseDevice(handle);
                clearDeviceWorkQueue(handle);
                closeDevice(handle);
            } else {logger.log("Device suspended. Unable to shut down");
            }
        } else {logger.log("Invalid handle for:" + DEV1.toString());
        }
    }
}

这类伎俩的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻查看谬误。可怜的是,这个步骤很容易被忘记。所以,遇到谬误时,最好抛出一个异样。调用代码很整洁,其逻辑不会被错误处理搞乱。

以下代码展现了在办法中遇到谬误时抛出异样的情景

public class DeviceController {public void sendShutDown() {
        try {tryToShutDown();
        } catch (DeviceShutDownError e) {logger.log(e);
        }
    }

    private void tryToShutDown() throws DeviceShutDownError {DeviceHandle handle = getHandle(DEV1);
        DeviceRecord record = retrieveDeviceRecord(handle);
        pauseDevice(handle);
        clearDeviceWorkQueue(handle);
        closeDevice(handle);
    }
}

留神这段代码整洁了很多。这不仅关乎好看。这段代码更好,因为之前纠结的元素设施敞开算法和错误处理当初被隔离了。你能够查看其中任一元素,别离了解它。

给出异样产生的环境阐明

你抛出的每个异样,都该当提供足够的环境阐明,以便判断谬误的起源和处所。在 Java 中,你能够从任何异样里失去堆栈形迹(stack trace);然而,堆栈形迹却无奈通知你该失败操作的初衷。

应创立信息充沛的谬误音讯,并和异样一起传递进来。在音讯中,包含失败的操作和失败类型。如果你的应用程序有日志零碎,传递足够的信息给 catch 块,并记录下来。

别返回 null 值

要探讨错误处理,就肯定要提及那些容易引发谬误的做法。第一项就是返回 null 值。我不想去计算已经见过多少简直每行代码都在查看 null 值的应用程序。上面就是个例子:

public void registerItem (Item item){if (item != null) {ItemRegistry registry = peristentStore.getItemRegistry();
        if (registry != null) {Item existing = registry.getItem(item.getID());
            if (existing.getBillingPeriod().hasRetailOwner()) {existing.register(item);
            }
        }
    }
}

这种代码看似不坏,其实糟透了!返回 null 值,基本上是在给本人减少工作量,也是在给调用者添乱。只有有一处没查看 null 值,应用程序就会失控。

你有没有留神到,嵌套 if 语句的第二行没有查看 null 值?如果在运行时 persistentStore 为 null 会产生什么事?咱们会在运行时失去一个 NullPointerException 异样,兴许有人在代码顶端捕捉这个异样,也可能没有捕捉。两种状况都很蹩脚。对于从应用程序深处抛出的 NullPointerException 异样,你到底该作何反馈呢?

能够搪塞说上列代码的问题是少做了一次 null 值查看,其实问题多多。如果你打算在办法中返回 null 值,不如抛出异样,或是返回特例对象。如果你在调用某个第三方 API 中可能返回 null 值的办法,能够思考用新办法打包这个办法,在新办法中抛出异样或返回特例对象。

在许多状况下,特例对象都是爽口良药。构想有这么一段代码:

List<Employee> employees = getEmployees();
if (employees != null) {for(Employee e : employees) {totalPay += e.getPay();  
    }
}

所幸 Java 有 Collections.emptyList() 办法,该办法返回一个预约义不可变列表,可用于这种目标:

public List<Employee> getEmployees () {if ( ..there are no employees ..)
            return Collections.emptyList();}

这样编码,就能尽量避免 NullPointerException 的呈现,代码也就更整洁了。

别传递 null 值

在办法中返回 null 值是蹩脚的做法,但将 null 值传递给其余办法就更蹩脚了。除非 API 要求你向它传递 null 值,否则就要尽可能防止传递 null 值。

举例说明起因。用上面这个简略的办法计算两点的投射:

public class MetricsCalculator{public double xProjection(Point p1, Point p2) {return (p2.x – p1.x) * 1.5;  
    }  
    …
}

如果有人传入 null 值会怎么?

calculator.xProjection(null, new Point(12, 13));

当然,咱们会失去一个 NullPointerException 异样。

如何修改?能够创立一个新异样类型并抛出:

public class MetricsCalculator {public double xProjection(Point p1, Point p2) {if (p1 == null || p2 == null) {throw InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection");
        }
        return (p2.x –p1.x) *1.5;
    }
}

这样做好些吗?可能比 null 指针异样好一些,但要记住,咱们还得为 InvalidArgumentException 异样定义处理器。这个处理器该做什么?还有更好的做法吗?

还有代替计划。能够应用一组断言:

public class MetricsCalculator {public double xProjection(Point p1, Point p2) {assert(p1 != null,  "p1 should not be null");
        assert(p2 != null,  "p2 should not be null");
        return (p2.x –p1.x) *1.5;
    }
}

看上去很美,但仍未解决问题。如果有人传入 null 值,还是会失去运行时谬误。思考下, 还能够怎么持续优化?

整洁代码是可读的,但也要强固。可读与强固并不抵触。 如果将错误处理隔离对待,独立于次要逻辑之外,就能写出强固而整洁的代码 。做到这一步,咱们就能独自解决它,也极大地晋升了代码的可维护性。

我的项目中的最佳实际

  • 千万别吞噬
  • 应用全局异样捕捉,避免前端用户看到不该看到的
  • 异样堆栈残缺打印
  • 自定义异样可打印 3 到 5 行异样栈就够用
  • 定义错误码枚举值,分门别类分明
  • 不要在异样 catch 中做业务操作
  • 对于无需关注的可控异样(checked exception), 应用 @SneakyThrows, 不要在业务代码里写 try/catch 来解决

    可控异样的代价就是违反凋谢 / 闭合准则。如果你在办法中抛出可控异样,而 catch 语句在三个层级之上,你就得在 catch 语句和抛出异样处之间的每个办法签名中申明该异样。这意味着对软件中较低层级的批改,都将波及较高层级的签名。批改好的模块必须从新构建、公布,即使它们本身所关注的任何货色都没改变过。

  • 如无必要, 不要在业务代码里写 try/catch, 对立交由 spring boot 全局异样拦截器解决 (全局异样拦截器会做 ” 异样堆栈残缺打印 ”, “ 定义错误码枚举值,分门别类分明 ” 这几件事, 不必业务开发人员时刻操心), 以下状况例外:

    • lamda 表达式中的必须捕捉异样;
    • 新起的线程, 异样会被吞噬;
    • 有的异样不能影响业务, 要本人解决掉, 这种状况的法则是: 如果产生异样, 心愿逻辑继续执行上来, 就本人 try/catch, 否则就不要 try/catch, 交由对立异样拦截器解决

依据以上准则和最佳实际实操优化

具体的问题和优化过程 review 时解说

总结

咱们写的代码根本能够分为三种:

  • 一种是实现业务失常逻辑的代码;
  • 一种是为了实现业务逻辑, 不得不写的额定操作的代码;
  • 一种是小心翼翼地解决各种谬误或意外逻辑的代码;

作为程序猿, 应时刻思考下, 本人写的代码是否是第一种代码, 如果不是, 看看能够如何优化缩小第二 or 第三种代码, 毕竟真正有价值, 且能带给咱们愉悦感的都是第一种代码;

正文完
 0