web-dev-qa-db-fra.com

C #: Lancer les meilleures pratiques d'exception personnalisées

J'ai lu quelques-unes des autres questions concernant les pratiques de gestion des exceptions C #, mais aucune ne semble me demander ce que je recherche.

Si j'implémente ma propre exception personnalisée pour une classe ou un ensemble de classes particulier. Toutes les erreurs liées à ces classes doivent-elles être encapsulées dans mon exception à l'aide de l'exception interne ou dois-je les laisser passer?

Je pensais qu'il serait préférable d'attraper toutes les exceptions afin que l'exception puisse être immédiatement reconnue à partir de ma source. Je passe toujours l'exception d'origine comme exception intérieure. D'un autre côté, je pensais qu'il serait superflu de renverser l'exception.

Exception:

class FooException : Exception
{
    //...
}

Option 1: Foo encapsule toutes les exceptions:

class Foo
{
    DoSomething(int param)
    {
        try 
        {
             if (/*Something Bad*/)
             {  
                 //violates business logic etc... 
                 throw new FooException("Reason...");
             }
             //... 
             //something that might throw an exception
        }
        catch (FooException ex)
        {
             throw;
        }
        catch (Exception ex)
        {
             throw new FooException("Inner Exception", ex);
        }
    }
}

Option 2: Foo lève des FooExceptions spécifiques mais permet à d'autres exceptions de passer:

class Foo
{
    DoSomething(int param)
    {
        if  (/*Something Bad*/)
        {
             //violates business logic etc... 
             throw new FooException("Reason...");
        }
        //... 
        //something that might throw an exception and not caught
    }
}
47
user295190

Sur la base de mon expérience avec les bibliothèques, vous devriez envelopper tout (que vous pouvez prévoir) dans un FooException pour plusieurs raisons:

  1. Les gens savent que cela vient de vos cours, ou du moins de leur utilisation. S'ils voient FileNotFoundException ils peuvent le chercher partout. Vous les aidez à les réduire. (Je me rends compte maintenant que la trace de la pile sert à cet effet, alors vous pouvez peut-être ignorer ce point.)

  2. Vous pouvez fournir plus de contexte. En enveloppant un FNF avec votre propre exception, vous pouvez dire "J'essayais de charger ce fichier à cet effet, et je ne l'ai pas trouvé. Cela suggère des solutions correctes possibles.

  3. Votre bibliothèque peut gérer correctement le nettoyage. Si vous laissez la bulle d'exception, vous forcez l'utilisateur à nettoyer. Si vous avez correctement encapsulé ce que vous faisiez, alors ils n'ont aucune idée de comment gérer la situation!

N'oubliez pas d'envelopper uniquement les exceptions que vous pouvez anticiper, comme FileNotFound. Ne vous contentez pas d'envelopper Exception et espérez le meilleur.

54
Tesserex

Jetez un œil à cela MSDN-best-practices .

Pensez à utiliser throw au lieu de throw ex si vous souhaitez relancer les exceptions interceptées, car de cette façon, la trace de pile d'origine est conservée (numéros de ligne, etc.).

18
Tim Schmelter

J'ajoute toujours quelques propriétés lors de la création d'une exception personnalisée. L'un est le nom d'utilisateur ou l'ID. J'ajoute une propriété DisplayMessage pour transporter le texte à afficher à l'utilisateur. Ensuite, j'utilise la propriété Message pour transmettre des détails techniques à enregistrer dans le journal.

J'attrape chaque erreur dans la couche d'accès aux données à un niveau où je peux toujours capturer le nom de la procédure stockée et les valeurs des paramètres passés. Ou le SQL en ligne. Peut-être le nom de la base de données ou une chaîne de connexion partielle (pas d'informations d'identification, s'il vous plaît). Ceux-ci peuvent aller dans Message ou dans leur propre nouvelle propriété DatabaseInfo personnalisée.

Pour les pages Web, j'utilise la même exception personnalisée. Je mettrai dans la propriété Message les informations du formulaire - ce que l'utilisateur a entré dans chaque contrôle de saisie de données sur la page Web, l'ID de l'élément en cours de modification (client, produit, employé, peu importe) et l'action que l'utilisateur prenait quand l'exception s'est produite.

Donc, ma stratégie selon votre question est: attraper seulement quand je peux faire quelque chose au sujet de l'exception. Et bien souvent, tout ce que je peux faire est d'enregistrer les détails. Donc, je n'attrape qu'au point où ces détails sont disponibles, puis je rejette pour laisser l'exception bouillonner jusqu'à l'interface utilisateur. Et je conserve l'exception d'origine dans mon exception personnalisée.

6
DOK

Le but des exceptions personnalisées est de fournir des informations contextuelles détaillées à la trace de pile pour faciliter le débogage. L'option 1 est meilleure car sans elle, vous n'obtenez pas l '"origine" de l'exception si elle s'est produite "plus bas" dans la pile.

3
Dave Swersky

si vous exécutez l'extrait de code pour "Exception" dans Visual Studio, vous disposez d'un modèle d'écriture d'exception de bonne pratique.

1
Felice Pollano

Remarque Option 1: votre throw new FooException("Reason..."); ne sera pas interceptée car elle se trouve en dehors du bloc try/catch

  1. Vous ne devez intercepter que les exceptions que vous souhaitez traiter.
  2. Si vous n'ajoutez aucune donnée supplémentaire à l'exception, utilisez throw; car cela ne tuera pas votre pile. Dans l'option 2, vous pouvez toujours effectuer un traitement dans catch et simplement appeler throw; pour renvoyer l'exception d'origine avec la pile d'origine.
1
Jakub Konecki

La chose la plus importante que le code doit savoir lors de la capture d'une exception, qui est malheureusement complètement absente de l'objet Exception, est l'état du système par rapport à ce qu'il "devrait" être (probablement l'exception a été levée parce qu'il y avait quelque chose de mal). Si une erreur se produit dans une méthode LoadDocument, le document ne s'est probablement pas chargé avec succès, mais il existe au moins deux états système possibles:

  1. L'état du système peut être comme si la charge n'avait jamais été tentée. Dans ce cas, il serait tout à fait approprié que l'application continue si elle peut le faire sans le document chargé.
  2. L'état du système peut être suffisamment corrompu pour que la meilleure solution consiste à enregistrer ce qui peut être enregistré dans des fichiers de "récupération" (éviter de remplacer les bons fichiers de l'utilisateur par des données éventuellement corrompues) et à arrêter.

Évidemment, il y aura souvent d'autres états possibles entre ces extrêmes. Je suggérerais que l'on s'efforce d'avoir une exception personnalisée qui indique explicitement que l'état # 1 existe, et éventuellement un pour # 2 si des circonstances prévisibles mais inévitables peuvent le provoquer. Toutes les exceptions qui se produisent et entraîneront l'état n ° 1 doivent être enveloppées dans un objet d'exception indiquant l'état n ° 1. Si des exceptions peuvent se produire de telle manière que l'état du système puisse être compromis, elles doivent être soit encapsulées en tant que # 2, soit autorisées à percoler.

1
supercat

L'option 2 est la meilleure. Je crois que la meilleure pratique consiste à intercepter uniquement les exceptions lorsque vous prévoyez de faire quelque chose avec l'exception.

Dans ce cas, l'option 1 encapsule simplement une exception avec votre propre exception. Il n'ajoute aucune valeur et les utilisateurs de votre classe ne peuvent plus simplement intercepter ArgumentException, par exemple, ils doivent également intercepter votre FooException puis effectuer une analyse syntaxique sur l'exception interne. Si l'exception interne n'est pas une exception, ils sont capables de faire quelque chose d'utile avec lesquels ils devront recommencer.

0
btlog