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

S73*_*17H 16 java jpa unchecked service-layer checked-exceptions

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

UserService.get(userId);
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Wil*_*ler 25

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

抛出未经检查的异常可帮助您避免严重错误.

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

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

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

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