Is a finally block without a catch block a java anti-pattern?
我刚刚在排除一些类似以下代码的故障时有过非常痛苦的故障排除经验:
1 2 3 4 5 6 | try { doSomeStuff() doMore() } finally { doSomeOtherStuff() } |
这个问题很难解决,因为dosomestufacture()引发了一个异常,从而导致dosomeotherstufacture()也引发了一个异常。第二个异常(由finally块抛出)被抛出到我的代码中,但是它没有处理第一个异常(从dosomestufacture()抛出),这是问题的真正根本原因。
如果代码说的是这样的话,问题就显而易见了:
1 2 3 4 5 6 7 8 | try { doSomeStuff() doMore() } catch (Exception e) { log.error(e); } finally { doSomeOtherStuff() } |
所以,我的问题是:
最后一个块使用没有任何catch块一个著名的Java反模式?(这显然是众所周知的反模式"不要吞咽异常"的一个不太明显的子类。)
一般来说,不,这不是反模式。finally块的要点是无论是否抛出异常,都要确保清除所有内容。异常处理的关键是,如果你不能处理它,你就让它冒泡到某个人身上,通过相对干净的带外信号异常处理提供。如果您需要确保在抛出异常时清理所有内容,但在当前范围内无法正确处理异常,那么这正是正确的操作。你可能只是想更小心一点,确保你的最后一个挡块没有扔出去。
我认为真正的"反模式"是在一个
一点也不.
出了问题的是最后里面的代码。
记住,最后总是会被执行,把可能引发异常的东西放在那里是有风险的(正如你刚才看到的)。
没有什么错,一次最后的尝试,一次没有收获的尝试。考虑以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 | InputStream in = null; try { in = new FileInputStream("file.txt"); // Do something that causes an IOException to be thrown } finally { if (in != null) { try { in.close(); } catch (IOException e) { // Nothing we can do. } } } |
如果一个异常被抛出,而这个代码不知道如何处理它,那么这个异常应该在调用方的调用堆栈中冒泡。在这种情况下,我们仍然希望清理流,所以我认为在没有catch的情况下使用try块是完全有意义的。
我认为它远不是反模式的,而且在方法执行过程中释放获得的资源非常关键时,我经常这样做。
在处理文件句柄(用于写入)时,我要做的一件事是在使用ioutils.closequisely方法关闭流之前对其进行刷新,这不会引发异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
我喜欢这样做,原因如下:
- 关闭一个文件时忽略一个异常是不完全安全的——如果有字节尚未写入文件,那么该文件可能不处于调用者预期的状态;
- 因此,如果在flush()方法期间引发异常,它将传播到调用方,但我仍然会确保所有文件都已关闭。方法ioutils.closequisely(…)没有相应的try那么冗长。抓住…忽略我的阻挡;
- 如果使用多个输出流,flush()方法的顺序很重要。应首先刷新通过将其他流作为构造函数传递而创建的流。对于close()方法同样有效,但是flush()在我看来更清晰。
在我看来,更多的情况是,
1 2 3 4 5 6 | acquire try { use } finally { release } |
在爪哇,你可以在任何地方都有例外。通常,Acquire抛出一个检查过的异常,处理这种异常的明智方法是将一个catch放在what lot周围。不要尝试一些可怕的空检查。
如果你真的要分析,你应该注意到在例外中有隐含的优先权。例如,无论是来自获取/使用/发布,threaddeah都应该将其全部删除。正确处理这些优先级是不明智的。
因此,使用围绕习语执行来抽象资源处理。
1 2 3 4 5 6 7 8 | try { doSomeStuff() doMore() } catch (Exception e) { log.error(e); } finally { doSomeOtherStuff() } |
也不要这样做…你只是藏了更多的虫子(不完全是藏起来的…但这让他们更难对付。捕获异常时,还将捕获任何类型的RuntimeException(如NullPointer和ArrayIndexOutOfBounds)。
通常,捕获必须捕获的异常(选中的异常),并在测试时处理其他异常。RuntimeExceptions被设计用于程序员错误——而程序员错误是在正确调试的程序中不应该发生的事情。
我使用以下形式的Try/Finally:
1 2 3 4 5 6 7 8 9 10 11 12 | try{ Connection connection = ConnectionManager.openConnection(); try{ //work with the connection; }finally{ if(connection != null){ connection.close(); } } }catch(ConnectionException connectionException){ //handle connection exception; } |
我更喜欢这个,而不是try/catch/finally(+finally中嵌套的try/catch)。我认为它更简洁,我不复制catch(例外)。
我想说,一个没有抓块的试块是反模式的。说"没有最后的收获就没有收获"是"没有收获就没有尝试"的一个子集。
如果一个方法有多个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) { Cursor cursor = db.rawQuery("SELECT * FROM table", null); if (cursor != null) { try { if (cursor.getCount() == 0) { return false; } } finally { // this will get executed even if return was executed above cursor.close(); } } // database had rows, so do something... return true; } |
如果没有
Try/Finally是一种适当释放资源的方法。finally块中的代码不应引发,因为它只应作用于在进入try块之前获取的资源或状态。
顺便说一下,我认为log4j几乎是一种反模式。
如果要检查正在运行的程序,请使用适当的检查工具(即调试器、IDE或从极端意义上说是字节代码weaver,但不要每隔几行放置日志语句!).
在这两个例子中,第一个看起来是正确的。第二个包含记录器代码并引入了一个bug。在第二个示例中,如果前两个语句抛出了一个异常(即捕获并记录它,但不重新引发),则会抑制该异常。这是我在log4j用法中发现的非常常见的一个问题,也是应用程序设计的一个真正问题。基本上,随着你的改变,你使程序失败的方式,将是非常困难的系统处理,因为你基本上前进,如果你从来没有一个例外(排序像VB基本的错误恢复下一个构造)。
我认为不被抓住的尝试是反模式的。使用try/catch处理异常情况(文件IO错误、套接字超时等)不是反模式。
如果使用try/finally进行清理,请考虑使用一个using块。