18

我在一个带有遗留服务层的项目上工作,如果请求的记录不存在,或者由于调用者未经授权而无法访问,该服务层会在许多地方返回 null。我说的是 ID 要求的特定记录。例如,类似:

UserService.get(userId);

我最近推动了这个 API 的更改,或者补充了一个引发异常的新 API。关于已检查与未检查异常的争论随之而来。

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

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

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

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

4

5 回答 5

27

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

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

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

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

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

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

于 2011-07-06T06:36:51.520 回答
2

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

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

于 2011-07-06T05:01:31.087 回答
1

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

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

  2. 未经检查的异常是代码中的严重错误条件或真正的错误,通常不应在发生后处理。您可以更正您的代码,以便一开始就不会显示此错误。(Eq. NLP、Class cast、除以零等)

于 2011-07-06T07:17:00.930 回答
0

通常,当您的更高级别的代码无法处理并且无法对异常执行任何操作时,抛出 RuntimeException 是一个好习惯。

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

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

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

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

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

在上面的逻辑中,我发送运输细节的逻辑应该抛出一个检查异常,因为如果有任何问题,那么我必须将金额退还给客户。如果在这种情况下我抛出一个未经检查的异常,那么用户可能会收到错误消息。(除非你捕获运行时异常 - 这很糟糕。)

谢谢,萨思威克

于 2011-07-06T06:13:24.917 回答
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 块在某种程度上减少了这种混乱

于 2011-07-06T06:35:39.393 回答