19

RuntimeException在 Java 中,观察到在处理 a 之后重新抛出 a 的约定Checked Exception

这种方式有好有坏。当编译器强制通过 a 处理某些内容时Checked Exception,开发人员可以通过捕获它并将其重新作为RuntimeException.

有人可以解释这种情况是否可以被视为一种好的做法吗?如果是这样,这种方法会不会更不容易出错,还是会使代码库变得不稳定?

4

5 回答 5

28

实际上,处理检查异常的无能尝试导致了不稳定的代码库。通常,您将拥有以下内容:

try {
   //stuff
} catch (IOException e) {
   log.error("Failed to do stuff", e);
   throw e;
}

然后再上一层,您将不得不再次处理它,通常将其全部记录下来并弄乱日志文件。如果你不重新抛出它会更糟:

try {
   // do stuff
} catch (IOException e) {
  return null;
}

现在调用代码不知道出了什么问题,更不用说什么了。与那些尝试相比,这实际上完全完成了应用程序逻辑所需的内容:

try {
  // do stuff
} catch (IOException e) {
  throw new RuntimeException(e);
}

现在异常可以自由地向上传播调用堆栈,直到它到达定义明确的异常屏障,它:

  1. 中止当前的工作单元;
  2. 被记录在一个统一的地方。

简而言之,要决定是抓住并处理还是抓住并重新抛出,只需问自己这个问题:

这个异常的发生必须中止当前的工作单元吗?

  • 如果:重新抛出未经检查的异常;
  • 如果:在 catch-block 中提供有意义的恢复代码。(不,日志记录不是恢复)。

根据多年的实际经验,我可以告诉你,超过 90% 的所有可能的已检查异常属于“中止”类型,不需要在发生的地方进行处理。

反对检查异常的语言特性

今天,检查异常被广泛认为是语言设计中失败的实验,简而言之,这里是关键论点:

API 创建者不能决定客户端代码中异常的语义。

Java的推理是异常可以分为

  1. 编程错误导致的异常(未选中);
  2. 由于程序员无法控制的情况导致的异常(已检查)。

虽然这种划分在某种程度上可能是真实的,但只能从客户端代码的角度来定义。更重要的是,它在实践中并不是一个非常相关的划分:真正重要的是必须在什么时候处理异常。如果要延迟处理,则在异常屏障处,检查异常不会获得任何收益。如果及早处理,那么只有有时会从检查的异常中获得轻微的收益。

实践已经证实,检查异常所带来的任何收益与对现实项目造成的现实损害相比相形见绌,正如每个 Java 专业人士所见证的那样。Eclipse 和其他 IDE 也是罪魁祸首,建议没有经验的开发人员将代码包装在 try-catch 中,然后想知道在 catch-block 中写什么。

每当你遇到一个方法时throws Exception,你就会发现另一个活生生的证据证明检查异常的不足。

于 2013-05-28T14:15:25.387 回答
3

检查异常的想法是“仅限Java” - 据我所知,Java 采用这种想法之后没有任何语言。

有太多已检查的异常被捕获......并被默默地忽略。

如果你看一下 Scala,他们也放弃了它——它只是为了与 Java 兼容。

在Oracle 网站上的本教程中,您将找到以下定义:

如果可以合理地期望客户端从异常中恢复,则使其成为受检异常。
如果客户端无法从异常中恢复,请将其设为未经检查的异常。

Scala 也采用了这个概念,并且效果很好。

从技术上讲,您的建议有效。无论哪种方式都需要纪律和代码审查。

于 2013-05-28T14:37:20.937 回答
1

在这种情况下,术语“可以摆脱它”并不完全正确。这是摆脱异常:

 try {

 } catch (Exception e){
     e.printStacktrace();
 } 

这是使用中最常见的不良做法try-catch。您正在捕获异常,然后仅打印它。在这种情况下,catch 块捕获异常并打印它,而程序在 catch 块之后继续,就好像什么都没发生一样

当您决定捕获块而不是抛出异常时,您必须能够管理异常。有时异常是不可管理的,它们必须被抛出

这是你应该记住的:

如果客户端可以采取一些替代操作来从异常中恢复,请将其设为已检查异常。如果客户端不能做任何有用的事情,那么就不要检查异常。有用,我的意思是采取措施从异常中恢复,而不仅仅是记录异常。

如果您不打算做一些有用的事情,那么不要捕获异常。将其作为 RuntimeException 重新抛出是有原因的:如前所述,程序无法继续,因为什么也没发生。这样,一个好的做法是:

try {

} catch (Exception e){
    //try to do something useful  
    throw new RuntimeException(e); 
}

这意味着:您刚刚捕获了一个异常(如SQLException),如果不停止和重置线程就无法从中恢复。你抓住它,你尝试在两者之间做点什么(比如重置一些东西,关闭打开的套接字等等),然后你抛出一个RuntimeException().

RuntimeException 将暂停整个线程,避免程序继续执行,就好像什么都没发生一样。此外,您无需打印即可管理其他异常。

于 2013-05-28T14:28:27.213 回答
0

The general rule is: you throw a checked exception when the caller might be able to do some kind of recovery when informed about it. Otherwise, throw an unchecked exception.

This rule applies when an exception is first thrown.

But this also applies when you catch an exception and are wondering whether to throw a checked or unchecked exception. So there is no convention to throw a RunTimeException after catching a checked one. It is decided in a case-by-case basis.

One small tip: if you are going to just re-throw an checked exception after catching one and do nothing else, most of the time it is alright to just not catch the exception and add it to the exceptions thrown by the method.

于 2013-05-28T14:41:00.140 回答
0

这可能好也可能不好,这取决于上下文,但可能不是。

根据经验,RuntimeExceptions 应该只用于指示编程错误(示例包括 IllegalArgumentException 和 IllegalStateException)。它们不必被检查异常,因为您通常假设您的程序是正确的,除非另有证明,并且您无法以有意义的方式处理这些异常(您必须发布程序的更新版本)。

运行时异常的另一个有效用途是当您使用一个将为您捕获和处理异常的框架时。在这种情况下,当您无论如何都不打算处理它时,必须在每个方法中声明异常只会很麻烦。

所以一般来说,我会说重新抛出已检查的异常作为运行时异常是非常糟糕的做法,除非你有一个可以正确处理它的框架。

于 2013-05-28T14:40:05.110 回答