Share via


Meilleures pratiques pour la gestion des exceptions

Un jeu de blocs de code de gestion d'erreurs correctement conçu peut rendre un programme plus robuste et moins enclin aux blocages dus à la gestion de ces erreurs par l'application. La liste suivante contient des suggestions relatives aux meilleures pratiques pour la gestion des exceptions :

  • Déterminez quand vous devez définir un bloc try/catch. Par exemple, vous pouvez vérifier par programmation la présence d'une condition qui risque probablement de se produire sans le recours à la gestion des exceptions. Dans d'autres situations, l'utilisation de la gestion des exceptions pour intercepter un cas d'erreur est appropriée.

    L'exemple suivant utilise une instruction if pour déterminer si une connexion est fermée. Vous pouvez utiliser cette méthode au lieu de lever une exception si la connexion n'est pas fermée.

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

Dans l'exemple suivant, une exception est levée si la connexion n'est pas fermée.

Try
    conn.Close()
Catch ex As InvalidOperationException
    Console.WriteLine(ex.GetType().FullName)
    Console.WriteLine(ex.Message)
End Try
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);
}

La méthode que vous choisissez dépend de la fréquence à laquelle l'événement se produit. Si l'événement est véritablement exceptionnel et constitue une erreur (par exemple une fin de fichier inattendue), l'utilisation de la gestion des exceptions est plus indiquée car la quantité de code exécutée en situation normale est moindre. Si l'événement se produit régulièrement, l'utilisation de la méthode par programmation pour rechercher les erreurs est plus appropriée. En ce cas, si une exception se produit, la gestion de l'exception prendra plus de temps.

  • Utilisez des blocs try/finally autour du code pouvant potentiellement générer une exception et centralisez vos instructions catch en un point unique. De cette façon, l'instruction try génère l'exception, l'instruction finally ferme ou libère des ressources et l'instruction catch gère l'exception à partir d'un point central.

  • Veillez à toujours classer les exceptions dans les blocs catch de la plus spécifique à la moins spécifique. Cette technique permet de gérer l'exception spécifique avant qu'elle ne passe à un bloc catch plus général.

  • Terminez les noms de classes d'exception par le mot « Exception ». Par exemple :

Public Class MyFileNotFoundException
    Inherits Exception
End Class
public class MyFileNotFoundException : Exception
{
}
public ref class MyFileNotFoundException : public Exception
{
};
  • Lors de la création d'exceptions définies par l'utilisateur, vous devez vous assurer que les métadonnées des exceptions sont disponibles pour le code s'exécutant à distance, y compris lorsque des exceptions se produisent à travers des domaines d'application. Par exemple, supposons que le domaine d'application A crée le domaine d'application B, lequel exécute le code qui lève une exception. Pour que le domaine d'application A intercepte et gère correctement l'exception, il doit pouvoir trouver l'assembly contenant 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, ce dernier ne peut pas trouver l'exception et le Common Language Runtime lève une exception FileNotFoundException. Pour éviter cette situation, vous pouvez déployer l'assembly contenant les informations sur les exceptions de deux façons :

    • placez l'assembly dans une base d'application commune partagée par les deux domaines d'application

      - ou -

    • si les domaines ne partagent pas une base d'application commune, signez l'assembly contenant les informations sur les exceptions à l'aide d'un nom fort et déployez l'assembly dans le Global Assembly Cache.

  • En C# et C++, utilisez au moins les trois constructeurs communs lors de la création de vos propres classes d'exception. Pour obtenir un exemple, consultez Comment : créer des exceptions définies par l'utilisateur.

  • Dans la plupart des cas, utilisez les types d'exceptions prédéfinis. Définissez de nouveaux types d'exceptions pour des scénarios par programmation. Introduisez une nouvelle classe d'exceptions afin de permettre au programmeur d'exécuter une action différente basée sur la classe d'exceptions.

  • Pour la plupart des applications, dérivez des exceptions personnalisées à partir de la classe Exception. À l'origine, il était prévu que les exceptions personnalisées devraient dériver de la classe ApplicationException ; toutefois, en pratique, il s'est avéré que cette méthode n'apportait pas beaucoup d'avantages.

  • Insérez une chaîne de description localisée dans chaque exception. Quand l'utilisateur voit un message d'erreur, celui-ci est dérivé non pas de la classe d'exceptions mais de la chaîne de description de l'exception qui a été levée.

  • Créez des messages d'erreur corrects du point de vue de la syntaxe, y compris la ponctuation de fin. Chaque phrase de la chaîne de description d'une exception doit se terminer par un point.

  • Fournissez des propriétés Exception pour l'accès par programmation. Incluez des informations supplémentaires (autres que la chaîne de description) dans une exception uniquement s'il existe un scénario par programmation dans lequel les informations supplémentaires sont utiles.

  • Retournez null pour les cas d'erreur très répandus. Par exemple, une commande Open retourne null si le fichier est introuvable, mais lève une exception si le fichier est verrouillé.

  • Créez des classes de façon qu'une exception ne soit jamais levée en usage normal. Vous pouvez, par exemple, utiliser une classe FileStream pour déterminer si la fin d'un fichier a été atteinte. Vous évitez ainsi l'exception qui est levée si vous dépassez la fin du fichier pendant la lecture. L'exemple suivant montre comment lire le fichier jusqu'à la fin.

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
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:
    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.
        }
    }
};

Levez InvalidOperationException si un appel de jeu de propriétés ou de méthode n'est pas approprié étant donné l'état actuel de l'objet.

Levez ArgumentException ou une classe dérivée de ArgumentException si des paramètres incorrects sont passés.

La trace de la pile commence à l'instruction où l'exception est levée et se termine à l'instruction catch qui intercepte l'exception. N'oubliez pas ce fait au moment de déterminer l'endroit auquel vous devez placer une instruction throw.

Utilisez des méthodes de générateur d'exceptions. Il est fréquent qu'une classe lève la même exception à partir de différents endroits de son implémentation. Pour éviter un excès de code, utilisez des méthodes d'assistance qui créent une exception et la retournent. Par exemple :

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
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);
    }
}
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);
    }
};

Dans les autres cas, utilisez le constructeur d'exception pour générer l'exception. Cette approche est plus appropriée pour les classes d'exceptions globales, telles que ArgumentException.

  • Levez des exceptions au lieu de retourner un code d'erreur ou HRESULT.

  • Supprimez les résultats intermédiaires lorsque vous levez une exception. Les appelants doivent supposer qu'il n'y a aucun effet secondaire quand une exception est levée à partir d'une méthode.

Voir aussi

Concepts

Gestion et levée des exceptions