使用嵌套异常是一种好习惯吗?

时间:2012-12-22 08:54:22

标签: java exception exception-handling

这可能是一个广泛的问题,不是那种风格,但如果可能的话,我仍然希望得到一些提示或指导。

我一直在查看一些遗留代码,并发现其中有一部分具有嵌套3或4级异常的方法。
这被认为是正常的做法还是应该尽可能避免这种代码风格?如果应该避免,除了异常处理成本增加和可读性降低之外还有哪些负面影响?是否有通用的方法来重构代码以避免这种情况?

8 个答案:

答案 0 :(得分:16)

我个人更喜欢以下意识形态

包裹异形异常

“外来”异常是Java API或第三方库抛出的异常。换句话说,您无法控制的异常。

最好捕获所有异常异常并将它们包装在适当的应用程序特定异常中。一旦外来异常转换为您自己的异常,您可以按照自己喜欢的方式传播该异常。

重新检查异常可能会出现凌乱

如果您的应用程序使用已检查的异常,则重新抛出原始异常意味着重新抛出它的方法也必须声明它。

越接近调用层次结构的顶部,将声明抛出的异常越多。除非你只是声明所有方法都抛出异常。但是,如果你这样做,你也可以使用未经检查的异常,因为无论如何你都没有从编译器异常检查中获得任何好处。

这就是为什么我喜欢捕获非特定于应用程序的异常并将它们包装在特定于应用程序的异常中,然后再将它们传播到调用堆栈中。

包装指南:发生异常的上下文可能与异常本身的位置一样重要。应用程序中的给定位置可以通过不同的执行路径访问,如果发生错误,执行路径可能会影响错误的严重性和原因。

如果需要在向调用堆栈传播异常时向异常添加上下文信息,则需要使用活动传播。换句话说,您需要在调用堆栈的路上在各个相关位置捕获异常,并在重新抛出或包装之前将相关的上下文信息添加到它。

public void doSomething() throws SomeException{

    try{

        doSomethingThatCanThrowException();

    } catch (SomeException e){

       e.addContextInformation(“more info”);
       throw e;  //throw e, or wrap it – see next line.

       //throw new WrappingException(e, “more information”);

    } finally {
       //clean up – close open resources etc.
    }

}

答案 1 :(得分:3)

异常处理往往是处理流控制的一种昂贵的方法(当然对于C#和Java)。

构造异常对象时,运行时会做很多工作 - 将堆栈跟踪放在一起,找出处理异常的位置等等。

如果将流量控制语句用于流量控制,则所有这些内存和CPU资源都不需要扩展。

此外,还存在语义问题。例外情况适用于特殊情况,而不适用于正常的流量控制。应该使用异常处理来处理意外/异常情况,而不是正常的程序流程,否则,未被捕获的异常会告诉您更少。

除了这两个,还有其他人阅读代码的问题。以这种方式使用异常并不是大多数程序员所期望的,因此可读性以及代码的可理解程度如何。当人们看到“异常”时,人们会认为 - 发生了一些不好的事情,这种事情本来就不应该发生。因此,以这种方式使用异常只是简单的混淆。

请查看以下链接

Exception Handling: Common Problems and Best Practice with Java 1.4 - pdf

Why not use exceptions as regular flow of control?

Best Practices for Exception Handling

Error Handling

Mr. Google Links

答案 2 :(得分:3)

我一直在查看一些遗留代码,并发现其中有一部分具有嵌套3或4级异常的方法。

这被认为是正常做法,还是应该尽可能避免这种代码风格?

这不是以这种方式处理异常的必要过程,因为它会增加您的应用程序开销,直到您真正需要处理非常特定的异常(已检查或异常异常)并且您可以忽略开销以获取特定信息处理那个例外。

如果应该避免,除了异常处理成本增加和可读性降低之外还有哪些负面影响?

正如我所提到的,如果您不打算使用嵌套异常处理(向上层处理程序添加一些附加信息的抛出),您将无法获得有关异常的具体信息,您可能/不会代表某些人执行特定操作严重的异常,但在嵌套的情况下,您可以通过处理特定情况来采取行动。

是否有通用的方法来重构代码以避免这种情况?

如果你有一个计算欠佳的程序可以做你想要的并且没有严重的错误,那么为了上帝的缘故,请不要管它!当您需要修复错误或添加功能时,您将无情地重构您在工作中遇到的代码。覆盖自定义异常处理程序中的异常类,并添加一些添加的功能来处理您的问题。

重写方法不得抛出新的或更广泛的已检查异常,而不是被重写方法声明的异常。例如,声明FileNotFoundException的方法不能被声明SQLException,Exception或任何其他非运行时异常的方法覆盖,除非它是FileNotFoundException的子类。

跳这会对你有帮助。

答案 3 :(得分:2)

Checked Exceptions不应该在堆栈中传播或链接(如果可能)。如果一个方法抛出一个已检查的Exception,它的调用者应该处理它,如果调用者没有处理它并将它传播给它的调用者,那么整体复杂性就会增加。

在三层示例中:Dao,Service,Controller

DAO层将抛出DAOException 服务层不应该将DAOException暴露给Controller,而应该抛出相关的BuinessExceptions,Controller应该处理它。

答案 4 :(得分:0)

您应该取消异常嵌套。您应该首先避免链接异常,或者(有选择地)解包然后重新抛出堆栈中的嵌套异常。

答案 5 :(得分:0)

关于处理遗留代码,我建议您查看有关该主题的书: http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 你甚至不必浏览整本书,只看看你现在关心的事情。

关于良好做法的好书也是: http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_1?s=books&ie=UTF8&qid=1356340809&sr=1-1&keywords=clean+code

处理嵌套异常时的最佳方法是重构代码并使用运行时而不是已检查的异常,并在需要时处理这些异常。这样,代码更易读,更易于维护。

答案 6 :(得分:0)

它取决于业务逻辑。你可以对那里的异常采取行动,或者你可以将它一直传播给调用者,并将其留给调用者以进行他想要的操作。

e.g。有许多第三方API,他们不处理异常,但他们从方法中抛出它,因此便于API用户根据他们的需要采取行动。

如参见。 oracle JDBC驱动程序。 Driver.getConnection()抛出异常。现在,调用者/ API用户可以根据需要处理它。可以只打印堆栈跟踪,可以通知管理员询问他的注意力,或者可以选择只是默默地退出应用程序。

答案 7 :(得分:0)

有两种方法:

To generate a separate exception for each event.
To create a generic exception and describe what caused it 

第一种方法允许你编写不同的代码来处理不同的事件,但它需要你编写很多Exception类,在某些情况下它可能太多了。

第二种方法更简洁,但却难以处理不同的情况。

正如在编程中经常发生的那样,最好的解决方案是在中间,在那里平衡生成单独的异常并对其他情况使用一个例外。

在这种情况下,拇指的规则可能是为要使用单独代码专门处理的异常生成一个单独的Exception类。

与投掷的内容类似,我们也应该控制要捕获的内容。我们可以为catch块使用两种方法:

所有人的单一捕获区块。例如:

catch (Throwable e) {
throw new CommandExecutorException(e);
}

每个例外的许多捕获块一个。例如:

} catch (ClassCastException e1) {
 ...
} catch (FileNotFoundException e) {
... 
} catch (IOException e) {
...
}

第一种方法非常紧凑,但基本上在同一情况下对每个异常进行分组,它仅在所有异常被平等管理并执行相同操作的情况下才有用。 这种方法通常是不鼓励的,因为它无法控制被捕获的异常,有时会导致难以发现的严重错误。

第二种方法需要更多代码行,但您可以根据发生的异常执行不同的操作。这种方法更灵活,但在某些情况下由于异常处理会导致您的方法很长。