Is it a bad practice to catch Throwable?

Is it a bad practice to catch Throwable?

For example something like this:

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

Is this a bad practice or we should be as specific as possible?

99932 次浏览

您需要尽可能具体,否则不可预见的错误可能会以这种方式悄悄溜走。

Besides, Throwable covers Error as well and that's usually no point of return. You don't want to catch/handle that, you want your program to die immediately so that you can fix it properly.

这取决于你的逻辑,或者更具体地说明你的选择/可能性。如果有任何特定的异常,您可能以一种有意义的方式作出反应,您可以首先抓住它并这样做。

如果没有,并且您确信将对所有异常和错误执行相同的操作(例如,使用错误消息退出) ,那么捕获可抛出对象就不成问题。

通常情况下,第一种情况下,你不会赶上扔。但是仍然有很多情况下,抓住它是有效的。

如果绝对不能从方法中产生异常气泡,那么这种做法并不坏。

如果您真的不能处理异常,那么这是一个糟糕的实践。最好向方法签名中添加“ throw”,而不仅仅是 catch 和 re-throw,或者更糟糕的是,将其包装在 RuntimeException 中并 re-throw。

这不是个好主意。事实上,即使抓住 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()失败是因为中断异常,或者在尝试记录结果时被拒绝的权限异常,或者其他各种失败。你不知道哪里出错了,正因为如此,你也不知道如何解决这个问题。

你有三个更好的选择:

1 -- Catch exactly the Exception(s) you know how to handle:

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);
}

Catching Throwable is sometimes necessary if you are using libraries that throw Errors over-enthusiastically, otherwise your library may kill your application.

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

Throwable 是所有可以抛出的类的基类(不仅仅是异常)。如果您捕捉到 OutOfMemory 错误或 KernelError (请参阅 何时捕获 java.lang。错误?) ,那么您几乎无能为力

捕捉异常就足够了。

还要注意,当你捕捉到 Throwable时,你也可以捕捉到需要特殊处理的 InterruptedException。有关详细信息,请参阅 Dealing with InterruptedException

If you only want to catch unchecked exceptions, you might also consider this pattern

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

这样,当您修改代码并添加一个可以引发检查异常的方法调用时,编译器将提醒您这一点,然后您就可以决定该如何处理此情况。

直接来自 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

尽管它被描述为一种非常糟糕的做法,但有时您可能会发现 稀有的案例不仅有用,而且是强制性的。这里有两个例子。

在 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

如果我们使用 可以扔,那么它也覆盖 错误,就是这样。

例子。

    public class ExceptionTest {
/**
* @param args
*/
public static void m1() {
int i = 10;
int j = 0;
try {
int k = i / j;
System.out.println(k);
} catch (Throwable th) {
th.printStackTrace();
}
}


public static void main(String[] args) {
m1();
}

}

输出:

java.lang.ArithmeticException: / by zero
at com.infy.test.ExceptionTest.m1(ExceptionTest.java:12)
at com.infy.test.ExceptionTest.main(ExceptionTest.java:25)

Throwable 是所有错误和异常的超类。 如果在 catch 子句中使用 Throwable,它不仅会捕获所有异常,还会捕获所有错误。错误由 JVM 抛出,以指示应用程序无意处理的严重问题。这方面的典型例子是 OutOfMemory 错误或 StackOverflow 错误。这两种情况都是由应用程序控制范围之外的无法处理的情况造成的。所以你不应该捕捉 Throwables,除非你非常自信它只是 Throwable 内部的一个异常。

这个问题有点模糊; 你是在问“捕获 Throwable是否可以”,还是“捕获 Throwable却什么都不做是否可以”?这里的许多人回答了后者,但这是一个次要问题; 99% 的情况下,您不应该“消耗”或丢弃异常,无论您是捕获 Throwable还是 IOException或其他什么。

如果传播异常,那么答案(就像许多问题的答案一样)是“视情况而定”。这取决于你在做什么ーー为什么要捕捉异常。

为什么要捕获 Throwable的一个很好的例子是,如果有任何错误,就提供某种类型的清除。例如,在 JDBC 中,如果在事务期间发生错误,您将希望回滚事务:

try {
…
} catch(final Throwable throwable) {
connection.rollback();
throw throwable;
}

请注意,异常不会被丢弃,而是被传播。

但是作为一般策略,捕获 Throwable是因为您没有理由,并且懒得去查看抛出了哪些特定的异常,这是一个糟糕的形式和糟糕的主意。

一般来说,你想要避免接触到 Error,但是我可以想到(至少)两个适合这样做的特殊情况:

  • 您需要关闭应用程序以响应错误,特别是 AssertionError,否则它是无害的。
  • 您是否实现了类似于 提交()的线程池机制,该机制要求您将异常转发回用户,以便他们能够处理异常。

一个更有区别的答案是: 视情况而定。

Exception 和 Error 的区别在于,Exception 是必须预期的状态,而 Error 是意外状态,通常是致命的。错误通常无法从程序中恢复,需要重新设置程序的主要部分甚至整个 JVM。

为了处理可能发生的状态,总是应该捕获异常,这就是 JVM 强制执行异常的原因。例如,打开文件可能导致 FileNotFoundException,调用 Web 资源可能导致 TimeoutException,等等。您的代码需要准备好处理这些通常会发生的情况。如何处理这些问题取决于你自己,没有必要从所有问题中恢复,但是你的应用程序不应该仅仅因为 Web 服务器需要更长的时间才能恢复,就重新启动到桌面。

只有在真正有必要的时候,你才应该去捕捉错误。一般情况下,除非有充分的理由,否则不能从错误中恢复,也不应该尝试这样做。捕获错误的原因是关闭关键资源,否则这些资源将被打开,或者如果您有一个运行插件的服务器,然后可以停止或重新启动导致错误的插件。其他原因是记录可能有助于以后调试该错误的附加信息,在这种情况下,当然应该重新抛出该错误,以确保应用程序正确终止。

经验法则: 除非你有重要的理由去捕捉错误,否则不要这样做。

因此,只有在这种真正重要的情况下才使用 catch (Throwable t),否则就坚持使用 catch (Exception e)