捕获可抛出异常并处理特定异常

24

好的,我知道捕获可抛出异常并不是一个好主意:

    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
    }

这似乎并不那么糟糕?这种方法有什么问题吗?


4
有时候,在框架中你别无选择,只能捕获Throwable。但在应用程序级别的代码中,除非你想要一个“最后的手段处理程序”,否则这仍然不是一个好主意。 - biziclop
你有相关开源代码的链接吗(例如在GitHub上的源文件)?根据情境,我可以想到几种使用案例,这可能是一个不错的策略。 - James_pic
@James_pic 给你链接https://github.com/Eldelshell/jongo/blob/master/jongo-core/src/main/java/jongo/RestController.java - BrownRecluse
9个回答

21

有很多原因你不应该捕获 Throwable。首先,Throwable 包括 Error,如果出现其中一个,通常应用程序无法做太多事情。此外,Throwable 降低了你发现问题的几率。你只会得到“出了些问题”的提示,这可能是一场灾难或者只是个麻烦。

另一种方法更好,但当然我仍然不会捕获 Throwable,而是尽可能地捕获更具体的异常。否则,你将捕获所有异常,然后试图找出发生了哪种糟糕的事情。你的示例可以写成...

try {
    ...
} catch (SQLEXception ex){
    response = ... ;
} catch (IllegalArgumentException ex){
    response = ...;
}

…这将减少if(...instanceof...)块的数量(这些块只是因为作者首先决定在一个大桶中捕获所有内容而需要)。当然,如果某些东西实际上会throws Throwable,那么你没有太多选择。


2
问题在于有一些“错误”是非常容易恢复的。如果一个框架试图加载一个类并失败了,事情仍然可能会很好。 - biziclop
在这种情况下,框架不应该产生错误,而是应该抛出一个更有意义的异常。当然,并非总是如此。 - Florian Schaetz
3
不,我的意思是框架本身必须捕获错误并处理它。不幸的是,Java的作者没有区分可恢复和不可恢复的错误,因此有时(很少)捕获“Throwable”是唯一明智的选择。 - biziclop
2
啊,好的。是的,你说得对,但这些都是非常特定的情况,只适用于非常低级别的用例。这就是为什么我写道“通常”应用程序无法做太多事情。如果你是Spring框架并遇到错误,可能有一些事情可以做。如果你是一个HelloWorld应用程序,最好的选择就是在有人修复你的错误时休息一下;-) - Florian Schaetz
如果你是一个 Web 服务器,在处理请求时遇到错误,你希望记录下来并仅中止该请求,而不会使服务器崩溃。(除非每个请求都抛出错误) - user253751

19
当您说捕获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多个异常,这段代码也可能会占用很多行,并且多重捕获结构不会使代码更加清晰。你在问题中提出的代码只是将catch委派给另一个方法,以使实际执行工作的方法更易读。
2.可重用性 handleRequest方法的代码可以轻松修改并放置在实用类中,在整个应用程序中访问以处理异常和错误。你甚至可以将该方法提取为两个私有方法;一个处理异常,一个处理错误,并且将接受Throwable的handleException方法进一步委托调用这些方法。
3.可维护性
如果您决定更改应用程序中记录SQLException的方式,您需要在一个地方进行此更改,而不是访问抛出SQLException的每个类中的每个方法。
那么捕获Throwable是一个坏主意吗?
您在问题中提供的代码实际上并不完全等同于仅捕获Throwable。以下代码是一个大忌:
try {
   doSomething();
} catch(Throwable e) {
    //log, rethrow or take some action
}

你应该尽可能在catch链中尽早捕获ThrowableException
最后但并非最不重要的是,请记住,你在问题中展示的代码是框架代码,框架仍然可以从某些错误中恢复。请参阅何时捕获java.lang.Error以获得更好的解释。

为什么你要使用 Throwable 而不是 Exception?这个方法必须被称为 handleThrowable... - maraca
2
@maraca 请记住,这是框架代码。框架会想要捕获可恢复的错误(链接错误就是一个典型的例子)。话虽如此,我同意他们使用的方法名在这里并不是一个好选择,但这与实际问题无关。 - Chetan Kinger
谢谢,我明白了。但我认为这是一个设计问题,无法想出一种情况,这将是一个好的方法。他们可能应该只使用throws并在正确的位置处理,而不是捕获所有内容并调用此方法。 - maraca
2
@maraca 他们捕获的大多数异常都是RuntimeException的子类。 RuntimeException不需要遵循catch或指定要求,因此框架开发人员无法保证其API的用户将处理这些异常。他们可能会将这些异常包装成已检查的异常并抛出它们,或者他们可能会代表其处理这些异常。总之,在我看来,在这种情况下声明一个方法throws RuntimeException没有意义。 - Chetan Kinger

9

因懒惰而捕获Throwable异常是个坏主意。

try-multi-catch 之前,这种做法特别诱人。

try {
   ...
} catch (SomeException e) {
   //do something
} catch (OtherException e) {
   //do the same thing
} ...

重复编写catch块非常繁琐冗长,因此有些人决定只捕获ExceptionThrowable就结束。但这应该避免使用,因为:
  1. 它使得你尝试理解代码时变得困难。
  2. 你可能会捕捉到很多你无法处理的东西。
  3. 如果在catch块中完全吞噬Throwable,那么你应该受到额外的惩罚。(我们都见过这样的代码... :)

但是,在必要时捕获Throwable是可以的。

什么情况下是必要的呢?非常少见。在框架式代码中存在各种场景(动态加载外部类是最明显的场景),在独立应用程序中,一个典型的例子是在退出之前尝试显示/记录某种类型的错误消息。(请记住,尝试可能会失败,所以你不想在那里放置任何关键内容。)

作为经验法则,如果对于异常/错误你无能为力,就不应该捕捉它。


3

您发布了一个 Jongo 的链接,它展示了这种技术的一种可能用法:重复使用错误处理代码。

假设您有一大块错误处理代码,在代码中自然地多次重复出现 - 例如,Jongo 为某些标准错误类别生成标准响应。将该错误处理代码提取到方法中可能是一个好主意,这样您就可以从需要它的所有位置重复使用它。

但是,并不是说 Jongo 的代码没有问题。

捕获 Throwable(而不是使用 multicatch)仍然是可疑的,因为您可能会捕获您无法真正处理的 Errors(您确定要捕获 ThreadDeath 吗?)。在这种情况下,如果您绝对必须捕获 Throwable,最好是“捕获并释放”(即重新抛出您不想捕获的任何内容)。Jongo 没有这样做。


3

使用巨大的网络有两个确切的有效用途:

  • 如果您将统一处理所有内容,如用于日志记录/报告的顶级catch,可能紧随其后的是立即退出。

  • 通过将所有处理导出到自己的方法中来减少重复。
    捕获最派生的共同祖先以避免额外工作并增加清晰度。
    DRY 是一个重要的设计原则。

在这两种情况下,除非您预期该异常并完全处理它,否则重新抛出。


2
首先,捕获Throwable会使您的应用程序不够透明。在捕获异常时,应尽可能明确,以便在异常情况下实现良好的可追溯性。
让我们看一下handleException(...)方法,并了解此方法存在的一些问题:
- 您捕获Throwable但仅处理异常,如果抛出类型为错误(例如OutOfMemoryError),会发生什么? - 我认为会发生糟糕的事情... - 就良好的面向对象编程而言,使用instanceof违反了开闭原则,并且使代码更改(例如添加新异常)变得非常混乱。
在我看来,catch块正是为handleExceptions(...)中要覆盖的功能而设计的,因此请使用它们。

2
Java 7解决了一些乏味的问题,即多次捕获类似异常并进行类似处理。你绝对不应该像这个人所做的那样。只需按需要捕获适当的异常,虽然可能看起来很丑陋,但这就是throws的作用,将其传递给应该捕获它的方法,这样你就不会浪费太多代码空间。

查看此链接以获取更多信息。


2

为了提供平衡,仅有一个地方我总是会catch(Throwable)

public static void main(String args[]) {
    try {
        new Test().test();
    } catch (Throwable t) {
        t.printStackTrace(System.err);
    }
}

至少在某个地方显示了一些东西出错了。


这段话涉及到IT技术,意思是当系统出现错误时,会在某个地方提醒用户发生了错误。

真的吗?我从来没有这样做过...因为它总是被打印出来了。也许这是特定于操作系统的? - maaartinus

0

您可以始终捕获不同类型的异常,并根据所获取的异常类型执行一些操作。

这里是一个例子:

          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.
             }

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