如何处理未检查的异常?

6

我很难理解如何处理未检查的异常。

如果方法没有声明异常,而且我也不需要处理它,那我怎么知道这个方法会抛出异常呢?

据我所知,未检查的异常是由编程错误引起的,所以为了避免NullPointerException,我会引入一个空值检查。或者对于ArrayIndexOutOfBoundsException,我会检查如何访问数组。

但当我不知道一个方法会抛出自定义的未检查异常时,我该怎么处理呢?

那么如何将异常从数据层传播到用户界面呢?这些必须是已检查的异常,对吗?

我会让数据层抛出已检查的DataLayerExceptions,然后在主持人类中使用try-catch块(如果我正在使用MVP),然后在catch块中在视图上设置错误消息...

或者可以使用未检查的异常来完成吗?

编辑

感谢迄今为止的回复。

以下是我面临的具体例子。

我正在开始使用Spring,在一本书中看到了一个UserRepository类,它使用JDBCTemplate从数据库中检索数据。 UserRepositoty包含一个类似于此的方法:

@Override
public List<User> findAll() {
    String sql = "select id, username, email, password from p_user";
    return new ArrayList<>(jdbcTemplate.query(sql, rowMapper));
}

现在,jdbcTemplate的查询方法会抛出一个运行时异常DataAccessException。我通过查看方法签名得知这一点。
那么,我该如何处理这个异常呢?肯定需要给用户一些反馈信息,告诉他们发生了什么错误。同时,我也要记录下这个异常。
但是,我应该在哪里捕获这个异常呢?
是应该立即捕获并重新抛出自己的未检查异常吗? 还是应该在findAll方法上声明throws DataAccessException,并在调用类(可能是某种服务或Presenter)中处理异常呢?
又或者,我应该确保永远不会发生抛出异常的情况(如果这些条件可以由我控制的话)?

1
抛出自定义的未检查异常,我该如何处理?你不需要处理。如果是未检查异常,通常不会期望调用者进行恢复。 - Elliott Frisch
你可以不去管它,因为你无法对其进行任何操作,或者如果你还不知道这是什么,你可以捕获RuntimeException来处理这个自定义的未检查异常。 - glee8e
如果你想捕获任何类型的异常,你可以添加一个catch (RuntimeException e)块,甚至是最通用的Exception类型。 - airos
你看到了 https://dev59.com/DW025IYBdhLWcg3wRDq8?rq=1 吗? - user180100
@GhostCat 除了决定很难之外,我已经编辑了我的问题,我会等一下,也许有人会回应我的编辑。 - user3813234
完全没问题。这只是我的常规做法...当我遇到未被接受的旧问题答案时,我会尝试弄清楚是“被遗忘的接受”还是“仍在等待”或者“回答不够好”。因为在后一种情况下,我可能能够采取一些措施。 - GhostCat
3个回答

9
我对如何处理未经检查的异常感到困惑。
如何处理它们很容易。您可以像处理已检查的异常一样处理它们。您可以使用 try/catch(要捕获所有异常,请捕获 RuntimeException 或 Exception,如果还需要已检查的异常,请注意不要捕获 Error 或 Throwable,并且不要捕获超出您所期望或实际处理的异常)。
你真正想问的是如何知道它们何时被抛出。那就更困难了。
- 如您所见,某些未经检查的异常是由语言本身引发的。理论上,可能写出代码来避免这种情况。实际上:1) 人们会犯错误,2) 太多的防御检查很麻烦...并且毫无意义。 - 其他未经检查的异常明确声明为被“抛出”,或者在方法或构造函数的 javadocs 中记录为被“抛出”。 (实际上,我会说,一个设计良好的 API 应该记录它希望抛出或传播的未经检查的异常。) - 在问题情况下,直到运行时才能看到异常。
最后一种情况说明了在开发周期中作为完整和可重复测试的需要。
但当我不知道方法是否抛出自定义未经检查的异常时,我该如何处理?
通常情况下,你不用处理。
任何“意外”的异常都应视为错误。您在测试或生产中找到它。你修复它。如果已正确修复,则意外异常不会再次出现...或者现在已经被期望并且代码可以处理它。
推论是,如果您确实捕获并处理了意外异常,那么让您的代码假装什么坏事也没发生是一个真正的糟糕主意。至少,需要记录异常以便程序员拥有一些信息以查找根本原因。
那么,如何从数据层传播异常到用户界面?
通常,向用户显示异常(特别是堆栈跟踪!)是一个不好的主意。通常,您捕获它们,将其记录...以及堆栈跟踪...然后告诉用户:“发生了一些错误:请向 XXX 报告此问题,并提供以下额外信息”。
除上述情况外,不必是已检查的异常。
我会让数据层引发已检查的 DataLayerExceptions,然后在主持人类中设置 try-catch 块(如果使用 MVP),然后在 catch 块中在视图上设置错误消息。
你可以这样做。除了向用户显示异常的要点之外。
显然,如果你采用这种方法,你需要能够区分你可以解释,处理和“继续”的预期异常和不能解释且无法恢复的意外异常。你可能已经有一些方案来做到这一点...
底线是,没有简单的公式或“最佳实践”可以为您解决这个问题。它需要思考和设计...还需要小心...和测试。

谢谢您的回复! - user3813234
这个回答值得更多的声望。谢谢@stephen C - Calicoder

2
当你编写小型“学习”应用程序时,只需让未经检查的异常“冒泡”到主方法,并在命令行上打印堆栈跟踪是完全可以的。
在“现实世界”的应用程序中,你需要确保某个层次能够捕获此类异常并执行以下操作:
- 向用户显示有意义的错误消息 - 创建某种日志条目以供以后调试使用 - 决定应用程序是否可以继续运行或应该“崩溃”(在现实世界中很少这样做)
换句话说:“现实世界”应用程序的一个重要的非功能性要求是健壮性。你不希望在某个地方缺失了空值检查而导致整个应用程序崩溃。因此,你需要确保应用程序能够在这些情况下继续运行。(请注意:我并不是说这些东西很容易实现)。
除此之外: 你可以使用自己的已检查异常; 但是这样做的一个很大的缺点是: 它们会破坏你的方法签名。但这是一个高度主观的讨论(尽管某些重要人物声称"已检查 vs. 未检查的战争已经结束了; 未检查的胜利了"),Oracle 的官方文档仍然建议checked异常在现实世界中有其作用。

谢谢您的回复!那么在一个真实世界的复杂应用程序中,您将如何处理这个问题? - user3813234
1
以一种无法简单回答的方式。如果我能做到的话,我早就这么做了;-) 本质上是:这是一个广泛的主题;你可以花费无数的时间进行研究;和无数的项目工作来处理它... - GhostCat

1
通常情况下,您不应该捕获突然出现的、完全意料之外的 RuntimeExceptions。这会很丑陋(您的代码将充满 try-catch 语句),而且没有普遍优雅的方法来从中恢复。
许多这些 RuntimeExceptions 将由于编程错误而抛出,您在测试应用程序时应该修复这些错误。修复导致 NullPointerException 等错误的根本原因胜过调用代码可能做的任何捕获和修复。
有时,API会抛出RuntimeExceptions(请阅读其文档),您需要捕获它们。例如,参数检查可能导致抛出IllegalArgumentException。在这些情况下,捕获这些特定异常并妥善处理它们可能是有意义的(例如,将它们重新抛出为自定义已检查异常;重新抛出时记得使用cause参数)。在这些IllegalArguments的情况下,您可以将用户的输入标记为无效,并请求进行修复。优秀的API将广泛描述何时会发生什么。阅读反对已检查异常的理由以了解任何API程序员为什么要抛出未经检查的异常。
然而,正如Ghostcat(很棒)的回答中所发现的那样,如果应用程序将拥有超出编程练习的用户,则绝不能使用main()中的隐式catch-everything-and-show-stacktraces。对于非开发人员来说,它们毫无意义,并且除非他们知道在抛出异常之前发生了什么,否则无法为实际开发人员提供足够的上下文。因此,最好避免这种做法。
  • 记录任何未捕获的异常,以便稍后找出发生了什么。在抛出异常之前,您应该已经使用日志记录来帮助您弄清楚发生了什么。

  • 为触发它们的用户提供有用的反馈,以便他们可以采取行动。请注意,“有用”取决于用户;对于电子商务 Web 应用程序,只有开发人员会认为异常详细信息有用;而买家将更喜欢友好、低详细信息的错误消息,基本上要求他们稍后重试。

相比上述两点,将所有未检查的异常都包装在自定义的已检查异常中是不明智的做法。这解决不了问题:您仍然需要尝试采取上述两个步骤来稍后修复问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接