例外の推奨事項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. たとえば、既に終了している接続を終了しようとすると、InvalidOperationException を受け取ります。For 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

例外が返されるのを回避するもう 1 つの方法は、非常に一般的なエラーの場合に、例外をスローする代わりに 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

カスタム例外クラスに 3 つのコンストラクターを含めるInclude three constructors in custom exception classes

独自の例外クラスを作成するときに、少なくとも 3 つの共通コンストラクターを使用します。それらは、既定のコンストラクター、文字列メッセージを受け取るコンストラクター、および文字列メッセージと内部例外を受け取るコンストラクターです。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 は、例外を検出できなくなり、共通言語ランタイムが 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:

  • 2 つのアプリ ドメインが共有する共通アプリケーション ベースにアセンブリを配置する。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. 通常は、メッセージ文字列を例外コンストラクターmessage 引数に渡すことで、Exception.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.ResourceManagerに関するページを参照してください。For 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. コードが長くなることを防ぐため、例外を作成して返すヘルパー メソッドを使用します。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. ArgumentException などのグローバル例外クラスはその一例です。An 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);
}

この状況に対処する方法の 1 つは、預金トランザクションによってスローされた例外をキャッチし、引き出しをロールバックすることです。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 TransferFundsException("Withdrawal failed", innerException: ex)
    {
        From = from,
    To = to,
    Amount = amount
    };
}

関連項目See also