47

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

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

这个问题很难解决,因为 doSomeStuff() 引发了异常,这反过来又导致 doSomeOtherStuff() 也引发了异常。第二个异常(由 finally 块抛出)被抛出到我的代码中,但它没有处理第一个异常(从 doSomeStuff() 抛出),这是问题的真正根本原因。

如果代码是这样说的,问题就会很明显:

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

所以,我的问题是:

在没有任何 catch 块的情况下使用 finally 块是众所周知的 java 反模式吗?(它当然似乎是众所周知的反模式“不要吞噬异常!”的一个不太明显的子类)

4

12 回答 12

65

一般来说,不,这不是反模式。finally 块的目的是确保无论是否引发异常,都可以清理内容。异常处理的全部意义在于,如果您不能处理它,您可以通过提供的相对干净的带外信号异常处理提供的相对干净的带外信令,让它冒泡给可以处理的人。如果您需要确保在引发异常时清理内容,但无法正确处理当前范围内的异常,那么这正是正确的做法。您可能只想更加小心,以确保您的 finally 块不会抛出。

于 2009-03-02T03:14:56.650 回答
30

我认为这里真正的“反模式”是在一个finally可以抛出的块中做一些事情,而不是没有捕获。

于 2009-03-02T03:19:51.100 回答
17

一点也不。

问题出在 finally 里面的代码。

请记住,finally 总是会被执行,并且将可能引发异常的东西放在那里是有风险的(正如您刚刚见证的那样)。

于 2009-03-02T04:15:15.883 回答
11

尝试使用 finally 并且没有捕获绝对没有错。考虑以下:

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 块是非常有意义的。

于 2009-03-02T03:15:03.187 回答
5

我认为它远不是一种反模式,而且当释放方法执行期间获得的资源至关重要时,我经常这样做。

我在处理文件句柄(用于写入)时做的一件事是在使用 IOUtils.closeQuietly 方法关闭流之前刷新流,该方法不会引发异常:


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

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

  • 关闭文件时忽略异常并不是完全安全的——如果还有字节还没有写入文件,那么文件可能不是调用者期望的状态;
  • 因此,如果在 flush() 方法期间引发异常,它将传播给调用者,但我仍然会确保所有文件都已关闭。IOUtils.closeQuietly(...) 方法比相应的 try ... catch ... ignore me 块更简洁;
  • 如果使用多个输出流,则 flush() 方法的顺序很重要。应首先刷新通过将其他流作为构造函数传递而创建的流。同样的事情对 close() 方法也是有效的,但在我看来,flush() 更清楚。
于 2009-03-02T05:23:03.193 回答
1

我会说没有 catch 块的 try 块是一种反模式。说“没有捕获就不要最终”是“没有捕获就不要尝试”的子集。

于 2009-03-02T03:14:10.333 回答
1

我以下列形式使用 try/finally:

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(Exception)。

于 2009-03-02T03:40:19.367 回答
1
try {
    doSomeStuff()
    doMore()
} catch (Exception e) {
    log.error(e);
} finally {
   doSomeOtherStuff()
}

也不要那样做......你只是隐藏了更多的错误(当然没有完全隐藏它们......但是更难处理它们。当你捕捉到异常时,你也会捕捉到任何类型的 RuntimeException(如 NullPointer 和 ArrayIndexOutOfBounds) .

一般来说,捕获你必须捕获的异常(检查异常)并在测试时处理其他异常。RuntimeExceptions 旨在用于程序员错误 - 程序员错误是在正确调试的程序中不应该发生的事情。

于 2009-03-02T03:40:36.573 回答
1

在我看来,更多的情况finallycatch表明某种问题。资源成语很简单:

acquire
try {
    use
} finally {
    release
}

在 Java 中,您几乎可以在任何地方遇到异常。获取通常会抛出一个已检查的异常,处理该异常的明智方法是围绕多少进行捕获。不要尝试一些可怕的空值检查。

如果你真的是肛门,你应该注意例外中有隐含的优先级。例如 ThreadDeath 应该破坏所有,无论它来自获取/使用/释放。正确处理这些优先事项是不雅观的。

因此,使用 Execute Around 习惯用法抽象您的资源处理。

于 2009-03-02T12:49:12.307 回答
0

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

顺便说一句,我认为 log4J几乎是一种反模式。

如果您想检查正在运行的程序,请使用适当的检查工具(即调试器、IDE,或者极端意义上的字节码编织器,但不要在每几行中都放置日志语句!)。

在你提出的两个例子中,第一个看起来是正确的。第二个包括记录器代码并引入了一个错误。在第二个示例中,如果前两个语句抛出一个异常(即捕获并记录它但不重新抛出),则抑制异常。这是我发现在 log4j 使用中非常常见的事情,并且是应用程序设计的一个真正问题。基本上通过您的更改,您使程序以一种系统难以处理的方式失败,因为您基本上继续前进,就好像您从未遇到过异常一样(类似于 VB basic on error resume next 构造)。

于 2009-10-12T04:44:13.780 回答
0

try-finallyreturn如果方法有多个语句,可以帮助您减少复制粘贴代码。考虑以下示例(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 falseif

于 2014-04-15T10:39:33.673 回答
-1

我认为没有捕获的尝试是反模式。使用 try/catch 处理异常情况(文件 IO 错误、套接字超时等)不是反模式。

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

于 2009-03-02T03:51:24.957 回答