簡體   English   中英

在 catch/finally 塊中拋出的吞咽異常

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM