捕获Throwable是一种不好的做法吗?

117

捕获 Throwable 是一种不好的做法吗?

例如像这样:

try {
    // Some code
} catch(Throwable e) {
    // handle the exception
}

这种做法是不好的吗,还是我们应该尽可能的具体明确?


5
相关问题:https://dev59.com/AXRB5IYBdhLWcg3wl4EP 和 https://dev59.com/5kvSa4cB1Zd3GeqPfpZF这些问题讨论了在Java中应该如何处理异常。在Java中,所有异常都是Throwable类或其子类的实例。对于可以预测的异常情况,建议使用异常处理技术来报告和处理异常情况,而不是简单地将异常向上抛出。但是,不要捕获并处理不应该被捕获的异常,例如VirtualMachineError(虚拟机错误)和ThreadDeath(线程死亡),因为它们表示致命错误,无法恢复,应该终止程序运行。 - finnw
14个回答

111

48
有些情况下,捕获错误并继续执行是合适的。例如,在Servlet中,如果遇到OutOfMemoryError,因为某个请求消耗了所有内存,您可以尝试继续执行,因为对象将在请求处理后进行垃圾回收。断言错误也是一样的。您不应该因为一个请求出错就关闭整个应用程序。 - gawi
7
在 OOME 发生之前,你如何知道已经分配了什么,未分配了什么?一旦出现 OOME,即使在像 Tomcat 或 JBoss 这样的 J2EE 容器内部,一切都不确定。 - bmauter
12
我们曾经遇到过NoSuchMethodError的问题,但幸运的是,在部署两周后发生此问题时,我们没有关闭服务器而使所有客户端下线。也就是说,我们总是设置了一个catchall,捕获Throwable并尽力处理并将错误发送给客户端。毕竟,有很多类型的错误是可以恢复的,可能只会影响1000个客户中的1个。 - Dean Hiller
12
如果你的程序突然崩溃,你如何获悉出了什么问题呢?捕获Throwable/Error并记录下来是一个合理的做法,而不是让程序立刻停止运行以便你更好地修复它。注意,此处的翻译保留了原文的意思,同时使语言更加通俗易懂。 - assylias
4
一个独立应用程序会将致命错误输出到 stderr。 - Philip Whitehouse
显示剩余7条评论

39

这是一个不好的想法。事实上,即使捕获Exception也通常是一个不好的想法。让我们考虑一个例子:

try {
    inputNumber = NumberFormat.getInstance().formatNumber( getUserInput() );
} catch(Throwable e) {
    inputNumber = 10; //Default, user did not enter valid number
}

假设现在getUserInput()会阻塞一段时间,并且另一个线程以最坏的方式停止了你的线程(调用了thread.stop())。你的catch块将捕获ThreadDeath错误。这非常糟糕。在捕获该异常后,您的代码行为很大程度上是未定义的。

捕获Exception也存在类似的问题。也许getUserInput()由于InterruptException失败,或者在尝试记录结果时因权限被拒绝而失败,或者出现各种其他故障。由于这个原因,你不知道出了什么问题,也不知道如何解决问题。

你有三个更好的选择:

1 -- 只捕获你知道如何处理的异常:

try {
    inputNumber = NumberFormat.getInstance().formatNumber( getUserInput() );
} catch(ParseException e) {
    inputNumber = 10; //Default, user did not enter valid number
}

2 -- 抛出你遇到但不知道如何处理的任何异常:

try {
    doSomethingMysterious();
} catch(Exception e) {
    log.error("Oh man, something bad and mysterious happened",e);
    throw e;
}

3 -- 使用 finally 块,这样您就不必记住重新抛出异常:

 Resources r = null;
 try {
      r = allocateSomeResources();
      doSomething(r);
 } finally {
     if(r!=null) cleanUpResources(r);
 }

4
即使捕获异常也不好。至少有一个ThreadInterruptedException需要特别注意(简而言之,在捕获它后,您必须将线程的中断状态设置回“true”)。 - Kirill Gamazkov
我知道这只是为了阐述你的话,但我认为您可以使用正则表达式检查用户输入是否为字母数字或其他所需格式,并且不要在任何时候都使用try catch。 - amdev
如果我不捕获错误/可抛出异常,我怎么知道有没有这样的问题?日志中也没有看到任何信息。这是一个Java EE应用程序。在添加了这个catch之前,我花了几天时间陷入困境,不知道问题出在哪里。 - Philip Rego
3
我认为选项#2是不好的实践。想象一下有10个链接调用,带有日志和重新抛出。如果你查看日志文件,你不会感到高兴。异常被记录了10次,使得日志非常难以阅读。在我看来,更好的做法是使用throw new Exception("Some additional info, eg. userId " + userId, e);。这将在一个漂亮的异常中记录10个原因。 - Petr Újezdský

22

请注意,当你捕获Throwable时,也可以同时捕获需要特殊处理的InterruptedException。有关更多详细信息,请参见处理InterruptedException

如果您只想捕获未经检查的异常,您也可以考虑此模式。

try {
   ...
} catch (RuntimeException exception) {
  //do something
} catch (Error error) {
  //do something
}

这样,当您修改代码并添加可能引发已检查异常的方法调用时,编译器会提醒您,并且您可以决定如何处理此情况。


16

直接从 Error 类的 Javadoc 中摘取(建议不要捕获这些异常):

 * An <code>Error</code> is a subclass of <code>Throwable</code> 
 * that indicates serious problems that a reasonable application 
 * should not try to catch. Most such errors are abnormal conditions. 
 * The <code>ThreadDeath</code> error, though a "normal" condition,
 * is also a subclass of <code>Error</code> because most applications
 * should not try to catch it. 

 * A method is not required to declare in its <code>throws</code> 
 * clause any subclasses of <code>Error</code> that might be thrown 
 * during the execution of the method but not caught, since these 
 * errors are abnormal conditions that should never occur. 
 *
 * @author  Frank Yellin
 * @version %I%, %G%
 * @see     java.lang.ThreadDeath
 * @since   JDK1.0

13

如果你绝对不能让一个方法抛出异常,那么这并不是一种不好的实践。

但如果你真的无法处理异常,那么这就是一种不好的实践。最好在方法签名中添加“throws”,而不是仅仅捕获并重新抛出异常,或者更糟糕的是将其包装在RuntimeException中并重新抛出。


10
完全同意 - 处理所有的 Throwable 实例确实有绝对合法的情况 - 例如用于自定义异常日志记录。 - Yuriy Nakonechnyy

13

如果你使用的库过度热衷于抛出Errors,有时就需要捕获Throwable,否则你的库可能会导致应用程序崩溃。

然而,在这种情况下,最好只指定库抛出的具体错误,而不是所有的Throwables。


13
还是使用更好的库? - Raedwald
9
确实,如果你有选择的话;-) - DNA
这就是捕获可抛出异常并重新抛出它们的最大问题。这真的为堆栈中所有上游方法创建了一个不可能的接口。它们要么必须处理可抛出异常,要么具有无法使用的throws throwable签名,其他人将不得不处理它们。 - Andrew Norman

7
这个问题有点模糊,你是在问“捕获 Throwable 是否可以”,还是在问“捕获 Throwable 后没有做任何事情是否可以”?这里有很多人回答了后者,但那只是一个次要问题;99% 的情况下,无论你是捕获 Throwable 还是 IOException 或其他异常,都不应该 “消费” 或丢弃异常。
如果你传播异常,答案(就像许多问题的答案一样)是 “取决于” 你捕获异常的原因。下面是为什么你想捕获 Throwable 的一个好例子:当出现任何错误时,提供一些清理。例如在 JDBC 中,如果在事务期间发生错误,则需要回滚事务。
try {
  …
} catch(final Throwable throwable) {
  connection.rollback();
  throw throwable;
}

请注意,异常并没有被丢弃,而是被传递了下来。
然而,一般来说,由于没有特定的原因并且懒得查看抛出的具体异常,捕获 Throwable 是不好的形式,是个坏主意。

6

Throwable是所有可被抛出的类(不仅仅是异常)的基础类。如果你捕获了OutOfMemoryError或KernelError(请参见何时捕获java.lang.Error?),那么你能做的很少。

捕获异常应该足够了。


5
这取决于您的逻辑或更具体地说,取决于您的选项/可能性。如果有任何特定的异常情况可以以有意义的方式进行反应,您可以首先捕获它并这样做。
如果没有,并且您确定对所有异常和错误都会执行相同的操作(例如以错误消息退出),那么捕获throwable就不是问题。
通常第一种情况适用,您不需要捕获throwable。但仍然有很多情况下捕获它也能正常工作。

4

虽然这被描述为一种非常不好的做法,但在某些罕见的情况下,它不仅有用而且是必需的。以下是两个例子。

在必须向用户显示有意义的错误页面的Web应用程序中。此代码确保发生这种情况,因为它在所有请求处理程序(servlet、struts操作或任何控制器...)周围有一个大的try/catch块。

try{
     //run the code which handles user request.
   }catch(Throwable ex){
   LOG.error("Exception was thrown: {}", ex);
     //redirect request to a error page. 
 }

以另一个例子为例,考虑您拥有一个服务类,用于处理资金转移业务。如果转移完成,则此方法返回TransferReceipt;如果未能完成,则返回NULL

String FoundtransferService.doTransfer( fundtransferVO);

现在想象一下,你得到了一个用户资金转账的List,而你必须使用上述服务来完成它们。
for(FundTransferVO fundTransferVO : fundTransferVOList){
   FoundtransferService.doTransfer( foundtransferVO);
}

但是,如果发生任何异常会怎么样呢?你不应该停止,因为可能有一个转账成功了,而另一个没有,你应该继续遍历所有用户List,并将结果显示给每个转账。因此,您最终得到以下代码。

for(FundTransferVO fundTransferVO : fundTransferVOList){
    FoundtransferService.doTransfer( foundtransferVO);
 }catch(Throwable ex){
    LOG.error("The transfer for {} failed due the error {}", foundtransferVO, ex);
  }
}

您可以浏览许多开源项目,看到throwable确实被缓存和处理。例如,这里是对tomcatstruts2primefaces的搜索结果链接:

https://github.com/apache/tomcat/search?utf8=%E2%9C%93&q=catch%28Throwable https://github.com/apache/struts/search?utf8=%E2%9C%93&q=catch%28Throwable https://github.com/primefaces/primefaces/search?utf8=%E2%9C%93&q=catch%28Throwable


1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Abc.Xyz
@developer101 当然,但它们会捕获 throwable,这也是这个问题的关键。 - Alireza Fattahi

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