最终在内部抛出异常


27

诸如Fortify之类的静态代码分析器会在一个finally块内抛出异常时说“抱怨” Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally。通常我同意这一点。但是最近我遇到了以下代码:

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} catch (...) {
     //exception handling
} finally {
     if (writer!= null) writer.close();  
}

现在,如果writer无法正确关闭,则该writer.close()方法将引发异常。应该抛出异常,因为(很可能是)写入后未保存文件。

我可以声明一个额外的变量,如果关闭时出错,则进行设置,writer并在finally块之后引发异常。但是这段代码可以正常工作,我不确定是否要更改它。

finally块内引发异常的缺点是什么?


4
如果这是Java,并且可以使用Java 7,请检查ARM块是否可以解决您的问题。
Landei 2013年

@Landei,这解决了它,但不幸的是我们没有使用Java 7
superM

我要说的是,您显示的代码不是“在finally块中使用throw语句”,因此逻辑上的进展就很好。
Mike

@Mike,我使用了Fortify显示的标准摘要,但最终还是直接或间接地引发了异常。
superM 2013年

不幸的是,Fortify也将try-with-resources块检测为最终抛出了异常。.它太聪明了,该死..仍然不确定如何克服它,看来try-with-resources的原因是要确保关闭资源终于,现在每个这样的语句由Fortify的报告为安全威胁..
mmona

Answers:


18

基本上,有finally子句可确保正确释放资源。但是,如果在finally块中引发异常,则该保证将消失。更糟糕的是,如果您的主代码块引发异常,则该代码块中引发的异常finally将隐藏该异常。看起来错误是由于调用引起的close,而不是出于真正原因。

有些人遵循令人讨厌的嵌套异常处理程序模式,吞噬了finally块中引发的所有异常。

SomeFileWriter writer = null; 
try { 
     //init the writer
     //write into the file
} finally {
    if (writer!= null) {
        try {
            writer.close();
        } catch (...) {
        }
    }
}

在Java的旧版本中,您可以通过将资源包装在为您执行“安全”清除的类中来“简化”此代码。我的一个好朋友创建了一个匿名类型列表,每种类型都提供了清理其资源的逻辑。然后,他的代码仅循环遍历该列表,并在该finally块内调用dispose方法。


1
+1虽然我是使用该讨厌代码的人之一。但是出于我的理由,我会说,仅当异常不是很关键时,我才不会总是这样做。
superM 2013年

2
我发现自己也这样做。有时创建任何整个资源管理器(无论是否匿名)都会损害代码的目的。
特拉维斯公园公园

我也+1;您基本上说了我要说的,但是更好。值得注意的是,Java中的某些Stream实现实际上无法在close()上抛出Exception,但是接口声明了它,因为其中有些实现了。因此,在某些情况下,您可能会添加一个实际上并不需要的catch块。
vaughandroid 2013年

1
在finally块中使用try / catch块有什么讨厌的?我宁愿这样做,也不愿通过包装所有连接和流等来膨胀我的所有代码。只是为了使它们可以安静地关闭-这本身就带来了一个新问题,即不知道是否关闭连接或流(或其他任何原因)都会导致异常-这是您实际上可能想了解的事情或对该事有所作为...
禁止进行地理工程

10

特拉维斯·帕克斯(Travis Parks)所说的话是正确的,即finally块中的异常将消耗任何返回值或try...catch块中的异常。

但是,如果您使用的是Java 7,则可以使用try-with-resources块解决问题。根据文档,只要您的资源实现了java.lang.AutoCloseable(大多数库作者/读者现在都这样做了),try-with-resources块就会为您关闭它。这样做的另一个好处是,在关闭它时发生的任何异常都将被抑制,从而允许原始返回值或异常向上传递。

FileWriter writer = null;
try {
  writer = new FileWriter("myFile.txt");
  writer.write("hello");
} catch(...) {
  // return/throw new exception
} finally {
  writer.close(); // an exception would consume the catch block's return/exception
}

try (FileWriter writer = new FileWriter("myFile.txt")) {
  writer.write("hello");
} catch(...) {
  // return/throw new exception, always gets returned even if writer fails to close
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


1
有时这很有帮助。但是在无法关闭文件的情况下,当我实际上需要引发异常时,这种方法会隐藏问题。
superM 2013年

1
@superM实际上,try-with-resources不会从中隐藏异常close()。“关闭它时发生的任何异常都将被抑制,从而允许原始返回值或异常通过”- 这根本不是真的close()仅当从try / catch块抛出另一个异常时,才会抑制来自方法的异常。因此,如果该try块没有引发任何异常,close()则将引发来自method的异常(并且不会返回返回值)。它甚至可以从当前catch块中捕获。
Ruslan Stelmachenko

0

我认为这是您需要逐案解决的问题。在某些情况下,分析器所说的是正确的,因为您拥有的代码不是那么好,需要重新考虑。但是,在其他情况下,最好甚至扔掉甚至重新扔掉。您不能强制执行此操作。


0

这将是为什么即使使用资源尝试方法也可能存在这些警告的概念性答案。不幸的是,这也不是您可能希望实现的简单解决方案。

错误恢复无法失败

finally 对事务成功或失败之后执行的事务后控制流进行建模。

在失败的情况下,finally即在执行捕获逻辑从错误中恢复,它已经完全恢复之前的(之前我们到达我们的catch目的地)。

想象一下,它呈现给遇到一个错误的概念问题,中央从错误中恢复的。

想象一下一个数据库服务器,我们试图在该数据库中提交事务,但是它在中途失败(例如,服务器在中间耗尽了内存)。现在服务器希望将事务回滚到某个点,好像什么也没发生。但是,可以想象它在回滚过程中遇到另一个错误。现在,我们最终对数据库进行了一半提交的事务-事务的原子性和不可分割的性质现在被破坏了,并且数据库的完整性也将受到损害。

这个概念性问题存在于处理错误的任何语言中,无论是使用C进行手动错误代码传播,还是使用C ++进行异常和析构函数,或者使用Java进行异常和finally

finally 不能以提供破坏者的方式相同的语言失败,析构函数在遇到异常的过程中也不能在C ++中失败。

避免此概念性难题的唯一方法是确保回滚事务和在中间释放资源的过程不可能遇到递归异常/错误。

因此,这里唯一安全的设计是writer.close()不可能失败的设计。在设计中,通常有一些方法可以避免在恢复过程中此类事情可能失败而无法实现的情况。

不幸的是,这是唯一的方法-错误恢复不会失败。确保这一点的最简单方法是使那些“资源释放”和“反作用”功能无法失效。这并不容易-正确的错误恢复很难,而且不幸的是很难测试。但是,实现此目标的方法是确保“破坏”,“关闭”,“关闭”,“回滚”等任何功能都不会在此过程中遇到外部错误,因为此类功能通常需要在从现有错误中恢复的过程中被调用。

示例:记录

假设您想将内容记录在一个finally块中。除非记录不能失败,否则这通常将是一个巨大的问题。日志记录几乎肯定会失败,因为它可能希望将更多数据附加到文件中,并且很容易找到失败的许多原因。

因此,这里的解决方案是使它成为可能,以便finally块中使用的任何日志记录函数都不会抛出给调用者(它可能会失败,但不会抛出)。我们该怎么做?如果您的语言允许在有嵌套的try / catch块的情况下最终抛出,则这将是通过吞入异常并将其转换为错误代码避免抛出给调用者的一种方法,例如,也许可以单独记录日志可能会分别失败并且不在现有错误恢复堆栈展开的进程或线程。只要您可以与该进程进行通信而不会出现错误,那也是异常安全的,因为如果我们递归地从同一线程中抛出该安全问题,则仅在这种情况下存在安全问题

在这种情况下,我们可以避免日志记录失败,但前提是它不会抛出日志,因为无法记录日志和不执行任何操作不是世界末日(例如,它不会泄漏任何资源或无法回滚副作用)。

无论如何,我相信您已经可以开始想象真正使软件具有异常安全性是多么的困难。除了最关键任务的软件外,可能没有必要在所有方面都做到这一点。但是值得一提的是如何真正实现异常安全,因为即使是非常通用的库作者也常常在这里摸索,破坏了使用该库的应用程序的整体异常安全。

SomeFileWriter

如果SomeFileWriter可以扔进去close,那么我会说它通常与异常处理不兼容,除非您从未尝试在涉及从现有异常中恢复的上下文中关闭它。如果该代码不在您的控制范围内,那么我们可能是SOL,但值得将此明显的异常安全问题通知作者。如果它在您的控制范围内,我的主要建议是确保关闭它不会因任何必要的方式而失败。

想象一下,如果操作系统实际上无法关闭文件。现在,任何试图在关闭时关闭文件的程序都将无法关闭。我们现在应该怎么做,只是保持应用程序处于打开状态并且处于混乱状态(可能没有),只是泄漏文件资源并忽略该问题(如果不是很关键就可以了)?最安全的设计:使其无法关闭文件是不可能的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.