我在一个带有旧服务层的项目上工作,如果请求的记录不存在,或者由于调用者未得到授权而无法访问,则在很多地方返回null。我说的是ID要求的特定记录。例如,类似:
UserService.get(userId);
最近,我一直在努力更改此API,或者用引发异常的新API进行补充。随之而来的是关于检查异常与未检查异常的争论。
从JPA / Hibernate等所有设计师的笔记中,我建议未检查的异常可能是最合适的。我的观点是,不能合理地期望API用户从这些异常中恢复,并且在99%的情况下,我们最多只能通知应用程序用户发生了一些错误。
将运行时异常传播到通用处理机制显然会减少很多复杂性,并减少处理边缘情况异常所需的分支处理。但是,围绕这种方法有很多担忧(正确地如此)。
为什么诸如JPA / EJB和Hibernate之类的项目的设计人员选择使用未经检查的异常模型?有很好的理由吗?有什么优点/缺点。使用这些框架的开发人员是否应该仍在处理运行时异常,例如使用适配器包装程序抛出异常的地方?
我希望这些问题的答案可以帮助我们对自己的服务层做出"正确"的决定。
尽管我同意未经检查的异常可以使API更加方便的观点,但我认为这并不是最重要的好处。而是这样的:
抛出未经检查的异常可以帮助您避免严重的错误。
这就是为什么。鉴于十个开发人员被迫处理受检查的异常,您将获得二十种不同的策略来处理它们,其中许多是完全不合适的。以下是一些较常见的错误方法:
吞。捕获异常并完全忽略它。即使应用程序现在处于不稳定状态,也可以继续进行,好像什么都没发生。
记录并吞下。捕获异常并记录下来,以为现在我们要负责了。然后继续前进,好像什么也没发生。
谜底默认。捕获异常,然后将某些字段设置为某些默认值,通常无需告知用户。例如,如果您无法加载某些用户角色,则只需选择一个低特权角色并进行分配即可。用户想知道发生了什么。
愚蠢/危险的神秘预设。捕获异常,并将某些字段设置为某些非常差的默认值。我在现实生活中看到的一个示例:无法加载用户的角色,因此继续尝试并发挥最好的作用(即为他们提供高特权角色,以免给任何人带来不便)。
错误报道。开发人员不知道异常意味着什么,因此只提出自己的想法。即使建立连接与该问题无关,IOException也会变为"无法连接到服务器"。
通过广泛的掩盖和误报。尝试通过捕获Exception或(ugh)Throwable而不是方法实际抛出的两个检查的异常来清理代码。异常处理代码不会尝试将资源可用性问题(例如某些IOException)与直接代码错误(例如NullPointerException)区分开。实际上,它通常会任意选择一个例外,并将每个例外都错误地报告为此类例外。
通过大量尝试掩盖,并误报。先前策略的一种变体是将一大堆异常声明调用放在单个大型try块的范围内,然后捕获Exception或Throwable,因为没有别的东西可以处理所有抛出的异常。
抽象不当的重新抛出。即使该异常不适合抽象,也要抛出该异常(例如,从应该隐藏资源的服务接口中重新抛出与资源相关的异常)。
重新包装而不包裹。抛出一个异常(未检查或抽象适当的检查),但是只需删除嵌套的异常,该异常将使任何人都有机会真正了解正在发生的事情。
戏剧性的反应。通过退出JVM来响应非致命异常。 (感谢这篇博客文章。)
以我的经验,看到上面的方法比看到正确的答案要普遍得多。许多开发人员-甚至是"高级"开发人员-都具有必须不惜一切代价抑制异常的想法,即使这意味着在不稳定的状态下运行该应用程序。这是危险的错误。
未经检查的异常有助于避免此问题。不知道如何处理异常的开发人员倾向于将异常视为要克服的不便之处,并且他们不会全力以赴地捕获异常。因此,异常只会冒泡地到达顶部,在那里它们提供了堆栈跟踪,并且可以以一致的方式对其进行处理。在极少数情况下,实际上要做的事要比使异常冒出来要好得多,没有什么可以阻止您。
我可能是错的,但它可能与EJB容器处理异常的方式有关。从EJB异常处理的最佳实践中:
To use the EJB container's internal
housekeeping, you will have to have
your checked exceptions thrown as
unchecked exceptions.
我从以下角度看待检查/未检查的异常(从标准JDK中获得线索)
如果您的代码/库依赖于JVM外部的某些资源,请使用Checked异常(例如.file / socket / remote api等,即JVM无法控制它)。 您的代码必须处理源可能导致的所有可能的错误情况。 这是为了确保您的程序健壮并且在该资源出现问题的情况下正常运行。
未经检查的异常是指严重的错误情况或代码中的实际错误,通常不应在发生错误后进行处理。 您可以更正您的代码,以便首先不会显示此错误。 (等式NLP,类别转换,被零除等)
未检查的异常有助于避免代码混乱。例如考虑此代码
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();
}
我们处理两种异常类型,但是如果处理这些异常不会发生可操作的事件,那么为什么要首先捕获它们?答案是不要抛出检查的异常,因为最终,调用层次结构中的某些类将必须处理它(否则它将被抛出给JVM)。如果开发人员真的有兴趣处理这些问题,请捕获解决该问题的特定RuntimeException类。
包含多个异常的catch块在某种程度上减少了混乱
通常,当较高级别的代码无法处理RuntimeException并且对异常无法执行任何操作时,抛出RuntimeException是一个好习惯。
对于您的情况,如果结果成功,API将返回一些结果,并且应返回
如果没有找到结果,则为null,如果执行操作时出现任何错误,则引发异常。
如果您只是向用户显示错误消息,并且不对该问题进行任何处理,则可以选中"未选中"异常。
另一方面,如果您计划在出现??问题的情况下采取一些替代步骤,则应该抛出一个已检查的异常。
例如,
我有一个类似的代码-
扣除付款,然后
发送货件详细信息。
如果(发送货运详细信息失败)
然后
退还款项
其他
发送成功邮件。
在以上逻辑中,我发送货运详细信息的逻辑应
抛出一个经过检查的异常,如果有任何问题,那么我必须将这笔款项退还给客户。如果在这种情况下,我抛出了未经检查的异常,那么唯一的事情就是
否则用户可能会收到错误消息。(除非您捕获了runtimeException-这很糟糕。)
谢谢,
萨斯威克