[英]Swallowing exception thrown in catch/finally block
通常我會遇到這樣的情況,我必須吞下catch
/ finally
塊中的清理代碼拋出的異常,以防止原始異常被吞下。
例如:
// 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;
}
}
}
假設記錄任何異常不是方法塊范圍內的選項,而是由調用example1()
和example2()
方法的代碼完成。
吞下close()
和Rollback()
方法拋出的異常是個好主意嗎? 如果不是,那么處理上述情況的更好方法是什么,以便不吞下異常?
我不喜歡捕獲和重新拋出異常。
如果你抓住了它,就用它做點什么——即使它只是記錄了異常。
如果你不能用它做任何事情,不要抓住它 - 在方法簽名中添加一個 throws 子句。
捕獲異常告訴我,您可以處理異常情況並制定恢復計划,或者“責任到此為止”,因為異常無法以這種形式傳播得更遠(例如,沒有堆棧跟蹤回用戶)。
您可以創建一個自定義Exception
類型,該類型可以包含這兩個異常。 如果重載ToString()
,則可以記錄這兩個異常。
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 語句將確保(通過 finally)無論有任何異常都關閉連接並讓原始異常冒泡(帶有完整/正確的堆棧跟蹤)。 如果在調用 Commit 之前發生異常,則事務將永遠不會提交,並且會在事務/連接關閉時自動回滾。
我相信異常應該是你意想不到的。 如果您期望異常,那么您應該對它做些什么。 因此,在您的第一個示例中,如果您還聲明您的方法將拋出 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()
被調用兩次。
在不了解您的所有特定情況的情況下,您可以考慮拋出一個新的異常。 至少在 C# 中,當拋出新異常時,可選構造函數之一接受現有異常作為參數。 例如:
throw new Exception("This is my new exception.", ex);
這樣做的目的是保留原始異常。
另一種選擇可能是 try .. catch .. finally 構造。
try { // 可能拋出異常的普通代碼 } catch (Exception ex) { // 處理第一個異常 } finally { // 處理任何清理,無論拋出異常如何 }
一般來說,如果我的代碼可以處理特定 try .. catch 中的異常,那么我不會重新拋出該異常。 如果調用堆棧上的某些內容具有該異常很重要,我將拋出一個新異常並將原始異常設置為內部異常。
通常情況下,風險代碼都放在一個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
}
或者,另一個好主意是為同一次嘗試放置多個捕獲:
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..
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.