在catch / finally块中抛出吞咽exception

通常我会遇到这样的情况:我必须吞下catch / finally块中清理代码抛出的exception,以防止吞噬原始exception。

例如:

 // Closing a file in Java public void example1() throws IOException { boolean exceptionThrown = false; FileWriter out = new FileWriter(“test.txt”); try { out.write(“example”); } catch (IOException ex) { exceptionThrown = true; throw ex; } finally { try { out.close(); } catch (IOException ex) { if (!exceptionThrown) throw ex; // Else, swallow the exception thrown by the close() method // to prevent the original being swallowed. } } } // Rolling back a transaction in .Net public void example2() { using (SqlConnection connection = new SqlConnection(this.connectionString)) { SqlCommand command = connection.CreateCommand(); SqlTransaction transaction = command.BeginTransaction(); try { // Execute some database statements. transaction.Commit(); } catch { try { transaction.Rollback(); } catch { // Swallow the exception thrown by the Rollback() method // to prevent the original being swallowed. } throw; } } } 

假设记录任何exception不是方法块范围内的选项,而是由调用example1()example2()方法的代码完成。

吞咽close()Rollback()方法引发的exception是一个好主意吗? 如果没有,那么处理上述情况的更好方法是什么,以免exception被吞噬?

我不喜欢抓住并重新抛出exception。

如果你抓住它,用它做一些事情 – 即使它只是记录exception。

如果你不能对它做任何事情,不要抓住它 – 在方法签名中添加一个throws子句。

捕获exception告诉我,您可以处理exception情况并制定恢复计划或“降压停止此处”,因为exception不能以该forms传播任何更远(例如,没有堆栈跟踪回到用户)。

您可以创建可以包含两个exception的自定义exception类型。 如果重载ToString(),则可以记录这两个exception。

 try { transaction.Commit(); } catch(Exception initialException) { try { transaction.Rollback(); } catch(Exception rollbackException) { throw new RollbackException(initialException, rollbackException); } throw; } 

这正是Commons IO有一个IOUtils.closeQuietly方法的原因。 在大多数情况下,关闭文件时出错的地方并不那么有趣。

必须回滚的数据库事务可能更有趣,因为在这种情况下,该函数没有做它应该做的事情(把东西放在数据库中)。

没有理由在C#代码中回滚事务。 如果你关闭连接而没有回滚事务(或提交它),这是相同和更有效的……

 public void example2() { using (SqlConnection connection = new SqlConnection(this.connectionString)) using (SqlCommand command = connection.CreateCommand()) using (SqlTransaction transaction = command.BeginTransaction()) { // Execute some database statements. transaction.Commit(); } } 

你完成了

using语句将确保(最后通过)连接被关闭而不管任何exception,并让原始exception冒出来(使用完整/正确的堆栈跟踪)。 如果在调用Commit之前发生exception,则事务将永远不会提交,并且在事务/连接关闭时将自动回滚。

我相信exception应该是你不期望的。 如果你期望一个例外,那么你应该对它做点什么。 因此,在您的第一个示例中,我认为如果您还声明您的方法将抛出IOException,您可能不应该费心去捕获IOException。

我会考虑重写example1如下:

 // Closing a file in Java public void example1() throws IOException { boolean success = false; FileWriter out = new FileWriter(“test.txt”); try { out.write(“example”); success = true; out.close(); } finally { if (!success) { try { out.close(); } catch (IOException ex) { // log and swallow. } } } } 

移动success = true;out.close(); 声明会使success的含义更清晰……虽然它可能导致out.close()被调用两次。

如果不了解您的特定情况,您可以考虑抛出新的exception。 至少在C#中,当抛出一个新的exception时,其中一个可选的构造函数接受一个现有的exception作为参数。 例如:

 throw new Exception("This is my new exception.", ex); 

这样做的目的是保留原始exception。

另一种选择可能是try .. catch ..最后构造。

try {//可能引发exception的普通代码} catch(Exception ex){//处理第一个exception} finally {//处理任何清理而不管抛出exception}

一般来说,如果我的代码可以在特定的try .. catch中处理exception,那么我不会重新抛出该exception。 如果调用堆栈中的某些内容对于该exception很重要,我将抛出一个新exception并将原始exception设置为内部exception。

通常,风险代码都放在一个try-catch块中。 嵌套的try-catch块不是一个好主意,IMO(或者只是尽可能地避免嵌套的try-catch块,除非你真的需要它们)。

因为风险代码是特殊情况,所以为特殊情况下的特殊代码提供更特殊的情况,这是很多不必要的工作。

例如在example1() ,将所有有风险的代码放在一个try-catch块中:

 try{ FileWriter out = new FileWriter(“test.txt”); out.write(“example”); out.close(); } catch(Exception e) { //handle exception } 

或者,另一个好主意是为同一个尝试放置几个catch:

 try{ FileWriter out = new FileWriter(“test.txt”); out.write(“example”); out.close(); } catch(FileNotFoundException e) { //if IOException, do this.. } catch(IOException e) { //if FileNotFound, do this.. } catch(Exception e) { //completely general exception, do this.. }