是一个没有捕获块java终止模式的反模式?

我在解决一些看起来像这样的代码时遇到了非常痛苦的故障排除经验:

try { doSomeStuff() doMore() } finally { doSomeOtherStuff() } 

问题很难排除,因为doSomeStuff()抛出一个exception,这又导致doSomeOtherStuff()也抛出一个exception。 第二个exception(由finally块引发)被抛出到我的代码,但它没有处理第一个exception(从doSomeStuff()抛出),这是问题的真正根源。

如果代码是这样说的话,那么问题就显而易见了:

 try { doSomeStuff() doMore() } catch (Exception e) { log.error(e); } finally { doSomeOtherStuff() } 

所以,我的问题是这样的:

是不是一个知名的Java反模式使用没有任何catch块的finally块? (这当然似乎是一个显而易见的反模式的不明显的子类“不要吞噬例外!”)

一般来说,不,这不是反模式。 finally块的目的是确保东西得到清理,无论是否抛出exception。 exception处理的重点在于,如果你不能处理这个问题,你可以通过相对干净的带外信令exception处理提供的东西让它冒泡。 如果您需要确保在抛出exception时清理垃圾,但无法正确处理当前作用域中的exception,那么这是正确的。 你可能想要更小心地确保你的finally块不会抛出。

我认为这里真正的“反模式”是在一个可以投掷的东西上做一些事情,而不是没有抓住。

一点也不。

最后的代码出了什么问题。

请记住,最后总是会被执行,而且只是有风险(就像你刚刚目睹的那样),把一些可能抛出exception的东西放在那里。

尝试一个终于没有抓住绝对没有错。 考虑以下:

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

如果抛出一个exception,并且这段代码不知道如何处理它,那么这个exception就会把调用栈唤醒给调用者。 在这种情况下,我们仍然希望清理stream,所以我认为有一个尝试块没有捕获是完全合理的。

我认为这远非反模式,而且在执行方法执行期间取消分配资源时,我非常关心。

处理文件句柄(用于写入)时,我所做的一件事是在使用IOUtils.closeQuietly方法closures之前刷新stream,该方法不会抛出exception:

OutputStream os = null; OutputStreamWriter wos = null; try { os = new FileOutputStream(...); wos = new OutputStreamWriter(os); // Lots of code wos.flush(); os.flush(); finally { IOUtils.closeQuietly(wos); IOUtils.closeQuietly(os); }
OutputStream os = null; OutputStreamWriter wos = null; try { os = new FileOutputStream(...); wos = new OutputStreamWriter(os); // Lots of code wos.flush(); os.flush(); finally { IOUtils.closeQuietly(wos); IOUtils.closeQuietly(os); } 

我喜欢这样做,原因如下:

  • 在closures文件时忽略exception是不完全安全的 – 如果有字节还没有写入文件,那么文件可能不处于调用者期望的状态;
  • 所以,如果在flush()方法中引发exception,它将被传播给调用者,但我仍然会确保所有的文件都被closures。 方法IOUtils.closeQuietly(…)不那么详细,然后相应的try … catch …忽略我块;
  • 如果使用多个输出stream,则flush()方法的顺序非常重要。 通过传递其他stream作为构造函数创build的stream应该首先被刷新。 close()方法也是一样,但我认为flush()更清晰。

我用try / finally的forms如下:

 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(+终于嵌套try / catch)。 我认为这是更简洁,我不重复catch(例外)。

 try { doSomeStuff() doMore() } catch (Exception e) { log.error(e); } finally { doSomeOtherStuff() } 

不要这样做……你只是隐藏了更多的bug(并没有完全隐藏它们,但是更难处理它们)当你捕获exception时,你也捕获任何types的RuntimeException(如NullPointer和ArrayIndexOutOfBounds) 。

一般来说,捕捉必须捕获的exception(检查exception)并在testing时间处理其他exception。 RuntimeExceptions被devise为用于程序员错误 – 程序员错误是在正确debugging的程序中不应该发生的事情。

在我看来,情况更是如此, finally catch了一个问题。 资源成语非常简单:

 acquire try { use } finally { release } 

在Java中,你几乎可以在任何地方发生exception。 通常情况下,获取抛出一个检查的exception,处理这个问题的明智方式是把握多less。 不要尝试一些可怕的空检查。

如果你真的要肛门分明,你应该注意到,例外情况中有隐含的优先事项。 例如ThreadDeath应该全部打开,无论是来自acquire / use / release。 正确处理这些优先事项是不美观的。

因此,用Execute Around成语将您的资源处理抽象出来。

我会说一个没有catch块的try块是一个反模式。 说“没有一个抓不到的终极”是“不要一试就不试一试”的一个子集。

Try / Finally是一种正确释放资源的方法。 finally块中的代码永远不应该抛出,因为它只应该在获取之前获得的资源或状态下进入try块。

另外,我认为log4J 几乎是一个反模式。

如果您想要检查一个正在运行的程序,请使用正确的检查工具(例如,debugging器,IDE,或者在极端意义上的字节码编织器,但不要在每条线上放置测井语句!

在你介绍的两个例子中,第一个看起来是正确的。 第二个包括logging器代码并引入一个错误。 在第二个例子中,如果前两个语句抛出exception(例如,捕获并logging它,但不重新抛出exception),则会压制一个exception,这在log4j的使用中非常普遍,是一个真正的应用程序devise问题。随着你的改变,你使程序失败的方式将是非常难的系统来处理,因为你基本上前进,就好像你从来没有例外(就像VB基本错误恢复下一个构造)。

如果方法有多个return语句, try-finally可以帮助你减less复制粘贴代码。 考虑下面的例子(Android Java):

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

如果没有finally子句,则可能必须写两次cursor.close() :在return false之前以及在周围的if子句之后。

我认为,没有抓住就是反模式。 使用try / catch处理exception情况(文件IO错误,套接字超时等)不是反模式。

如果您正在使用try / finally进行清理,请考虑使用块。