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

时间:2011-07-06 04:14:51

标签: java jpa service-layer unchecked checked-exceptions

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

UserService.get(userId);

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

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

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

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

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

5 个答案:

答案 0 :(得分:25)

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

投放未经检查的例外可帮助您避免严重错误。

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

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

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

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

答案 1 :(得分:2)

我可能错了,但它可能与EJB容器处理异常的方式有关。来自Best practices in EJB exception handling

  

使用EJB容器的内部   家政,你必须有   抛出的已检查异常   未经检查的例外。

答案 2 :(得分:1)

我在以下视角中看到了检查/未检查的异常,(从标准JDK中获取线索)

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

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

答案 3 :(得分:0)

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

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

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

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

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

扣除付款然后   发送货件详情。   如果(发送货件详情不成功)   然后       退款   其他      发送成功邮件。

在上面的逻辑中,我发送运输细节的逻辑应该是 抛出一个检查过的例外,如果有任何问题,那么我必须将金额退还给客户。如果在这种情况下,我抛出一个未经检查的异常,唯一的事情 发生这种情况,用户可能会收到错误消息。(除非你抓住了运行时异常 - 哪个坏了。)

谢谢, Sathwick

答案 4 :(得分:0)

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

    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块在某种程度上可以减少这种混乱