服务层中的已选中与未选中的异常

我正在处理一个具有旧版服务层的项目,如果请求的记录不存在,或者由于调用方未获得授权而无法访问,则该服务层在许多位置返回 null。我说的是ID要求的特定记录。例如,像这样:

UserService.get(userId);

我最近推动更改此API,或者补充一个新的API,以引发异常。关于检查与未检查例外的辩论随之而来。

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

将运行时异常传播到通用处理机制显然会降低处理边缘情况异常所涉及的复杂性和所需的分支处理。但是,围绕这种方法有很多担忧(这是正确的)。

为什么 JPA/EJB 和 Hibernate 等项目的设计者选择使用未经检查的异常模型?有没有很好的理由?优点/缺点是什么。使用这些框架的开发人员是否仍应使用适配器包装器等内容处理运行时异常,使其接近它们被抛出的位置?

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


答案 1

虽然我同意未经检查的异常使API更方便的观点,但在我看来,这并不是最重要的好处。相反,它是这样的:

抛出未经检查的异常有助于避免严重的错误。

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

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

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

未选中的异常有助于避免此问题。不知道如何处理异常的开发人员倾向于将异常视为需要克服的不便,并且他们不会不厌其烦地捕获它们。因此,异常只是冒泡到顶部,在那里它们提供了堆栈跟踪,并且可以以一致的方式处理它们。在极少数情况下,实际上有比让异常冒泡更好的事情要做,没有什么能阻止你。


答案 2

我可能是错的,但它可能与 EJB 容器处理异常的方式有关。从 EJB 异常处理的最佳实践中

要使用 EJB 容器的内部管理,您必须将已检查的异常作为未选中的异常引发。


推荐