好吧,我知道捕捉throwable不是一个好主意:
try {
// Some code
} catch(Throwable e) { // Not cool!
// handle the exception
}
但是最近我正在阅读一个开源代码,并且看到了这段有趣的代码(至少对我来说):
try {
// Some Code
} catch (Throwable ex){
response = handleException(ex, resource);
}
private handleException(Throwable t, String resource) {
if (t instanceof SQLEXception) {
// Some code
} else if (t instanceof IllegalArgumentException) {
//some code
} //so on and so forth
}
这似乎没有那么糟糕吗? 这种方法有什么问题?
有时,尤其是在框架中,除了捕获Throwable之外,您别无选择。 在应用程序级代码中,这仍然不是一个好主意,除非您希望拥有某种"不得已的处理程序"。
您是否有指向相关开源代码的链接(例如GitHub上的源文件)? 我可以想到几个用例,根据实际情况,这可能是一个不错的策略。
@James_pic你去了github.com/Eldelshell/jongo/blob/master/jongo-core/src/main/
有多种原因导致您不应该使用Throwable。首先,Throwable包括Error s-如果其中之一出现,通常应用程序无能为力。而且Throwable减少了您发现已发生事件的机会。您得到的只是"一件坏事已经发生了"-这可能是灾难,也可能是令人讨厌的事情。
另一个方法更好,但是我当然不会捕获Throwable,但是如果可能的话,尝试捕获更具体的Exception。否则,您会抓住一切,然后尝试找出发生哪种不好的事情。您的示例可以写成...
try {
...
} catch (SQLEXception ex){
response = ... ;
} catch (IllegalArgumentException ex){
response = ...;
}
...这将减少if ( ... instanceof ... )块的数量(之所以需要,是因为作者首先决定将所有东西都放在一个大桶中)。实际上是throws Throwable的东西,那么您当然没有太多选择。
问题是有些Error很容易恢复。如果一个框架试图加载一个类而失败了,那仍然可以。
在这种情况下,框架不应产生错误,而应产生更有意义的异常。当然,事实并非总是如此。
不,我的意思是框架本身必须捕获错误并加以处理。不幸的是,Java的作者没有区分可恢复错误和不可恢复错误,因此有时(非常罕见)捕获Throwable是唯一明智的选择。
喔好吧。是的,您就在那里,但是对于非常低级的用例,这是非常非常特殊的情况。这就是为什么我写道"通常"应用程序不能做很多事情的原因。如果您是Spring框架并且遇到错误,则可能可以做些事情。如果您是HelloWorld应用程序,则最好的选择是在有人修复您的错误时放假;-)
@FlorianSchaetz如果您是Web服务器,并且在处理请求时遇到错误,则只想记录该请求并中止该请求,而无需关闭服务器。 (除非每个请求都引发错误)
说捕获Throwable不是一个好主意,这是对的。但是,您在问题中出现的代码并不是以一种邪恶的方式捕获Throwable,而是让我们稍后再讨论。现在,您在问题中呈现的代码具有几个优点:
1.可读性
如果仔细查看代码,您会注意到,即使catch块正在捕获Throwable,handleException方法也会检查抛出的异常的类型,并可能根据异常类型采取不同的操作。
您的问题中提供的代码与说:
try {
doSomething();
} catch (SQLEXception ex){
response = handleException(resource);
} catch(IllegalArgumentException ex) {
response = handleException(resource);
} catch(Throwable ex) {
response = handleException(resource);
}
即使您只需要捕获10个以上的异常,该代码也很容易占用很多代码行,并且multi-catch构造不会使代码更清晰。您在问题中出现的代码只是将catch委派给另一个方法,以使执行工作的实际方法更具可读性。
2.可重用性
handleRequest方法的代码可以轻松地修改并放置在实用程序类中,并在整个应用程序中进行访问以处理Exception和Error。您甚至可以将方法提取到两个private方法中。一个处理Exception的对象和一个处理Error并具有handleException方法的对象,该方法采用Throwable进一步将调用委托给这些方法。
3.可维护性
如果您决定要更改在应用程序中记录SQLException的方式,则必须在单个位置进行此更改,而不是访问每个抛出SQLException的类中的每个方法。
那么赶上Throwable是个坏主意吗?
您在问题中出现的代码与仅捕获Throwable并不完全相同。以下代码很重要:
try {
doSomething();
} catch(Throwable e) {
//log, rethrow or take some action
}
您应该在catch链中尽可能远地捕获Throwable或Exception。
最后但并非最不重要的一点,请记住,您在问题中出现的代码是框架的代码,并且框架仍然可以从中恢复某些错误。请参阅何时捕获java.lang.Error以获得更好的解释。
为什么要使用Throwable而不是仅仅使用Exception?该方法必须称为handleThrowable ...
@maraca请记住,这是框架代码。 Fameworks希望捕获可恢复的错误。 (链接错误是一个很好的例子)。话虽如此,我同意他们在这里使用的方法名称并不是一个很好的选择,但这与实际问题无关。
谢谢,我明白了。但是我认为这是一个设计问题,无法提出一种可能是一种不错的方法的方案。与其捕获所有内容并调用此方法,不如直接在正确的位置使用throws和handle。
@maraca他们捕获的大多数异常都是RuntimeException的子类。 RuntimeException不需要遵循catch或指定要求,因此框架专家无法保证其API的用户将处理这些异常。他们可能将这些异常包装到已检查的异常中并抛出它们,或者他们可能代表他们处理这些异常。底线,在这种情况下为IMO,没有必要声明方法throws RuntimeException。
懒惰地捕获Throwable是个坏主意。
在引入try-multi-catch之前,这尤其诱人。
try {
...
} catch (SomeException e) {
//do something
} catch (OtherException e) {
//do the same thing
} ...
重复捕获块是乏味且冗长的,因此有些人决定只捕获Exception或Throwable并加以处理。应当避免这种情况,因为:
这使您很难按照自己的意愿去做。
您可能最终会遇到很多无法处理的东西。
如果您完全吞下catch块中的Throwable,则应受到加分处罚。 (而且我们都已经看到了执行此操作的代码... :))
但是在绝对必要时捕获Throwable是可以的。
什么时候有必要?非常稀有。在框架样式的代码中,有多种情况(动态加载外部类是最明显的一种情况),在独立应用程序中,一个典型的示例是尝试在退出之前显示/记录某种错误消息。 (请记住,该尝试可能会失败,因此您不想在此放置任何关键内容。)
根据经验,如果您对异常/错误无能为力,则根本不应该抓住它。
使用庞大的网络确实有两种有效用法:
如果您将统一处理所有事情,例如用于记录/报告的顶级捕获,则可能紧随其后退出。
为了减少重复,可以将所有处理导出到自己的方法中。
抓住那里最衍生的共同祖先,以避免额外的工作并增加清晰度。
DRY是重要的设计原则。
在这两种情况下,除非您期望该异常并完全处理它,否则都将其重新抛出。
您发布了指向Jongo的链接,该链接演示了此技术的一种可能用法:重用错误处理代码。
假设您有一大堆错误处理代码,这些代码自然会在代码中的各个位置重复其自身,例如Jongo对某些标准错误类别产生标准响应。将错误处理代码提取到方法中可能是一个好主意,因此您可以在所有需要的地方重复使用它。
但是,这并不是说Jongo的代码没有错。
捕获Throwable(而不是使用multicatch)仍然令人怀疑,因为您可能会捕获到您实际上没有能力处理的Error(您确定要捕获ThreadDeath吗?)。在这种情况下,如果您绝对必须捕获Throwable,最好"捕获并释放"(即,扔掉所有您不想捕获的东西)。 Jongo不会这样做。
只是为了提供平衡-在一个地方我总是catch (Throwable):
public static void main(String args[]) {
try {
new Test().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
至少某些地方显示出某些地方出了问题。
真???我从不做...因为它总是会被打印出来。也许是特定于操作系统的?
Java 7解决了一些烦人的问题,即以相似的处理方式多次捕获相似的异常。您绝对不应该做这个人在这里所做的事情。只要捕获所需的适当异常,它看起来就很丑陋,但这就是throws的用途,将其传递给应该捕获它的方法,并且您不应该浪费太多的代码空间。
查看此链接以获取更多信息。
首先,捕获Throwable使您的应用程序变得不透明。您应该在捕获异常方面尽可能地明确,以在特殊情况下实现良好的可追溯性。
让我们看一下handleException(...)方法,看看这种方法会发生的一些问题:
您捕获了Throwable但仅处理异常,如果发生异常,例如错误类型为OutOfMemoryError抛出? -我看到坏事发生了...
关于使用instanceof进行良好的面向对象编程,会破坏Open-Closed-Principle,并使代码更改(例如,添加新的异常)变得非常混乱。
从我的角度来看,catch块是为在handleExceptions(...)中试图涵盖的功能而专门制造的,因此请使用它们。
您始终可以捕获不同类型的异常,并根据所获取的异常类型执行某些操作。
这是一个例子
try{
//do something that could throw an exception
}catch (ConnectException e) {
//do something related to connection
} catch (InvalidAttributeValueException e) {
// do anything related to invalid attribute exception
} catch (NullPointerException e) {
// do something if a null if obtained
}
catch (Exception e) {
// any other exception that is not handled can be catch here, handle it here
}
finally{
//perform the final operatin like closing the connections etc.
}