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. Veuillez être spécifique à la langue (ou à la famille de langues) et expliquer pourquoi vous préférez l'une à l'autre.
Je demande cela par curiosité. Personnellement, je préfère les codes de retour d'erreur car ils sont moins explosifs et ne forcent pas le code utilisateur à payer la pénalité de performance d'exception s'ils ne le souhaitent 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 des exceptions. La réponse sur le code de retour (et leurs poignées de frère aîné) ajoute beaucoup de bruit au code.
language-agnostic
exception
Robert Gould
la source
la source
Réponses:
Pour certains langages (c'est-à-dire C ++), la fuite de ressources ne devrait pas être une raison
C ++ est basé sur RAII.
Si vous avez du code qui pourrait échouer, retourner ou lancer (c'est-à-dire la plupart du code normal), alors vous devriez avoir votre pointeur enveloppé dans un pointeur intelligent (en supposant que vous avez une très bonne raison de ne pas créer votre objet sur la pile).
Les codes de retour sont plus détaillés
Ils sont verbeux et ont tendance à se développer en quelque chose comme:
Au final, votre code est une collection d'instructions identifiées (j'ai vu ce genre de code dans le code de production).
Ce code pourrait bien être traduit en:
Quel code distinct et traitement des erreurs, ce qui peut être une bonne chose.
Les codes de retour sont plus fragiles
Si ce n'est pas un avertissement obscur 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 possible (cela se produit ...). L'erreur est ignorée lorsqu'elle est "retournée", et risque d'exploser plus tard (c'est-à-dire un pointeur NULL). Le même problème ne se produira pas avec exception.
L'erreur ne sera pas ignorée. Parfois, vous voulez qu'elle n'explose pas, cependant ... Vous devez donc choisir avec soin.
Les codes de retour doivent parfois être traduits
Disons que nous avons les fonctions suivantes:
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 un 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 ci-dessus. Et si je veux écrire:
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é comme référence ... Ou pas.
Les exceptions sont saisies
Vous pouvez envoyer différentes classes pour chaque type d'exception. Les exceptions de ressources (c'est-à-dire en mémoire) devraient être légères, mais tout le reste pourrait être aussi lourd que nécessaire (j'aime l'exception Java qui me donne toute la pile).
Chaque prise peut alors être spécialisée.
Ne jamais utiliser la capture (...) sans relancer
En règle générale, vous ne devez pas masquer une erreur. Si vous ne relancez pas, à tout le moins, enregistrez l'erreur dans un fichier, ouvrez une boîte de message, peu importe ...
Les exceptions sont ... NUKE
Le problème avec l'exception est que leur utilisation excessive produira du code plein d'essais / captures. Mais le problème est ailleurs: qui essaie / capture son code en utilisant le conteneur STL? Pourtant, ces conteneurs peuvent envoyer une exception.
Bien sûr, en C ++, ne laissez jamais une exception quitter un destructeur.
Les exceptions sont ... synchrones
Assurez-vous de les attraper avant qu'ils ne mettent votre thread à genoux ou ne se propagent à l'intérieur de votre boucle de messages Windows.
La solution pourrait être de les mélanger?
Donc je suppose que la solution est de jeter quand quelque chose ne devrait pas arriver. Et quand quelque chose peut arriver, utilisez un code de retour ou un paramètre pour permettre à l'utilisateur d'y réagir.
Donc, la seule question est "qu'est-ce que quelque chose qui ne devrait pas arriver?"
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 correct de lever 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 à la place de pointeurs, mais ...)
Une autre solution serait d'afficher l'erreur
Parfois, votre problème est que vous ne voulez pas d'erreurs. Utiliser des exceptions ou des codes de retour d'erreur est cool, mais ... vous voulez en savoir plus.
Dans mon travail, nous utilisons une sorte de "Assert". Cela dépendra des valeurs d'un fichier de configuration, quelles que soient les options de compilation de débogage / publication:
Dans le développement et les tests, cela permet à l'utilisateur de localiser le problème exactement quand il est détecté, et non après (lorsque certains codes se soucient de la valeur de retour, ou à l'intérieur d'une capture).
Il est facile d'ajouter au code hérité. Par exemple:
conduit une sorte de code similaire à:
(J'ai des macros similaires qui ne sont actives que sur le 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 à l'aide de codes de retour, vous vous préparez à l'échec et espérez que votre forteresse de tests est suffisamment sécurisée.
Lorsque vous codez à l'aide d'une exception, vous savez que votre code peut échouer et placer généralement la capture de contre-feu à la position stratégique choisie dans votre code. Mais généralement, votre code concerne plus "ce qu'il doit faire" que "ce que je crains qu'il se passe".
Mais quand vous codez, vous devez utiliser le meilleur outil à votre disposition, et parfois, c'est "Ne cachez jamais une erreur, et montrez-la le plus tôt possible". La macro dont j'ai parlé ci-dessus suit cette philosophie.
la source
if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
doSomething(); doSomethingElse(); ...
mieux car si j'ai besoin d'ajouter if / while / etc. à des fins d'exécution normales, je ne veux pas qu'elles soient mélangées avec if / while / etc. instructions ajoutées à des fins exceptionnelles ... Et comme la vraie règle concernant l'utilisation des exceptions est de lancer , et non d'attraper , les instructions try / catch ne sont généralement pas invasives.J'utilise les deux en fait.
J'utilise des codes de retour s'il s'agit d'une erreur connue et possible. Si c'est un scénario dont je sais qu'il peut se produire et se produira, alors il y a un code qui est renvoyé.
Les exceptions sont utilisées uniquement pour des choses auxquelles je ne m'attends PAS.
la source
Selon le chapitre 7 intitulé «Exceptions» dans Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries , de nombreuses justifications sont données pour expliquer pourquoi l'utilisation d'exceptions sur les valeurs de retour est nécessaire pour les frameworks OO tels que C #.
C'est peut-être la raison la plus convaincante (page 179):
"Les exceptions s'intègrent bien avec 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 des fonctions dans des langages non OO. 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 dans la valeur de retour. Pour cette raison, il n'est pas possible de normaliser les rapports d'erreurs basés sur la valeur de retour pour les frameworks orientés objet. Une méthode de rapport d'erreurs, telle que les exceptions, qui est hors de la bande de signature est la seule option. "
la source
Ma préférence (en C ++ et Python) est d'utiliser des exceptions. Les fonctionnalités fournies par le langage en font un processus bien défini pour à la fois lever, attraper et (si nécessaire) relancer des exceptions, ce qui rend le modèle facile à voir et à utiliser. Conceptuellement, c'est plus propre que les codes de retour, en ce sens que des exceptions spécifiques peuvent être définies par leurs noms et être accompagnées d'informations supplémentaires. Avec un code de retour, vous êtes limité à la valeur d'erreur (à moins que vous ne souhaitiez définir un objet ReturnStatus ou quelque chose).
À 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 être préoccupée.
la source
Les exceptions ne doivent être renvoyées que lorsque quelque chose se produit auquel vous ne vous attendiez pas.
L'autre point d'exceptions, historiquement, est que les codes de retour sont intrinsèquement propriétaires, parfois un 0 pourrait être retourné d'une fonction C pour indiquer le succès, parfois -1, ou l'un d'eux pour un échec avec 1 pour un succès. Même lorsqu'elles sont énumérées, les énumérations peuvent être ambiguës.
Les exceptions peuvent également fournir beaucoup plus d'informations, et préciser spécifiquement `` Quelque chose s'est mal passé, voici quoi, une trace de pile et des informations de support pour le contexte ''
Cela étant dit, un code de retour bien énuméré peut être utile pour un ensemble connu de résultats, un simple `` voici les résultats de la fonction, et il s'est simplement déroulé de cette façon ''
la source
En Java, j'utilise (dans l'ordre suivant):
Conception par contrat (s'assurer que les conditions préalables sont remplies avant d'essayer tout ce qui pourrait échouer). Cela attrape la plupart des choses et je renvoie un code d'erreur pour cela.
Renvoyer les codes d'erreur pendant le traitement du travail (et effectuer une restauration si nécessaire).
Exceptions, mais celles-ci ne sont utilisées que pour des choses inattendues.
la source
assert
en ce sens qu'il ne détectera pas de problèmes dans le code de production à moins que vous ne couriez avec des assertions tout le temps. J'ai tendance à voir les assertions comme un moyen de détecter les problèmes pendant le développement, mais uniquement pour les éléments qui seront affirmés ou non de manière cohérente (comme les éléments avec des constantes, pas les éléments avec des variables ou tout autre élément qui peut changer au moment de l'exécution).Les codes de retour échouent presque à chaque fois au test « Pit of Success ».
Concernant les performances:
la source
Un excellent conseil que j'ai reçu de The Pragmatic Programmer était quelque chose du genre "votre programme devrait être capable d'exécuter toutes ses fonctionnalités principales sans utiliser d'exceptions du tout".
la source
J'ai écrit un article de blog à ce sujet il y a quelque temps.
La surcharge de performance liée à la levée d'une exception ne devrait jouer aucun rôle dans votre décision. Si vous le faites correctement, après tout, une exception est exceptionnelle .
la source
Je n'aime pas les codes de retour car ils provoquent une prolifération du modèle suivant dans votre code
CRetType obReturn = CODE_SUCCESS; obReturn = CallMyFunctionWhichReturnsCodes(); if (obReturn == CODE_BLOW_UP) { // bail out goto FunctionExit; }
Bientôt, un appel de méthode composé de 4 appels de fonction gonfle avec 12 lignes de gestion des erreurs. Certaines d'entre elles ne se produiront jamais. Si et les cas de changement abondent.
Les exceptions sont plus propres si vous les utilisez bien ... pour signaler des événements exceptionnels ... après quoi le chemin d'exécution ne peut plus continuer. Ils sont souvent plus descriptifs et informatifs que les codes d'erreur.
Si vous avez plusieurs états après un appel de méthode qui doivent être traités différemment (et ne sont pas des cas exceptionnels), utilisez des codes d'erreur ou des paramètres de sortie. Bien que personnellement, j'aie trouvé cela rare.
J'ai un peu cherché le contre-argument de la «pénalité de performance» ... plus dans le monde C ++ / COM mais dans les nouveaux langages, je pense que la différence n'est pas si grande. Dans tous les cas, quand quelque chose explose, les problèmes de performances sont relégués au second plan :)
la source
Avec n'importe quel compilateur ou environnement d'exécution décent, les exceptions n'entraînent pas de pénalité significative. C'est plus ou moins comme une instruction GOTO qui saute au gestionnaire d'exceptions. De plus, le fait d'avoir des exceptions capturées par un environnement d'exécution (comme la JVM) permet d'isoler et de corriger un bogue beaucoup plus facilement. Je prendrai une NullPointerException en Java sur un segfault en C n'importe quel jour.
la source
finally
blocs et les destructeurs pour les objets alloués à la pile.J'ai un ensemble de règles simples:
1) Utilisez des codes de retour pour les choses auxquelles vous attendez de votre appelant immédiat qu'il réagisse.
2) Utilisez des exceptions pour les erreurs dont la portée est plus large et dont on peut raisonnablement s'attendre à ce qu'elles soient gérées par quelque chose de plusieurs niveaux au-dessus de l'appelant afin que la conscience de l'erreur n'ait pas à se répandre à travers de nombreuses couches, rendant le code plus complexe.
En Java, je n'ai jamais utilisé que des exceptions non vérifiées, les exceptions vérifiées finissent par n'être qu'une autre forme de code de retour et d'après mon expérience, la dualité de ce qui pourrait être «retourné» par un appel de méthode était généralement plus une entrave qu'une aide.
la source
J'utilise des exceptions en python dans des circonstances exceptionnelles et non exceptionnelles.
Il est souvent agréable de pouvoir utiliser une exception pour indiquer que "la demande n'a pas pu être effectuée", par opposition à renvoyer une valeur d'erreur. Cela signifie que vous savez / toujours / que la valeur de retour est le bon type, au lieu de None ou NotFoundSingleton arbitrairement ou quelque chose. Voici un bon exemple où je préfère utiliser un gestionnaire d'exceptions au lieu d'un conditionnel sur la valeur de retour.
L'effet secondaire est que lorsqu'un datastore.fetch (obj_id) est exécuté, vous n'avez jamais à vérifier si sa valeur de retour est None, vous obtenez cette erreur immédiatement et gratuitement. Cela va à l'encontre de l'argument selon lequel "votre programme devrait être capable d'exécuter toutes ses fonctionnalités principales sans utiliser d'exceptions du tout".
Voici un autre exemple où les exceptions sont «exceptionnellement» utiles, afin d'écrire du code pour traiter le système de fichiers qui n'est pas soumis à des conditions de concurrence.
Un appel système au lieu de deux, aucune condition de concurrence. C'est un mauvais exemple car évidemment cela échouera avec une OSError dans plus de circonstances que le répertoire n'existe, mais c'est une solution «assez bonne» pour de nombreuses situations étroitement contrôlées.
la source
Je pense que les codes de retour ajoutent au bruit du code. Par exemple, j'ai toujours détesté l'apparence du code COM / ATL en raison des codes de retour. Il devait y avoir une vérification HRESULT pour chaque ligne de code. Je considère que le code de retour d'erreur est l'une des mauvaises décisions prises par les architectes de COM. Cela rend difficile le regroupement logique du code, donc la révision du code devient difficile.
Je ne suis pas sûr de la comparaison des performances lorsqu'il y a une vérification explicite du code de retour à chaque ligne.
la source
Je préfère utiliser des exceptions pour la gestion des erreurs et renvoyer des valeurs (ou des paramètres) comme résultat normal d'une fonction. Cela donne un schéma de gestion des erreurs simple et cohérent et, si cela est fait correctement, cela donne un code beaucoup plus propre.
la source
L'une des grandes différences est que les exceptions vous obligent à gérer une erreur, alors que les codes de retour d'erreur peuvent être décochés.
Les codes de retour d'erreur, s'ils sont utilisés de manière intensive, peuvent également provoquer un code très laid avec de nombreux tests if similaires à ce formulaire:
Personnellement, je préfère utiliser des exceptions pour les erreurs qui DEVRAIENT ou DOIVENT être traitées par le code appelant, et n'utiliser que des codes d'erreur pour les "échecs attendus" où le retour de quelque chose est réellement valide et possible.
la source
Il y a de nombreuses raisons de préférer les exceptions au code retour:
la source
Les exceptions ne concernent pas la gestion des erreurs, OMI. Les exceptions ne sont que cela; événements exceptionnels auxquels vous ne vous attendiez pas. Utilisez avec prudence je dis.
Les codes d'erreur peuvent être OK, mais renvoyer 404 ou 200 à partir d'une méthode est mauvais, IMO. Utilisez plutôt des enums (.Net), ce qui rend le code plus lisible et plus facile à utiliser pour les autres développeurs. De plus, vous n'avez pas à maintenir un tableau sur les nombres et les descriptions.
Aussi; le modèle try-catch-finally est un anti-pattern dans mon livre. Try-finally peut être bon, try-catch peut aussi être bon, mais try-catch-finally n'est jamais bon. try-finally peut souvent être remplacé par une instruction "using" (modèle IDispose), ce qui est mieux IMO. Et Try-catch où vous attrapez réellement une exception que vous êtes capable de gérer est bon, ou si vous faites ceci:
Donc, tant que vous laissez l'exception continuer à bouillonner, c'est OK. Un autre exemple est celui-ci:
Ici, je gère réellement l'exception; ce que je fais en dehors du try-catch lorsque la méthode de mise à jour échoue est une autre histoire, mais je pense que mon point a été fait. :)
Pourquoi alors try-catch-finally est un anti-pattern? Voici pourquoi:
Que se passe-t-il si l'objet db a déjà été fermé? Une nouvelle exception est lancée et doit être gérée! C'est mieux:
Ou, si l'objet db n'implémente pas IDisposable, procédez comme suit:
C'est mes 2 cents en tout cas! :)
la source
Je n'utilise que des exceptions, pas de codes de retour. Je parle de Java ici.
La règle générale que je suis est que si j'ai une méthode appelée,
doFoo()
il s'ensuit que si elle ne "fait pas foo", pour ainsi dire, quelque chose d'exceptionnel s'est produit et une exception devrait être lancée.la source
Une chose que je crains à propos des exceptions est que lancer une exception va bousiller le flux de code. Par exemple si vous faites
Ou pire encore, que se passerait-il si j'avais supprimé quelque chose que je n'aurais pas dû, mais que j'étais jeté à attraper avant de faire le reste du nettoyage. Lancer a mis beaucoup de poids sur le pauvre utilisateur IMHO.
la source
Ma règle générale dans l'argument exception vs code de retour:
la source
Je ne trouve pas que les codes de retour soient moins laids que les exceptions. À l'exception, vous avez le
try{} catch() {} finally {}
où comme avec les codes de retour que vous avezif(){}
. J'avais l'habitude de craindre des exceptions pour les raisons données dans le message; vous ne savez pas si le pointeur doit être effacé, qu'est-ce que vous avez. Mais je pense que vous avez les mêmes problèmes en ce qui concerne les codes de retour. Vous ne connaissez pas l'état des paramètres à moins de connaître certains détails sur la fonction / méthode en question.Quoi qu'il en soit, vous devez gérer l'erreur si possible. Vous pouvez tout aussi facilement laisser une exception se propager au niveau supérieur que d'ignorer un code de retour et laisser le programme segfault.
J'aime l'idée de renvoyer une valeur (énumération?) Pour les résultats et une exception pour un cas exceptionnel.
la source
Pour un langage comme Java, j'irais avec Exception car le compilateur donne une erreur de compilation si les exceptions ne sont pas gérées, ce qui oblige la fonction appelante à gérer / lever les exceptions.
Pour Python, je suis plus en conflit. Il n'y a pas de compilateur, il est donc possible que l'appelant ne gère pas l'exception levée par la fonction menant aux exceptions d'exécution. Si vous utilisez des codes de retour, vous pouvez avoir un comportement inattendu s'ils ne sont pas gérés correctement et si vous utilisez des exceptions, vous pouvez obtenir des exceptions d'exécution.
la source
Je préfère généralement les codes de retour car ils laissent l'appelant décider si l'échec est exceptionnel .
Cette approche est typique du langage Elixir.
Les gens ont mentionné que les codes de retour peuvent vous amener à avoir beaucoup d'
if
instructions imbriquées , mais que cela peut être géré avec une meilleure syntaxe. Dans Elixir, l'with
instruction nous permet de séparer facilement une série de valeurs de retour happy-path de tout échec.Elixir a toujours des fonctions qui soulèvent des exceptions. Pour revenir à mon premier exemple, je pourrais faire l'un ou l'autre de ces éléments pour lever une exception si le fichier ne peut pas être écrit.
Si, en tant qu'appelant, je sais que je veux déclencher une erreur si l'écriture échoue, je peux choisir d'appeler
File.write!
au lieu deFile.write
. Ou je peux choisir d'appelerFile.write
et de gérer différemment chacune des raisons possibles de l'échec.Bien sûr, il est toujours possible de faire
rescue
une exception si nous le voulons. Mais comparé à la gestion d'une valeur de retour informative, cela me semble gênant. Si je sais qu'un appel de fonction peut échouer ou même doit échouer, son échec n'est pas un cas exceptionnel.la source