这可能是一个广泛的问题,不是那么风格,但如果可能的话,我仍然想获得一些提示或指导。
我一直在查看一些遗留代码,发现其中的一部分包含异常嵌套 3 或 4 层的方法。
这是否被认为是一种正常的做法,还是应该尽可能避免这种代码风格?如果应该避免,除了增加异常处理的成本和降低可读性之外,还有哪些负面影响?有没有常见的重构代码的方法来避免这种情况?
这可能是一个广泛的问题,不是那么风格,但如果可能的话,我仍然想获得一些提示或指导。
我一直在查看一些遗留代码,发现其中的一部分包含异常嵌套 3 或 4 层的方法。
这是否被认为是一种正常的做法,还是应该尽可能避免这种代码风格?如果应该避免,除了增加异常处理的成本和降低可读性之外,还有哪些负面影响?有没有常见的重构代码的方法来避免这种情况?
我个人更喜欢以下意识形态
包装外星人异常
“外星人”异常是由 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.
}
}
如果可能,不应将检查的异常向上传播或链接。如果一个方法抛出一个检查异常,它的调用者应该处理它,如果调用者没有处理它并将它传播给它的调用者,那么整体复杂性就会增加。
在一个三层的例子中:Dao、Service、Controller
DAO 层将抛出 DAOException 服务层不应该将 DAOException 暴露给 Controller,而是应该抛出相关的 BuinessExceptions,而 Controller 应该处理这些异常。
我一直在查看一些遗留代码,发现其中的一部分包含异常嵌套 3 或 4 层的方法。
这是否被认为是一种正常的做法,还是应该尽可能避免这种代码风格?
这不是以这种方式处理您的异常的必要过程,因为它会增加您的应用程序开销,直到您确实需要处理非常具体的异常(已检查或 Alien Exceptions)并且您可以忽略开销以获取特定信息来处理该异常.
如果应该避免,除了增加异常处理的成本和降低可读性之外,还有哪些负面影响?
正如我所提到的,您不会获得有关异常的具体信息,如果您不打算使用嵌套异常处理(向上层处理程序抛出一些附加信息),您可能/可能不会代表一些棘手的异常执行特定操作,但在嵌套情况下,您可以通过处理特定情况来执行操作。
有没有常见的重构代码的方法来避免这种情况?
如果你的程序设计得很糟糕,可以做你想做的事情并且没有严重的错误,看在上帝的份上,别管它!当您需要修复错误或添加功能时,您会无情地重构您在努力中遇到的代码。覆盖自定义异常处理程序中的异常类并添加一些附加功能来处理您的问题。
重写方法不得抛出比重写方法声明的新异常或更广泛的已检查异常。例如,声明 FileNotFoundException 的方法不能被声明 SQLException、Exception 或任何其他非运行时异常的方法覆盖,除非它是 FileNotFoundException 的子类。
跳这会帮助你。
异常处理往往是一种处理流控制的昂贵方式(当然对于 C# 和 Java)。
构建异常对象时,运行时会做很多工作——将堆栈跟踪放在一起,找出处理异常的位置等等。
如果使用流控制语句进行流控制,则所有这些内存和 CPU 资源的成本都不需要扩展。
此外,还有一个语义问题。例外是针对特殊情况,而不是针对正常的流量控制。应该使用异常处理来处理意外/异常情况,而不是作为正常的程序流程,因为否则,未捕获的异常会告诉您更少。
除了这两个之外,还有其他人阅读代码的问题。大多数程序员不会期望以这种方式使用异常,因此代码的可读性和可理解性会受到影响。当一个人看到“异常”时,人们会想——发生了一件不好的事情,一件不应该正常发生的事情。因此,以这种方式使用异常只会令人困惑。
请查看以下链接
有两种方法:
To generate a separate exception for each event.
To create a generic exception and describe what caused it
第一种方法允许您编写不同的代码来处理不同的事件,但它需要您编写大量的异常类,并且在某些情况下可能太多了。
第二种方法更简洁,但很难处理不同的情况。
正如在编程中经常发生的那样,最好的解决方案是在中间,在这里您可以平衡生成单独的异常和将一个异常用于其他情况。
在这种情况下,经验法则可能是为要使用单独代码专门处理的异常生成单独的异常类。
与抛出什么类似,我们也应该控制捕获什么。我们可以对 catch 块使用两种方法:
一个单一的 catch 块。例如:
catch (Throwable e) {
throw new CommandExecutorException(e);
}
许多 catch 块为每个异常提供一个。例如:
} catch (ClassCastException e1) {
...
} catch (FileNotFoundException e) {
...
} catch (IOException e) {
...
}
第一种方法非常紧凑,但基本上将每个异常分组在同一个案例中,它仅在所有异常被平等管理并做同样事情的情况下才有用。这种方法通常不鼓励使用,因为它无法控制捕获的异常,有时会导致难以发现的严重错误。
第二种方法需要更多的代码行,但您可以根据发生的异常执行不同的操作。这种方法更灵活,但在某些情况下,由于异常处理,您的方法会变得很长。
关于处理遗留代码,我建议您看一下涵盖该主题的书: http ://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 您甚至不必阅读整个书,只看你此刻关心的事。
处理嵌套异常的最佳方法是重构代码并使用运行时而不是检查异常,并在需要时处理那些异常。这样代码更易读也更容易维护。
它取决于业务逻辑。您可以对异常本身采取行动,或者您可以将其一直传播到调用者并将其留给调用者以执行他想要的操作。
例如,有很多第三方 API 不处理异常,而是从方法中抛出异常,从而方便 API 用户根据需要采取行动。
eq oracle JDBC 驱动程序。Driver.getConnection() 抛出异常。现在调用者/API 用户可以根据需要处理它。一个可能只是打印堆栈跟踪,一个可能会通知管理员请求他的注意,或者一个可能选择只是静默退出应用程序。
您应该取消异常嵌套。您应该首先避免将异常链接起来,或者(有选择地)展开然后重新将嵌套的异常重新抛出堆栈。