我的直觉是,在调用者可能可以以某种有益的方式恢复的情况下,应该使用已检异常,而未检异常则更适用于不可恢复的情况,但我很想听听其他人的想法。
以下是我多年开发经验后的观点:
已检查异常。这是业务用例或调用流程的一部分,也是我们预期或不预期的应用程序逻辑的一部分。例如连接被拒绝、条件不满足等。我们需要处理它,并向用户显示相应的消息,说明发生了什么以及下一步该怎么做(稍后再试等)。 我通常称之为后处理异常或“用户”异常。
未检查异常。这是编程异常的一部分,是软件代码编程中的一些错误(bug、缺陷),并反映了程序员必须按照文档使用API的方式。如果外部库/框架文档说它希望在某个范围内获得数据并且非空,因为将抛出NPE或IllegalArgumentException,程序员应该按照文档正确使用API。否则,将抛出异常。 我通常称之为预处理异常或“验证”异常。
根据目标受众。现在让我们谈谈异常的目标受众或设计人群(根据我的观点):
按应用程序开发生命周期阶段划分。
框架通常使用未检查异常(例如Spring)的原因是框架无法确定您的应用程序的业务逻辑,这取决于开发人员捕获并设计自己的逻辑。
我同意在设计API时,作为规则使用未检查的异常。调用者总是可以选择捕获已记录的未检查异常。你只是不需要无端地强制调用者这样做。
我发现在低级别上,使用已检查异常作为实现细节非常有用。它通常似乎比管理指定的错误“返回代码”更好的控制流机制。它有时还可以帮助看到低级别代码更改的影响...声明下游的已检查异常并查看谁需要进行调整。如果有很多通用的catch(Exception e)或throws Exception,则最后一点并不适用,这通常也不是太经过深思熟虑。
我认为我们可以考虑从几个问题中豁免:
为什么会发生异常?当它发生时我们该怎么办
由于错误,出现了一个漏洞。例如调用了空对象的方法。
String name = null;
... // some logics
System.out.print(name.length()); // name is still null here
String name = ExternalService.getName(); // return null
System.out.print(name.length()); // name is null here
在这里,如果名称为空,则需要检查是否为null,如果想要继续,否则可以让它不变,并在此处停止并给调用者运行时异常。这种异常无需检查。
通过外部的运行时异常,您无法控制或信任外部服务。
在这里,如果发生异常,您可能需要捕获来自ExternalService的所有异常,如果想要继续,否则可以让它不变,并在此处停止并给调用者运行时异常。
通过外部的已检查异常,您无法控制或信任外部服务。
在这里,如果发生异常,您可能需要捕获来自ExternalService的所有异常,如果想要继续,否则可以让它不变,并在此处停止并给调用者运行时异常。
在这种情况下,我们需要知道ExternalService中发生了什么样的异常吗?这取决于:
如果您可以处理某些类型的异常,则需要捕获并处理它们。对于其他异常,请将其冒泡。
如果您需要记录或向用户响应特定的异常,则可以捕获它们。对于其他异常,请将其冒泡。
已检查异常适用于可恢复的情况,其中您希望向调用者提供信息(即权限不足、文件未找到等)。
未经检查的异常很少使用,如果有的话,仅用于在运行时通知用户或程序员发生严重错误或意外情况。如果您正在编写将由他人使用的代码或库,请勿抛出它们,因为他们可能不会期望您的软件抛出未经检查的异常,因为编译器不强制捕获或声明它们。