例外狀況的最佳做法Best practices for exceptions

設計良好的應用程式可處理例外狀況和錯誤,防止應用程式損毀。A well-designed app handles exceptions and errors to prevent app crashes. 本節說明處理和建立例外狀況的最佳做法。This section describes best practices for handling and creating exceptions.

使用 try/catch/finally 區塊Use try/catch/finally blocks

使用 try/catch/finally 區塊括住程式碼可能會產生例外狀況。Use try/catch/finally blocks around code that can potentially generate an exception.

catch 區塊中,一律將例外狀況從最特殊的排列到最不特殊的。In catch blocks, always order exceptions from the most specific to the least specific.

不論您是否可以復原,使用 finally 區塊都可清除資源。Use a finally block to clean up resources, whether you can recover or not.

處理常見的狀況,而不擲回例外狀況Handle common conditions without throwing exceptions

針對可能發生但可能會觸發例外狀況的狀況,請考慮以避免例外狀況的方式來處理這些狀況。For conditions that are likely to occur but might trigger an exception, consider handling them in a way that will avoid the exception. 例如,如果您嘗試關閉已關閉的連線,您會得到 InvalidOperationExceptionFor example, if you try to close a connection that is already closed, you'll get an InvalidOperationException. 您可以在嘗試關閉之前,使用 if 陳述式來檢查連線狀態,以避免此例外狀況。You can avoid that by using an if statement to check the connection state before trying to close it.

if (conn->State != ConnectionState::Closed)
{
    conn->Close();
}
if (conn.State != ConnectionState.Closed)
{
    conn.Close();
}
If conn.State <> ConnectionState.Closed Then
    conn.Close()
End IF

如果您沒有檢查連線狀態就關閉,您可能會攔截到 InvalidOperationException 例外狀況。If you don't check connection state before closing, you can catch the InvalidOperationException exception.

try
{
    conn->Close();
}
catch (InvalidOperationException^ ex)
{
    Console::WriteLine(ex->GetType()->FullName);
    Console::WriteLine(ex->Message);
}
try
{
    conn.Close();
}
catch (InvalidOperationException ex)
{
    Console.WriteLine(ex.GetType().FullName);
    Console.WriteLine(ex.Message);
}
Try
    conn.Close()
Catch ex As InvalidOperationException
    Console.WriteLine(ex.GetType().FullName)
    Console.WriteLine(ex.Message)
End Try

選擇的方法取決於您預期事件會發生的頻率。The method to choose depends on how often you expect the event to occur.

  • 如果事件不常發生,也就是說事件真的是例外並且表示錯誤 (例如未預期的檔案結尾),則使用例外狀況處理。Use exception handling if the event doesn't occur very often, that is, if the event is truly exceptional and indicates an error (such as an unexpected end-of-file). 當您使用例外狀況處理時,在正常情況下會執行較少的程式碼。When you use exception handling, less code is executed in normal conditions.

  • 如果事件定期發生而且可視為正常執行的一部分,請檢查程式碼中是否有錯誤狀況。Check for error conditions in code if the event happens routinely and could be considered part of normal execution. 當您檢查是否有常見的錯誤狀況時,因為避免例外狀況,所以會執行較少的程式碼。When you check for common error conditions, less code is executed because you avoid exceptions.

設計類別以避免例外狀況Design classes so that exceptions can be avoided

類別可提供方法或屬性,讓您避免進行會觸發例外狀況的呼叫。A class can provide methods or properties that enable you to avoid making a call that would trigger an exception. 例如,FileStream 類別會提供方法,協助判斷是否已經抵達檔案結尾。For example, a FileStream class provides methods that help determine whether the end of the file has been reached. 這些方法或屬性可用來避免萬一您讀取超過檔案結尾時,所擲回的例外狀況。These can be used to avoid the exception that is thrown if you read past the end of the file. 下列範例示範如何讀取至檔案結尾,而不會觸發例外狀況。The following example shows how to read to the end of a file without triggering an exception.

class FileRead
{
public:
    void ReadAll(FileStream^ fileToRead)
    {
        // This if statement is optional
        // as it is very unlikely that
        // the stream would ever be null.
        if (fileToRead == nullptr)
        {
            throw gcnew System::ArgumentNullException();
        }

        int b;

        // Set the stream position to the beginning of the file.
        fileToRead->Seek(0, SeekOrigin::Begin);

        // Read each byte to the end of the file.
        for (int i = 0; i < fileToRead->Length; i++)
        {
            b = fileToRead->ReadByte();
            Console::Write(b.ToString());
            // Or do something else with the byte.
        }
    }
};
class FileRead
{
    public void ReadAll(FileStream fileToRead)
    {
        // This if statement is optional
        // as it is very unlikely that
        // the stream would ever be null.
        if (fileToRead == null)
        {
            throw new System.ArgumentNullException();
        }

        int b;

        // Set the stream position to the beginning of the file.
        fileToRead.Seek(0, SeekOrigin.Begin);

        // Read each byte to the end of the file.
        for (int i = 0; i < fileToRead.Length; i++)
        {
            b = fileToRead.ReadByte();
            Console.Write(b.ToString());
            // Or do something else with the byte.
        }
    }
}
Class FileRead
    Public Sub ReadAll(fileToRead As FileStream)
        ' This if statement is optional
        ' as it is very unlikely that
        ' the stream would ever be null.
        If fileToRead Is Nothing Then
            Throw New System.ArgumentNullException()
        End If

        Dim b As Integer

        ' Set the stream position to the beginning of the file.
        fileToRead.Seek(0, SeekOrigin.Begin)

        ' Read each byte to the end of the file.
        For i As Integer = 0 To fileToRead.Length - 1
            b = fileToRead.ReadByte()
            Console.Write(b.ToString())
            ' Or do something else with the byte.
        Next i
    End Sub
End Class

另一個避免例外狀況的方法是針對很常見的錯誤案例傳回 null,而不擲回例外狀況。Another way to avoid exceptions is to return null for extremely common error cases instead of throwing an exception. 相當普遍的錯誤案例可視為一般控制流程。An extremely common error case can be considered normal flow of control. 針對這些案例傳回 null,就能盡量降低對應用程式效能的影響。By returning null in these cases, you minimize the performance impact to an app.

擲回例外狀況來代替傳回錯誤碼Throw exceptions instead of returning an error code

例外狀況可確保不會發生未通知失敗的情況,因為呼叫程式碼並不會檢查傳回碼。Exceptions ensure that failures do not go unnoticed because calling code didn't check a return code.

使用預先定義的 .NET 例外狀況類型Use the predefined .NET exception types

只有在預先定義的類型不適用時,才引進新的例外狀況類別。Introduce a new exception class only when a predefined one doesn't apply. 例如: For example:

使用字組 Exception 作為例外狀況類別名稱的結尾End exception class names with the word Exception

如需自訂例外狀況,請適當地加以命名,並從 Exception加以衍生。When a custom exception is necessary, name it appropriately and derive it from the Exception class. 例如: For example:

public ref class MyFileNotFoundException : public Exception
{
};
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
    Inherits Exception
End Class

在自訂例外狀況類別中包含三個建構函式Include three constructors in custom exception classes

當您建立自己的例外狀況類別時,請使用至少三個種常見的建構函式:預設建構函式、採用字串訊息的建構函式,以及採用字串訊息和內部例外狀況的建構函式。Use at least the three common constructors when creating your own exception classes: the default constructor, a constructor that takes a string message, and a constructor that takes a string message and an inner exception.

如需範例,請參閱如何:建立使用者定義的例外狀況For an example, see How to: Create User-Defined Exceptions.

確保從遠端執行程式碼時可以使用例外狀況資料Ensure that exception data is available when code executes remotely

當您建立使用者定義的例外狀況時,請確保例外狀況的中繼資料可供遠端執行的程式碼使用。When you create user-defined exceptions, ensure that the metadata for the exceptions is available to code that is executing remotely.

例如,在支援應用程式定義域的 .NET 實作上,例外狀況可能會跨應用程式定義域發生。For example, on .NET implementations that support App Domains, exceptions may occur across App domains. 假定應用程式定義域 A 建立應用程式定義域 B,它會執行擲回例外狀況的程式碼。Suppose App Domain A creates App Domain B, which executes code that throws an exception. 為使應用程式定義域 A 能夠正確地攔截及處理例外狀況,其必須能夠尋找含有應用程式定義域 B 所擲回之例外狀況的組件。若應用程式定義域 B 擲回例外狀況,但此例外狀況包含在位於其應用程式基底之下的組件,而不是位於在應用程式定義域 A 的應用程式基底之下的組件,應用程式定義域 A 將無法尋找例外狀況,而且 Common Language Runtime 將會擲回 FileNotFoundException 例外狀況。For App Domain A to properly catch and handle the exception, it must be able to find the assembly that contains the exception thrown by App Domain B. If App Domain B throws an exception that is contained in an assembly under its application base, but not under App Domain A's application base, App Domain A will not be able to find the exception, and the common language runtime will throw a FileNotFoundException exception. 若要避免這個情形,您可以透過兩種方式部署含有例外狀況資訊的組件:To avoid this situation, you can deploy the assembly that contains the exception information in two ways:

  • 將組件放入這兩個應用程式定義域共用的通用應用程式基底。Put the assembly into a common application base shared by both app domains.

    -或-- or -

  • 如果定義域不共用通用應用程式基底,則以強式名稱簽署含有例外狀況資訊的組件,並將組件部署到全域組件快取中。If the domains do not share a common application base, sign the assembly that contains the exception information with a strong name and deploy the assembly into the global assembly cache.

使用文法正確的錯誤訊息Use grammatically correct error messages

撰寫清楚的句子並包含結尾標點符號。Write clear sentences and include ending punctuation. 在每個指派給 Exception.Message 屬性的字串中之句子,都應以句點結束。Each sentence in the string assigned to the Exception.Message property should end in a period. 例如,「記錄資料表已溢位。」For example, "The log table has overflowed." 就是正確的訊息字串。would be an appropriate message string.

在每個例外狀況中,納入當地語系化的字串訊息Include a localized string message in every exception

使用者所看到的錯誤訊息,衍生自擲回的例外狀況之 Exception.Message 屬性,而並非來自例外狀況類別的名稱。The error message that the user sees is derived from the Exception.Message property of the exception that was thrown, and not from the name of the exception class. 一般來說,您要將值指派到 Exception.Message 屬性,方法是將訊息字串傳遞到例外狀況建構函式message 引數。Typically, you assign a value to the Exception.Message property by passing the message string to the message argument of an Exception constructor.

若是當地語系化的應用程式,則應對每個應用程式可能會擲回的例外狀況,該提供當地語系化的訊息字串。For localized applications, you should provide a localized message string for every exception that your application can throw. 您可使用資源檔,提供當地語系化的錯誤訊息。You use resource files to provide localized error messages. 如需當地語系化應用程式與擷取當地語系化字串的詳細資訊,請參閱傳統型應用程式中的資源System.Resources.ResourceManagerFor information on localizing applications and retrieving localized strings, see Resources in Desktop Apps and System.Resources.ResourceManager.

在自訂例外狀況中,視需要提供額外的屬性In custom exceptions, provide additional properties as needed

只有在其他資訊對某個程式設計案例實用時,才為例外狀況提供其他屬性 (以及自訂訊息字串)。Provide additional properties for an exception (in addition to the custom message string) only when there's a programmatic scenario where the additional information is useful. 例如,FileNotFoundException 提供 FileName 屬性。For example, the FileNotFoundException provides the FileName property.

放置 throw 陳述式讓堆疊追蹤更有用Place throw statements so that the stack trace will be helpful

堆疊追蹤會從擲回例外狀況所在的陳述式開始,並在攔截例外狀況的 catch 陳述式結束。The stack trace begins at the statement where the exception is thrown and ends at the catch statement that catches the exception.

使用例外狀況產生器方法Use exception builder methods

類別在它的實作中從不同的地方擲回相同的例外狀況是很常見的。It is common for a class to throw the same exception from different places in its implementation. 若要避免過多的程式碼,請使用 Helper 方法,以建立例外狀況並將它傳回。To avoid excessive code, use helper methods that create the exception and return it. 例如: For example:

ref class FileReader
{
private:
    String^ fileName;

public:
    FileReader(String^ path)
    {
        fileName = path;
    }

    array<Byte>^ Read(int bytes)
    {
        array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
        if (results == nullptr)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException^ NewFileIOException()
    {
        String^ description = "My NewFileIOException Description";

        return gcnew FileReaderException(description);
    }
};
class FileReader
{
    private string fileName;

    public FileReader(string path)
    {
        fileName = path;
    }

    public byte[] Read(int bytes)
    {
        byte[] results = FileUtils.ReadFromFile(fileName, bytes);
        if (results == null)
        {
            throw NewFileIOException();
        }
        return results;
    }

    FileReaderException NewFileIOException()
    {
        string description = "My NewFileIOException Description";

        return new FileReaderException(description);
    }
}
Class FileReader
    Private fileName As String

    
    Public Sub New(path As String)
        fileName = path
    End Sub

    Public Function Read(bytes As Integer) As Byte()
        Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
        If results Is Nothing
            Throw NewFileIOException()
        End If
        Return results
    End Function

    Function NewFileIOException() As FileReaderException
        Dim description As String = "My NewFileIOException Description"

        Return New FileReaderException(description)
    End Function
End Class

在某些情況下,使用例外狀況的建構函式來建置例外狀況會更適當。In some cases, it's more appropriate to use the exception's constructor to build the exception. 範例為全域例外狀況類別 ArgumentExceptionAn example is a global exception class such as ArgumentException.

在擲回例外狀況時清除中繼結果Clean up intermediate results when throwing an exception

呼叫端應該能夠假設,從方法擲回例外狀況時不會產生副作用。Callers should be able to assume that there are no side effects when an exception is thrown from a method. 例如,如果您有用來轉帳的程式碼,會從某個帳戶提款再存入另一個帳戶,而且在執行存款時擲回例外狀況,則您不想要讓提款仍有效。For example, if you have code that transfers money by withdrawing from one account and depositing in another account, and an exception is thrown while executing the deposit, you don't want the withdrawal to remain in effect.

public void TransferFunds(Account from, Account to, decimal amount)
{
    from.Withdrawal(amount);
    // If the deposit fails, the withdrawal shouldn't remain in effect. 
    to.Deposit(amount);
}

處理這種情況的一個方式是攔截存款交易所擲回的任何例外狀況,並復原提款。One way to handle this situation is to catch any exceptions thrown by the deposit transaction and roll back the withdrawal.

private static void TransferFunds(Account from, Account to, decimal amount)
{
    string withdrawalTrxID = from.Withdrawal(amount);
    try
    {
        to.Deposit(amount);
    }
    catch
    {
        from.RollbackTransaction(withdrawalTrxID);
        throw;
    }
}

此範例說明如何使用 throw 重新擲回原始的例外狀況,以方便呼叫端無須檢視 InnerException屬性,就能輕鬆查看問題的實際原因。This example illustrates the use of throw to re-throw the original exception, which can make it easier for callers to see the real cause of the problem without having to examine the InnerException property. 另一種做法是擲回新的例外狀況,並包含原始例外狀況作為內部例外狀況:An alternative is to throw a new exception and include the original exception as the inner exception:

catch (Exception ex)
{
    from.RollbackTransaction(withdrawalTrxID);
    throw new Exception("Withdrawal failed", ex);
}

請參閱See Also

例外狀況Exceptions