服务层中的已检查与未检查的例外

我处理具有遗留服务层的项目,如果请求的记录不存在,则在许多地方返回null,或者由于呼叫者未被授权而无法访问。 我在谈论ID要求的特定记录。 例如,类似于:

UserService.get(userId); 

我最近推动改变这个API,或者补充一个抛出exception的新API。 关于已检查与未经检查的例外的争论随之而来。

从JPA / Hibernate等人的设计者那里得到一个注释,我建议未经检查的exception可能是最合适的。 我的论点是,无法合理地期望API的用户从这些exception中恢复,并且在99%的情况下,我们最多只能通知应用程序用户发生了一些错误。

将运行时exception传播到通用处理机制显然会减少处理边缘情况exception所涉及的大量复杂性和所需的分支处理。 但是,围绕这种方法存在很多问题(这是正确的)。

为什么选择JPA / EJB和Hibernate等项目的设计者使用未经检查的exception模型? 它有充分的理由吗? 有什么利弊。 使用这些框架的开发人员是否仍然可以使用适配器包装器之类的东西处理接近抛出它们的运行时exception?

我希望这些问题的答案可以帮助我们对自己的服务层做出“正确”的决定。

虽然我同意这样的观点,即未经检查的exception会产生更方便的API,但这并不是我认为最重要的好处。 相反,它是这样的:

抛出未经检查的exception可帮助您避免严重错误。

这就是原因。 鉴于十个开发人员被迫处理已检查的exception,您将获得二十种不同的策略来处理它们,其中许多是完全不合适的。 以下是一些较常见的不良方法:

  • 吞。 捕获exception并完全忽略它。 继续前进即使应用程序现在处于不稳定状态也没有发生任何事情。
  • 记录并吞咽。 抓住exception并记录它,认为现在我们要负责任。 然后继续前进,好像什么也没发生。
  • 神秘默认。 捕获exception,并将某些字段设置为某个默认值,通常不会告诉用户。 例如,如果您无法加载某些用户的角色,只需选择一个低权限角色并进行分配即可。 用户想知道发生了什么。
  • 愚蠢/危险的神秘默认。 捕获exception,并将某些字段设置为一些非常糟糕的默认值。 我在现实生活中看到的一个例子:不能加载用户的角色,所以继续并假设最好(即给他们一个高权限角色,以免给任何人带来不便)。
  • 误报。 开发人员不知道exception意味着什么,因此只是想出了自己的想法。 即使建立连接与问题无关, IOException变为“无法连接到服务器”。
  • 面具通过广泛捕获和误报。 尝试通过捕获Exception或(ugh) Throwable而不是方法实际抛出的两个已检查exception来清理代码。 exception处理代码不会尝试将(例如)资源可用性问题(例如某些IOException )与完全代码错误(例如NullPointerException )区分开来。 实际上,它经常任意选择其中一个例外,并将每个例外误报为该类型。
  • 面具通过巨大的尝试,并误报。 以前策略的一个变体是在一个大的try块的范围内放入一大堆exception声明的调用,然后捕获ExceptionThrowable因为没有其他任何东西可以处理所有抛出的exception。
  • 抽象不合适的重新抛出。 即使exception不适合抽象,也要重新抛出exception(例如,从应该隐藏资源的服务接口重新抛出与资源相关的exception)。
  • 没有包装的Rethrow。 重新抛出一个exception(未经检查或其他抽象适当的检查),但只是删除嵌套exception,这将使任何人有机会真正弄清楚发生了什么。
  • 戏剧性的回应。 通过退出JVM响应非致命exception。 (感谢这篇博文 。)

根据我的经验,看到上述方法比看到正确的响应更常见。 许多开发人员 – 甚至是“高级”开发人员 – 都认为必须不惜一切代价抑制exception,即使这意味着在不稳定的状态下运行应用程序。 这是危险的错误。

未经检查的exception有助于避免此问题。 不知道如何处理exception的开发人员往往会将exception视为难以克服的问题,而且他们并不会竭尽全力去抓住exception。 因此,例外只是冒泡到顶部,它们提供堆栈跟踪,并且可以以一致的方式处理它们。 在极少数情况下,实际上有更好的事情要做,而不是让exception冒出来,没有什么可以阻止你。

我可能是错的,但它可能与EJB容器处理exception的方式有关。 来自EJBexception处理的最佳实践 :

要使用EJB容器的内部管理,您必须将已检查的exception作为未经检查的exception抛出。

我在以下透视图中看到了checked / uncheckedexception,(从标准JDK中获取线索)

  1. 如果代码/库依赖于JVM外部的某些资源(例如.file / socket / remote api等,即JVM无法控制它),请使用Checkedexception。 您的代码必须处理源可能导致的所有可能的错误条件。 这是为了确保您的程序是健壮的,并且在该资源出现问题时优雅地失败。

  2. 未经检查的exception是严重的错误条件或代码中的实际BUGS,通常不应该在它发生之后处理。 您可以更正代码,以便首先不显示此错误。 (等式NLP,分类演员,除零等)

通常,当较高级别的代码无法处理它并且无法对exception执行任何操作时,抛出RuntimeException是一种很好的做法。

在您的情况下,如果结果是成功的,您的API将返回一些结果,如果没有找到结果,则返回null,如果在执行操作时出现任何错误,则抛出exception。

如果您只是向用户显示错误消息并且不再对该问题执行任何操作,则可以取消选中该exception。

另一方面,如果您计划在出现​​问题时采取一些替代步骤,那么您应该抛出一个已检查的exception。

例如,我有一个类似的代码 –

扣除付款,然后发送货件详细信息。 如果(发送货件详细信息不成功),则退还付款,否则发送成功邮件。

在上面的逻辑中,我发送运输详细信息的逻辑应该抛出一个检查exception,因为如果有任何问题,那么我必须将金额退还给客户。 如果在这种情况下我抛出一个未经检查的exception,那么用户可能会收到错误消息的唯一事情。(除非你捕获了runtimeexception – 哪个坏了。)

谢谢,沙斯威克

未经检查的exception有助于避免代码混乱。 例如,考虑这段代码

  try { File file = new File("file"); FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader(fileReader); String temp = ""; StringBuilder result = new StringBuilder(""); while ((temp = bufferedReader.readLine()) != null) { result.append(temp); } System.out.println(result); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } 

我们处理两种exception类型,但是如果通过处理这些exception没有可操作的事件,为什么要首先捕获它们呢? 答案不是抛出已检查的exception,因为最终调用层次结构的某些类必须处理它(或者它被抛出到JVM)。 如果开发人员真的对处理这些问题感兴趣,那么捕获解决问题的特定RuntimeException类。

包含多个exception的Catch块在某种程度上可以减少这种混乱