97 votes

Lequel, et pourquoi, préférez-vous les exceptions ou les codes de retour ?

Ma question est de savoir ce que la plupart des développeurs préfèrent pour la gestion des erreurs, les exceptions ou les codes de retour d'erreur. Merci de préciser le langage (ou la famille de langage) et d'expliquer pourquoi vous préférez l'un plutôt que l'autre.

Je pose cette question par curiosité. Personnellement, je préfère les codes de retour d'erreur car ils sont moins explosifs et n'obligent pas le code utilisateur à payer la pénalité de performance de l'exception s'il ne le souhaite pas.

mise à jour : merci pour toutes les réponses ! Je dois dire que même si je n'aime pas l'imprévisibilité du flux de code avec les exceptions. La réponse concernant les codes de retour (et leurs frères aînés les handles) ajoute beaucoup de bruit au code.

1 votes

Un problème de poule ou d'œuf dans le monde du logiciel... éternellement discutable :)

0 votes

Je m'en excuse, mais j'espère que le fait d'avoir une variété d'opinions aidera les gens (moi y compris) à faire un choix approprié.

114voto

paercebal Points 38526

Pour certains langages (par exemple C++), la fuite de ressources ne devrait pas être une raison.

Le C++ est basé sur le RAII.

Si votre code est susceptible d'échouer, de retourner ou de lancer (c'est-à-dire la plupart des codes normaux), votre pointeur doit être enveloppé dans un pointeur intelligent (en supposant que vous ayez une balise très bon raison de ne pas créer votre objet sur la pile).

Les codes de retour sont plus verbeux

Elles sont verbeuses et ont tendance à se transformer en quelque chose comme.. :

if(doSomething())
{
   if(doSomethingElse())
   {
      if(doSomethingElseAgain())
      {
          // etc.
      }
      else
      {
         // react to failure of doSomethingElseAgain
      }
   }
   else
   {
      // react to failure of doSomethingElse
   }
}
else
{
   // react to failure of doSomething
}

En fin de compte, votre code est une collection d'instructions identifiées (j'ai vu ce type de code dans le code de production).

Ce code pourrait être traduit par :

try
{
   doSomething() ;
   doSomethingElse() ;
   doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
   // react to failure of doSomething
}
catch(const SomethingElseException & e)
{
   // react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
   // react to failure of doSomethingElseAgain
}

qui séparent proprement le traitement du code et des erreurs, qui puede soit un bon chose.

Les codes de retour sont plus fragiles

S'il ne s'agit pas d'un obscur avertissement d'un compilateur (voir le commentaire de "phjr"), ils peuvent facilement être ignorés.

Avec les exemples ci-dessus, supposons que quelqu'un oublie de gérer son erreur éventuelle (cela arrive...). L'erreur est ignorée lorsqu'elle est "retournée", et risque d'exploser plus tard (par exemple, un pointeur NULL). Le même problème ne se produira pas avec une exception.

L'erreur ne sera pas ignorée. Parfois, vous voulez qu'elle n'explose pas, cependant... Il faut donc choisir avec soin.

Les codes de retour doivent parfois être traduits

Supposons que nous ayons les fonctions suivantes :

  • doSomething, qui peut retourner un int appelé NOT_FOUND_ERROR
  • doSomethingElse, qui peut retourner un bool "false" (en cas d'échec)
  • doSomethingElseAgain, qui peut renvoyer un objet Error (avec les variables __LINE__, __FILE__ et la moitié de la pile).
  • doTryToDoSomethingWithAllThisMess qui, eh bien... Utiliser les fonctions ci-dessus, et renvoyer un code d'erreur de type...

Quel est le type de retour de doTryToDoSomethingWithAllThisMess si l'une de ses fonctions appelées échoue ?

Les codes de retour ne sont pas une solution universelle

Les opérateurs ne peuvent pas renvoyer de code d'erreur. Les constructeurs C++ ne le peuvent pas non plus.

Les codes de retour signifient que vous ne pouvez pas enchaîner les expressions.

Le corollaire du point précédent. Et si je veux écrire :

CMyType o = add(a, multiply(b, c)) ;

Je ne peux pas, car la valeur de retour est déjà utilisée (et parfois, elle ne peut pas être modifiée). La valeur de retour devient donc le premier paramètre, envoyé en tant que référence... Ou pas.

Les exceptions sont typées

Vous pouvez envoyer des classes différentes pour chaque type d'exception. Les exceptions relatives aux ressources (c'est-à-dire la perte de mémoire) doivent être légères, mais toutes les autres peuvent être aussi lourdes que nécessaire (j'aime bien que l'exception Java me donne toute la pile).

Chaque prise peut ensuite être spécialisée.

N'utilisez jamais catch(...) sans relancer la procédure

En général, vous ne devez pas cacher une erreur. Si vous ne relancez pas l'erreur, au moins, enregistrez l'erreur dans un fichier, ouvrez une boîte de message, etc...

Les exceptions sont... NUKE

Le problème avec les exceptions est qu'une utilisation excessive produit un code plein de try/catches. Mais le problème est ailleurs : Qui fait des try/catch dans son code en utilisant un conteneur STL ? Pourtant, ces conteneurs peuvent envoyer une exception.

Bien sûr, en C++, il ne faut jamais laisser une exception sortir d'un destructeur.

Les exceptions sont... synchrones

Assurez-vous de les attraper avant qu'ils ne mettent votre fil d'exécution à genoux ou qu'ils ne se propagent dans votre boucle de messages Windows.

La solution pourrait être de les mélanger ?

Je suppose donc que la solution consiste à lancer un message lorsque quelque chose devrait no se produisent. Et lorsque quelque chose peut se produire, il faut utiliser un code de retour ou un paramètre pour permettre à l'utilisateur de réagir.

La seule question qui se pose est donc la suivante : "Qu'est-ce qui ne devrait pas se produire ?".

Cela dépend du contrat de votre fonction. Si la fonction accepte un pointeur, mais spécifie que le pointeur doit être non-NULL, alors il est normal de lancer une exception lorsque l'utilisateur envoie un pointeur NULL (la question étant, en C++, quand l'auteur de la fonction n'a-t-il pas utilisé des références au lieu de pointeurs, mais...).

Une autre solution consisterait à afficher l'erreur

Parfois, votre problème est que vous ne voulez pas d'erreurs. L'utilisation d'exceptions ou de codes de retour d'erreur est intéressante, mais... Vous voulez le savoir.

Dans mon travail, nous utilisons une sorte de "Assert". Il va, en fonction des valeurs d'un fichier de configuration, peu importe les options de compilation debug/release :

  • enregistrer l'erreur
  • ouvrir une boîte de message avec un "Hey, you have a problem" (Hé, vous avez un problème)
  • ouvrir une boîte de message avec un "Hey, you have a problem, do you want to debug" (Hé, vous avez un problème, voulez-vous déboguer)

Dans le cadre du développement et des tests, cela permet à l'utilisateur de localiser le problème exactement au moment où il est détecté, et non après (lorsqu'un code se préoccupe de la valeur de retour, ou à l'intérieur d'un code de rattrapage).

Il est facile de l'ajouter au code existant. Par exemple :

void doSomething(CMyObject * p, int iRandomData)
{
   // etc.
}

conduit à une sorte de code similaire à :

void doSomething(CMyObject * p, int iRandomData)
{
   if(iRandomData < 32)
   {
      MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
      return ;
   }

   if(p == NULL)
   {
      MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
      throw std::some_exception() ;
   }

   if(! p.is Ok())
   {
      MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
   }

   // etc.
}

(J'ai des macros similaires qui ne sont actives qu'en cas de débogage).

Notez qu'en production, le fichier de configuration n'existe pas, donc le client ne voit jamais le résultat de cette macro... Mais il est facile de l'activer en cas de besoin.

Conclusion

Lorsque vous codez en utilisant des codes de retour, vous vous préparez à l'échec et vous espérez que votre forteresse de tests est suffisamment sûre.

Lorsque vous codez en utilisant des exceptions, vous savez que votre code peut échouer, et vous placez généralement des contre-feux à des endroits stratégiques choisis dans votre code. Mais en général, votre code est plus axé sur "ce qu'il doit faire" que sur "ce que je crains qu'il ne se produise".

Mais quand on code, il faut utiliser le meilleur outil à sa disposition, et parfois, c'est "Ne jamais cacher une erreur, et la montrer dès que possible". La macro dont j'ai parlé plus haut suit cette philosophie.

3 votes

Oui, MAIS en ce qui concerne votre premier exemple, on pourrait facilement l'écrire comme suit : if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }

0 votes

Pourquoi pas... Mais je trouve toujours doSomething(); doSomethingElse(); ... mieux parce que si je dois ajouter des instructions if/while/etc. à des fins d'exécution normale, je ne veux pas qu'elles soient mélangées avec des instructions if/while/etc. ajoutées à des fins exceptionnelles... Et comme la vraie règle concernant l'utilisation des exceptions est lancer , pas pour attraper les instructions try/catch ne sont généralement pas invasives.

2 votes

Votre premier point montre quel est le problème des exceptions. Votre flux de contrôle devient bizarre et est séparé du problème réel. Il remplace certains niveaux d'identification par une cascade de captures. J'utiliserais les deux, les codes de retour (ou les objets de retour avec des informations lourdes) pour les erreurs possibles et les exceptions pour les choses qui ne le sont pas. attendue .

38voto

Stephen Wrighton Points 15904

En fait, j'utilise les deux.

J'utilise les codes de retour s'il s'agit d'une erreur connue et possible. S'il s'agit d'un scénario dont je sais qu'il peut se produire et qu'il se produira, un code est renvoyé.

Les exceptions sont utilisées uniquement pour les choses auxquelles je ne m'attends PAS.

0 votes

+1 pour une solution très simple. Au moins, c'est assez court pour que je puisse le lire très rapidement :-)

1 votes

" Les exceptions sont utilisées uniquement pour les choses auxquelles je ne m'attends PAS. " Si vous ne les attendez pas, pourquoi ou comment les utiliser ?

6 votes

Ce n'est pas parce que je ne m'attends pas à ce que cela se produise que je ne vois pas comment cela pourrait se produire. Je m'attends à ce que mon serveur SQL soit activé et qu'il réponde. Mais je continue à coder mes attentes afin de pouvoir échouer de manière gracieuse en cas d'arrêt inattendu.

23voto

hurst Points 1192

Selon le chapitre 7 intitulé "Exceptions" de la Lignes directrices pour la conception du cadre : Conventions, idiomes et modèles pour des bibliothèques .NET réutilisables De nombreuses raisons sont données pour expliquer pourquoi l'utilisation d'exceptions plutôt que de valeurs de retour est nécessaire pour les cadres OO tels que C#.

C'est peut-être la raison la plus convaincante (page 179) :

"Les exceptions s'intègrent bien dans les langages orientés objet. Les langages orientés objet ont tendance à imposer des contraintes sur les signatures des membres qui ne sont pas imposées par les fonctions dans les langages non orientés objet. Par exemple, dans le cas des constructeurs, des surcharges d'opérateurs et des propriétés, le développeur n'a pas le choix de la valeur de retour. C'est pourquoi il n'est pas possible de normaliser les rapports d'erreur basés sur la valeur de retour pour les cadres orientés objet. Une méthode de signalement des erreurs, telle que les exceptions, qui ne fait pas partie de la signature de la méthode, est la seule option possible. "

0 votes

Nous vous recommandons vivement de lire ce chapitre. Le chapitre donne un guide très systématique de la gestion des erreurs/exceptions avec beaucoup de choses à faire et à ne pas faire que je ne peux pas trouver en ligne.

10voto

Jason Etheridge Points 3879

Je préfère (en C++ et en Python) utiliser les exceptions. Les facilités offertes par le langage en font un processus bien défini pour lever, attraper et (si nécessaire) relancer des exceptions, ce qui rend le modèle facile à voir et à utiliser. D'un point de vue conceptuel, le modèle est plus propre que les codes de retour, dans la mesure où les exceptions spécifiques peuvent être définies par leur nom et être accompagnées d'informations supplémentaires. Avec un code de retour, vous êtes limité à la valeur de l'erreur (à moins que vous ne souhaitiez définir un objet ReturnStatus ou autre).

À moins que le code que vous écrivez ne soit critique en termes de temps, la surcharge associée au déroulement de la pile n'est pas suffisamment importante pour s'en préoccuper.

3 votes

N'oubliez pas que l'utilisation d'exceptions rend l'analyse du programme plus difficile.

7voto

johnc Points 12140

Les exceptions ne doivent être renvoyées que lorsqu'il se produit quelque chose que l'on n'attendait pas.

L'autre point d'exception, historiquement, est que les codes de retour sont intrinsèquement propriétaires, parfois un 0 peut être renvoyé par une fonction C pour indiquer un succès, parfois -1, ou l'un ou l'autre pour un échec et 1 pour un succès. Même lorsqu'ils sont énumérés, les énumérations peuvent être ambiguës.

Les exceptions peuvent également fournir beaucoup plus d'informations et indiquer clairement "Quelque chose n'a pas fonctionné, voici ce qui s'est passé, une trace de la pile et quelques informations complémentaires pour le contexte

Ceci étant dit, un code de retour bien énuméré peut être utile pour un ensemble connu de résultats, un simple "voici les n résultats de la fonction, et elle s'est exécutée de cette façon

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X