Gestion des exceptions:Approche contractuelle vs approche exceptionnelle

StackOverflow https://stackoverflow.com/questions/26383

  •  09-06-2019
  •  | 
  •  

Question

Je connais deux approches de la gestion des exceptions, examinons-les.

  1. Approche contractuelle.

    Lorsqu’une méthode ne fait pas ce qu’elle dit dans l’en-tête de la méthode, elle lèvera une exception.Ainsi, la méthode « promet » qu’elle effectuera l’opération, et si elle échoue pour une raison quelconque, elle lèvera une exception.

  2. Approche exceptionnelle.

    Ne lancez des exceptions que lorsque quelque chose de vraiment bizarre se produit.Vous ne devez pas utiliser d'exceptions lorsque vous pouvez résoudre la situation avec un flux de contrôle normal (instructions If).Vous n'utilisez pas d'exceptions pour le flux de contrôle, comme vous le feriez dans l'approche contractuelle.

Utilisons les deux approches dans différents cas :

Nous avons une classe Customer qui a une méthode appelée OrderProduct.

approche contractuelle :

class Customer
{
     public void OrderProduct(Product product)
     {
           if((m_credit - product.Price) < 0)
                  throw new NoCreditException("Not enough credit!");
           // do stuff 
     }
}

approche exceptionnelle :

class Customer
{
     public bool OrderProduct(Product product)
     {
          if((m_credit - product.Price) < 0)
                   return false;
          // do stuff
          return true;
     }
}

if !(customer.OrderProduct(product))
            Console.WriteLine("Not enough credit!");
else
   // go on with your life

Ici, je préfère l'approche exceptionnelle, car il n'est pas vraiment exceptionnel qu'un client n'ait pas d'argent en supposant qu'il n'a pas gagné à la loterie.

Mais voici une situation dans laquelle je me trompe sur le style du contrat.

Exceptionnel:

class CarController
{
     // returns null if car creation failed.
     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         return null;
     }
 }

Lorsque j'appelle une méthode appelée CreateCar, je m'attends à une instance de Car au lieu d'un mauvais pointeur nul, qui peut ravager mon code en cours d'exécution une douzaine de lignes plus tard.Je préfère donc le contrat à celui-ci :

class CarController
{

     public Car CreateCar(string model)
     {
         // something went wrong, wrong model
         throw new CarModelNotKnownException("Model unkown");

         return new Car();
     }
 }

Quel style utilisez-vous ?Selon vous, quelle est la meilleure approche générale des exceptions ?

Était-ce utile?

La solution

Je privilégie ce que vous appelez l'approche « contractuelle ».Le renvoi de valeurs nulles ou d'autres valeurs spéciales pour indiquer des erreurs n'est pas nécessaire dans un langage qui prend en charge les exceptions.Je trouve beaucoup plus facile de comprendre le code lorsqu'il ne contient pas un tas de clauses "if (result == NULL)" ou "if (result == -1)" mélangées à ce qui pourrait être une logique très simple et directe.

Autres conseils

Mon approche habituelle consiste à utiliser le contrat pour gérer tout type d'erreur due à l'invocation du "client", c'est-à-dire due à une erreur externe (c'est-à-dire ArgumentNullException).

Chaque erreur sur les arguments n'est pas gérée.Une exception est levée et le « client » est chargé de la gérer.D'un autre côté, pour les erreurs internes, essayez toujours de les corriger (comme si vous ne parveniez pas à obtenir une connexion à la base de données pour une raison quelconque) et seulement si vous ne pouvez pas les gérer, relancez l'exception.

Il est important de garder à l'esprit que la plupart des exceptions non gérées à un tel niveau ne pourront de toute façon pas être gérées par le client, elles iront donc probablement vers le gestionnaire d'exceptions le plus général, donc si une telle exception se produit, vous êtes probablement FUBAR de toute façon.

Je crois que si vous créez une classe qui sera utilisée par un programme externe (ou qui sera réutilisée par d'autres programmes), vous devez alors utiliser l'approche contractuelle.Un bon exemple de ceci est une API de toute sorte.

Si vous êtes réellement intéressé par les exceptions et souhaitez réfléchir à la manière de les utiliser pour construire des systèmes robustes, pensez à lire Rendre des systèmes distribués fiables en présence d'erreurs logicielles.

Les deux approches sont correctes.Cela signifie qu'un contrat doit être rédigé de manière à spécifier pour tous les cas qui ne sont pas vraiment exceptionnels un comportement qui ne nécessite pas de lever d'exception.

Notez que certaines situations peuvent être exceptionnelles ou non en fonction de ce à quoi s'attend l'appelant du code.Si l'appelant s'attend à ce qu'un dictionnaire contienne un certain élément et que l'absence de cet élément indiquerait un problème grave, alors l'incapacité à trouver l'élément est une condition exceptionnelle et devrait entraîner la levée d'une exception.Toutefois, si l'appelant ne sait pas vraiment si un élément existe et est également prêt à gérer sa présence ou son absence, alors l'absence de l'élément serait une condition attendue et ne devrait pas provoquer d'exception.La meilleure façon de gérer de telles variations dans les attentes de l'appelant est de faire en sorte qu'un contrat spécifie deux méthodes :une méthode DoSomething et une méthode TryDoSomething, par ex.

TValue GetValue(TKey Key);
bool TryGetValue(TKey Key, ref TValue value);

Notez que, bien que le modèle standard « essayer » soit celui illustré ci-dessus, certaines alternatives peuvent également être utiles si l'on conçoit une interface qui produit des éléments :

 // In case of failure, set ok false and return default<TValue>.
TValue TryGetResult(ref bool ok, TParam param);
// In case of failure, indicate particular problem in GetKeyErrorInfo
// and return default<TValue>.
TValue TryGetResult(ref GetKeyErrorInfo errorInfo, ref TParam param);

Notez que l'utilisation de quelque chose comme le modèle TryGetResult normal dans une interface rendra l'interface invariante par rapport au type de résultat ;l'utilisation de l'un des modèles ci-dessus permettra à l'interface d'être covariante par rapport au type de résultat.De plus, cela permettra d'utiliser le résultat dans une déclaration 'var' :

  var myThingResult = myThing.TryGetSomeValue(ref ok, whatever);
  if (ok) { do_whatever }

Ce n’est pas tout à fait l’approche standard, mais dans certains cas, les avantages peuvent la justifier.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top