Bonnes pratiques pour les exceptionsBest practices for exceptions

Une application bien conçue gère les exceptions et les erreurs pour empêcher les incidents d'applications.A well-designed app handles exceptions and errors to prevent app crashes. Cette section présente les bonnes pratiques pour la gestion et la création des exceptions.This section describes best practices for handling and creating exceptions.

Utiliser des blocs try/catch/finally pour procéder à une récupération après des erreurs ou libérer des ressourcesUse try/catch/finally blocks to recover from errors or release resources

Utilisez des blocs try/catch autour du code susceptible de générer une exception et votre code peut récupérer suite à cette exception.Use try/catch blocks around code that can potentially generate an exception and your code can recover from that exception. Dans les blocs catch, veillez à toujours classer les exceptions de la plus dérivée à la moins dérivée.In catch blocks, always order exceptions from the most derived to the least derived. Toutes les exceptions dérivent de Exception.All exceptions derive from Exception. Les exceptions les plus dérivées ne sont pas gérées par une clause catch qui est précédée d’une clause catch pour une classe d’exception de base.More derived exceptions are not handled by a catch clause that is preceded by a catch clause for a base exception class. Quand votre code ne peut pas récupérer suite à une exception, n’interceptez pas cette exception.When your code cannot recover from an exception, don't catch that exception. Activez des méthodes un peu plus haut dans la pile d’appels pour récupérer si possible.Enable methods further up the call stack to recover if possible.

Nettoyez les ressources allouées avec des instructions using ou des blocs finally.Clean up resources allocated with either using statements, or finally blocks. Préférez les instructions using pour nettoyer automatiquement les ressources quand des exceptions sont levées.Prefer using statements to automatically clean up resources when exceptions are thrown. Utilisez des blocs finally pour nettoyer les ressources qui n’implémentent pas IDisposable.Use finally blocks to clean up resources that don't implement IDisposable. Le code dans une clause finally est presque toujours exécuté même lorsque des exceptions sont levées.Code in a finally clause is almost always executed even when exceptions are thrown.

Gérer les conditions courantes sans lever d’exceptionHandle common conditions without throwing exceptions

En ce qui concerne les conditions susceptibles de se produire, mais pouvant déclencher une exception, gérez-les de façon à éviter l’exception.For conditions that are likely to occur but might trigger an exception, consider handling them in a way that will avoid the exception. Par exemple, si vous essayez de fermer une connexion déjà fermée, vous obtenez une exception InvalidOperationException.For example, if you try to close a connection that is already closed, you'll get an InvalidOperationException. Vous pouvez l’éviter avec une instruction if qui permet de vérifier l’état de la connexion avant d’essayer de la fermer.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

Si vous ne vérifiez pas l’état de la connexion avant de la fermer, vous pouvez intercepter l’exception 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

Le choix de la méthode dépend de la fréquence à laquelle l’événement doit se produire.The method to choose depends on how often you expect the event to occur.

  • Utilisez la gestion des exceptions si l'événement ne se produit pas très souvent, c'est-à-dire, si l'événement est véritablement exceptionnel et indique une erreur (telle qu'une fin de fichier inattendue).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). Lorsque vous utilisez la gestion des exceptions, la quantité de code exécutée en situation normale est moindre.When you use exception handling, less code is executed in normal conditions.

  • Recherchez les conditions d’erreur dans le code si l’événement se produit régulièrement et peut être considéré comme faisant partie de l’exécution normale.Check for error conditions in code if the event happens routinely and could be considered part of normal execution. Quand vous recherchez les conditions d’erreur courantes, vous exécutez moins de code, car vous évitez les exceptions.When you check for common error conditions, less code is executed because you avoid exceptions.

Concevoir des classes pour éviter les exceptionsDesign classes so that exceptions can be avoided

Une classe peut fournir des méthodes ou propriétés qui vous permettent d’éviter d’effectuer un appel susceptible de déclencher une exception.A class can provide methods or properties that enable you to avoid making a call that would trigger an exception. Par exemple, une classe FileStream fournit des méthodes qui permettent de déterminer si la fin du fichier a été atteinte.For example, a FileStream class provides methods that help determine whether the end of the file has been reached. Ces méthodes peuvent servir à éviter l’exception qui est levée si vous dépassez la fin du fichier pendant la lecture.These can be used to avoid the exception that is thrown if you read past the end of the file. L’exemple suivant montre comment lire un fichier jusqu’à la fin sans lever d’exception.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 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

Un autre moyen d’éviter les exceptions est de retourner Null (ou une valeur par défaut) pour les cas d’erreur très répandus au lieu de lever une exception.Another way to avoid exceptions is to return null (or default) for extremely common error cases instead of throwing an exception. Un cas d'erreur très répandu peut être considéré comme un flux de contrôle normal.An extremely common error case can be considered normal flow of control. En retournant null (ou une valeur par défaut) dans ces cas-là, vous réduisez l'impact sur les performances d'une application.By returning null (or default) in these cases, you minimize the performance impact to an app.

Pour les types valeur, s’il faut utiliser Nullable<T> ou une valeur par défaut comme indicateur d’erreur est quelque chose à prendre en compte pour votre application particulière.For value types, whether to use Nullable<T> or default as your error indicator is something to consider for your particular app. À l’aide de Nullable<Guid>, default devient null au lieu de Guid.Empty.By using Nullable<Guid>, default becomes null instead of Guid.Empty. Parfois, l’ajout de Nullable<T> peut éclaircir les choses, lorsqu’une valeur est présente ou absente.Some times, adding Nullable<T> can make it clearer when a value is present or absent. Autres fois, l’ajout de Nullable<T> peut créer des cas supplémentaires qui ne sont pas nécessaires et uniquement servir pour créer les sources potentielles d’erreurs.Other times, adding Nullable<T> can create extra cases to check that aren't necessary, and only serve to create potential sources of errors.

Lever des exceptions au lieu de retourner un code d’erreurThrow exceptions instead of returning an error code

Les exceptions font en sorte que les échecs ne passent pas inaperçus parce que l’appel du code n’a pas vérifié le code de retour.Exceptions ensure that failures do not go unnoticed because calling code didn't check a return code.

Utiliser les types d’exception .NET prédéfinisUse the predefined .NET exception types

N'introduisez une nouvelle classe d'exception que quand aucune classe d'exception prédéfinie ne s'applique.Introduce a new exception class only when a predefined one doesn't apply. Par exemple :For example:

Terminer les noms de classes d’exception par le mot ExceptionEnd exception class names with the word Exception

Quand une exception personnalisée est nécessaire, nommez-la de manière appropriée et dérivez-la de la classe Exception.When a custom exception is necessary, name it appropriately and derive it from the Exception class. Par exemple :For example:

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

Inclure trois constructeurs dans des classes d’exception personnaliséesInclude three constructors in custom exception classes

Utilisez au moins les trois constructeurs communs pendant la création de vos propres classes d’exception : le constructeur sans paramètre, un constructeur qui prend un message de type chaîne et un constructeur qui prend un message de type chaîne et une exception interne.Use at least the three common constructors when creating your own exception classes: the parameterless constructor, a constructor that takes a string message, and a constructor that takes a string message and an inner exception.

Pour obtenir un exemple, consultez Guide pratique : créer des exceptions définies par l’utilisateur.For an example, see How to: Create User-Defined Exceptions.

Vérifier que les données d’exception sont disponibles quand le code s’exécute à distanceEnsure that exception data is available when code executes remotely

Quand vous créez des exceptions définies par l’utilisateur, vous devez vérifier que les métadonnées des exceptions sont disponibles pour le code qui s’exécute à distance.When you create user-defined exceptions, ensure that the metadata for the exceptions is available to code that is executing remotely.

Par exemple, sur les implémentations .NET qui prennent en charge des domaines d’application, des exceptions peuvent se produire entre domaines d’application.For example, on .NET implementations that support App Domains, exceptions may occur across App domains. Supposons que le domaine d’application A crée le domaine d’application B, lequel exécute du code qui lève une exception.Suppose App Domain A creates App Domain B, which executes code that throws an exception. Pour que le domaine d’application A intercepte et gère l’exception correctement, il doit pouvoir trouver l’assembly qui contient l’exception levée par le domaine d’application B. Si le domaine d’application B lève une exception qui est contenue dans un assembly sous sa base d’application, mais pas sous la base d’application du domaine d’application A, le domaine d’application A ne peut pas trouver l’exception et le Common Language Runtime lève une exception 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. Pour éviter cette situation, vous pouvez déployer l'assembly qui contient les informations sur les exceptions de deux façons :To avoid this situation, you can deploy the assembly that contains the exception information in two ways:

  • Placez l'assembly dans une base d'application commune partagée par les deux domaines d'application.Put the assembly into a common application base shared by both app domains.

    - ou -- or -

  • Si les domaines ne partagent pas une base d'application commune, signez l'assembly qui contient les informations sur les exceptions à l'aide d'un nom fort et déployez l'assembly dans le Global Assembly Cache.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.

Utiliser des messages d’erreur grammaticalement correctsUse grammatically correct error messages

Écrivez des phrases claires et insérez une ponctuation finale.Write clear sentences and include ending punctuation. Chaque phrase de la chaîne affectée à la propriété Exception.Message doit se terminer par un point.Each sentence in the string assigned to the Exception.Message property should end in a period. Par exemple, « La table du journal a débordé. »For example, "The log table has overflowed." est une chaîne de message appropriée.would be an appropriate message string.

Inclure une chaîne de message localisée dans chaque exceptionInclude a localized string message in every exception

Le message d’erreur que l’utilisateur voit est dérivé de la propriété Exception.Message de l’exception qui a été levée, et non pas du nom de la classe d’exception.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. En règle générale, vous affectez une valeur à la propriété Exception.Message en passant la chaîne de message à l’argument message d’un constructeur d’exception.Typically, you assign a value to the Exception.Message property by passing the message string to the message argument of an Exception constructor.

Pour les applications localisées, vous devez fournir une chaîne de message localisée pour chaque exception que votre application peut lever.For localized applications, you should provide a localized message string for every exception that your application can throw. Vous utilisez des fichiers de ressources pour fournir les messages d’erreur localisés.You use resource files to provide localized error messages. Pour plus d’informations sur la localisation d’applications et la récupération de chaînes localisées, consultez les articles suivants :For information on localizing applications and retrieving localized strings, see the following articles:

Dans les exceptions personnalisées, fournir des propriétés supplémentaires si nécessaireIn custom exceptions, provide additional properties as needed

Spécifiez des propriétés supplémentaires (en plus de la chaîne de message personnalisée) pour une exception seulement dans le cas d’un scénario du programme où les informations supplémentaires sont utiles.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. Par exemple, la classe FileNotFoundException fournit la propriété FileName.For example, the FileNotFoundException provides the FileName property.

Placer des instructions throw pour que la trace de la pile soit utilePlace throw statements so that the stack trace will be helpful

La trace de la pile commence à l'instruction où l'exception est levée et se termine à l'instruction catch qui intercepte l'exception.The stack trace begins at the statement where the exception is thrown and ends at the catch statement that catches the exception.

Utiliser des méthodes de générateur d’exceptionsUse exception builder methods

Il est fréquent qu'une classe lève la même exception à partir de différents endroits de son implémentation.It is common for a class to throw the same exception from different places in its implementation. Pour éviter un excès de code, utilisez des méthodes d'assistance qui créent une exception et la retournent.To avoid excessive code, use helper methods that create the exception and return it. Par exemple :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

Dans certains cas, il est plus approprié d’utiliser le constructeur de l’exception pour générer l’exception.In some cases, it's more appropriate to use the exception's constructor to build the exception. Par exemple, une classe d’exception globale comme ArgumentException.An example is a global exception class such as ArgumentException.

Restaurer l’état quand les méthodes ne sont pas exécutées en raison d’exceptionsRestore state when methods don't complete due to exceptions

Les appelants doivent supposer qu'il n'y a aucun effet secondaire quand une exception est levée à partir d'une méthode.Callers should be able to assume that there are no side effects when an exception is thrown from a method. Par exemple, si vous avez du code qui transfère de l’argent en le retirant d’un compte pour le déposer dans un autre, et qu’une exception est levée pendant l’exécution du transfert, vous ne voulez pas que le retrait reste en vigueur.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);
}
Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
    from.Withdrawal(amount)
    ' If the deposit fails, the withdrawal shouldn't remain in effect.
    [to].Deposit(amount)
End Sub

La méthode ci-dessus ne lève pas directement d’exceptions, mais doit être écrite avec précaution afin d’inverser le retrait si l’opération de dépôt échoue.The method above does not directly throw any exceptions, but must be written defensively so that if the deposit operation fails, the withdrawal is reversed.

Pour gérer cette situation, vous pouvez intercepter toutes les exceptions levées par la transaction de dépôt et annuler le retrait.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;
    }
}
Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
    Dim withdrawalTrxID As String = from.Withdrawal(amount)
    Try
        [to].Deposit(amount)
    Catch
        from.RollbackTransaction(withdrawalTrxID)
        Throw
    End Try
End Sub

Cet exemple illustre l’utilisation de throw pour lever de nouveau l’exception d’origine, ce qui peut permettre aux appelants de voir plus facilement la véritable cause du problème sans avoir à examiner la propriété 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. Vous pouvez aussi lever une nouvelle exception et inclure l’exception d’origine comme exception interne :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
    };
}
Catch ex As Exception
    from.RollbackTransaction(withdrawalTrxID)
    Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
    {
        .From = from,
        .[To] = [to],
        .Amount = amount
    }
End Try

Voir aussiSee also