何时适合抛出异常?

4

最近我看到一些代码,作者几乎在每个构造函数中都抛出异常,并在像下面这样的返回int类型的方法中抛出运行时异常:

if(condition){
  return 1;
}
if(condition){
  return 2;
}
if(condition){
  return 3;
}
throw new RuntimeException("Unreachable code");
// method ends here

我个人不会在那里抛出异常,因为我会使用if和else if语句进行结构化处理,在这种特定情况下,您的代码如果不能满足其中一个条件,则基本上是错误的。

有很多地方可以抛出运行时异常,如果您的代码正常工作,就永远不会到达这些地方。有时候似乎作者不相信代码能够正常工作,在上面的代码块中就是如此。此外,每个构造函数都可以抛出异常,以防它不能正确初始化,但您也可以将其结构化,使对象为空-您随后可以在主函数中检查它。

我的问题基本上是,什么时候值得抛出异常?


请注意,“无法访问的代码”是如此有用的错误信息... - Arnaud
你可以说这是“防御性编程”。它可以防止其他人添加错误。 - Steve Smith
2个回答

4
异常的目的是传达“异常”情况。
在这种意义下:如果在您的示例中所有条件都是“false”,且也没有有效的返回值来指示该情况是绝对不可预期的,那么抛出RuntimeException是合理的选择。但我可能会将消息更改为:
throw new RuntimeException("All conditions failed: " + some data)

如前所述:这是关于沟通的问题;在这种情况下,向调试问题的人传达信息可能会有所帮助。因此,在此处包含理解为什么所有这些检查都返回 false 所需的信息可能会很有用。

关键是:该方法有一个合同;而且该合同应该包括这些详细信息。也就是说:如果该方法是公共的,则应该添加一个带有清晰描述的 @throws RuntimeException。

在这种情况下,使用 RuntimeException 也是一种有效的做法;因为您不想在各个地方使用已检查的异常来污染您的方法签名。

编辑:当然,需要进行平衡。例如,我的类经常看起来像:

public class Whatever {
  private final Foo theFoo;

  public Whatever(Foo theFoo) {
   Objects.requireNonNull(theFoo, "theFoo must not be null");
   this.theFoo = theFoo;

所以,在我的构造函数中可能会抛出NPE;是的。但是:只有那里。所有方法都可以依赖于所有字段都被初始化为非空;而且它们是final,因此它们始终是非空的。
这意味着:必须保持合理; 并“开发”感觉:哪些问题是异常但可能的; 哪些问题是如此不可能,以至于你不需要在代码中随处检查它们。
最后;仅为了明确 - 添加异常仅是方程式的一部分。当某些东西抛出时,您需要捕获某些东西!因此,正如所说:平衡是关键。您在代码中所做的任何事情都必须“添加价值”(即达到既定目的)。如果您的代码没有明确定义的目的,则很有可能:您不需要它!

我同意,我经常使用这种方法将枚举类型转换为其他枚举类型或字符串。如果出现异常,显然是我(或维护者)忘记将该条件添加到翻译逻辑中的实例,但如果在生产中发生这种情况,则无法做任何事情,抛出异常是最佳解决方案。 - Daniel Bickler
是的,那将是一个很好的例子。但至少在切换枚举时,您可以使用“默认”原因。 - GhostCat
@GhostCat 在代码库中经常会抛出异常,这是很常见的吗?是否可以说几乎每个方法都可能有异常情况,因此在几乎每个方法中看到异常被抛出是很常见的? - screeb

-1

GhostCat已经基本涵盖了我们何时以及为什么应该使用异常的所有内容。更进一步,最好的做法是权衡包括异常的成本效益。在这个上下文中,成本指的是性能以及应用程序的降级客户友好性,而收益则是应用程序的平稳运行以及用户友好性。在我看来,首先应该区分应用程序和系统错误。然后,将这些错误进一步分为编译时和运行时进行审查(请注意,编译时错误通常不需要使用异常处理,但是要使用调试工具(如C++的assert)来调试和查找问题)。即使异常处理程序的细节取决于特定应用程序的上下文,但通常可以将以下原则作为起点:

1-识别代码的关键热点崩溃点;

2-区分系统和应用程序错误;

3-识别运行时和编译时错误;

4-使用调试工具(如assert或预处理器指令)处理编译时错误。此外,还要包括异常处理程序或跟踪或调试来处理运行时错误4-权衡运行时处理异常的后果;

5- 然后提供一个可测试的框架,通常可以在单元测试期间处理,以确定哪些地方需要包含异常,哪些不需要。

6- 最后,根据您认为决定性因素并需要包含异常处理程序来处理它们的因素,决定在生产代码中何时需要包含异常处理程序。

7- 最后最后... 您需要拥有一个崩溃证明的异常处理程序,在应用程序崩溃的不太可能的情况下触发,并包括回退安全性以处理状态,使应用程序非常用户友好。


编译时错误与任何时间的异常无关,不仅仅是“一般情况下”如此。也不涉及断言或预处理器指令。 - user207421

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