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