web-dev-qa-db-fra.com

Pourquoi les exceptions sont-elles considérées comme meilleures que les tests d'erreur explicites?

Duplicata possible:
Programmation défensive vs gestion des exceptions?
instructions if/else ou exceptions

Je rencontre souvent des articles de blog passionnés où l'auteur utilise l'argument: "exceptions vs vérification d'erreur explicite" pour défendre sa langue préférée par rapport à une autre langue. Le consensus général semble être que les langages qui utilisent des exceptions sont intrinsèquement meilleurs/plus propres que les langages qui reposent fortement sur la vérification des erreurs via des appels de fonction explicites.

L'utilisation d'exceptions est-elle considérée comme une meilleure pratique de programmation que la vérification d'erreur explicite, et si oui, pourquoi?

48
Richard Keller

Dans mon esprit, le plus grand argument est la différence dans ce qui se passe lorsque le programmeur fait une erreur. Oublier de gérer une erreur est une erreur très courante et facile à faire.

Si vous renvoyez des codes d'erreur, il est possible d'ignorer silencieusement une erreur. Par exemple, si malloc échoue, il renvoie NULL et définit le global errno. Donc, un code correct devrait faire

void* myptr = malloc(1024);
if (myptr == NULL) {
    perror("malloc");
    exit(1);
}
doSomethingWith(myptr);

Mais il est très facile et pratique d'écrire uniquement:

void* myptr = malloc(1024);
doSomethingWith(myptr);

qui passera de façon inattendue NULL dans votre autre procédure et supprimera probablement le errno qui a été soigneusement défini. Il n'y a rien de mal visiblement avec le code pour indiquer que cela est possible.

Dans une langue qui utilise des exceptions, vous écririez à la place

MyCoolObject obj = new MyCoolObject();
doSomethingWith(obj);

Dans cet exemple (Java), l'opérateur new renvoie soit un objet initialisé valide, soit lance OutOfMemoryError. Si un programmeur doit gérer cela, il peut l'attraper. Dans le cas habituel (et commodément, également paresseux) où il s'agit d'une erreur fatale, la propagation des exceptions termine le programme d'une manière relativement propre et explicite.

C'est une des raisons pour lesquelles les exceptions, lorsqu'elles sont correctement utilisées, peuvent faciliter l'écriture de code clair et sûr. Ce modèle s'applique à de très nombreuses choses qui peuvent mal tourner, pas seulement à l'allocation de mémoire.

63
Steven Schlansker

Alors que réponse de Steven fournit une bonne explication, il y a un autre point que je trouve assez important. Parfois, lorsque vous vérifiez un code d'erreur, vous ne pouvez pas gérer le cas d'échec immédiatement. Vous devez propager l'erreur explicitement à travers la pile d'appels. Lorsque vous refactorisez une grande fonction, vous devrez peut-être ajouter tout le code passe-partout de vérification des erreurs à votre sous-fonction.

À quelques exceptions près, vous n'avez qu'à prendre soin de votre flux principal. Si un morceau de code renvoie une erreur InvalidOperationError, vous pouvez toujours déplacer ce code vers une sous-fonction et la logique de gestion des erreurs sera conservée.

Les exceptions vous permettent donc de refaçonner plus rapidement et d'éviter la plaque de la chaudière.

57
Simon Bergot

Un point de vue sous un angle différent: la gestion des erreurs est une question de sécurité. Une erreur non vérifiée rompt toutes les hypothèses et conditions préalables sur lesquelles le code suivant était basé. Cela peut ouvrir de nombreux vecteurs d'attaque externes: d'un simple DoS sur un accès non autorisé aux données, à la corruption de données et à l'infiltration complète du système.

Bien sûr, cela dépend de l'application spécifique, mais lorsque les hypothèses et les conditions préalables sont rompues, tous les paris sont désactivés. Au sein d'un logiciel complexe, vous ne pouvez tout simplement plus dire avec certitude ce qui est possible à partir de ce moment, et ce qui peut et ne peut pas être utilisé depuis l'extérieur.

Cela étant, il existe une observation fondamentale: lorsque la sécurité est traitée comme un module complémentaire facultatif qui peut être attaché plus tard, elle échoue le plus souvent. Il fonctionne mieux lorsqu'il a déjà été pris en compte dans les toutes premières étapes de conception de base et intégré dès le départ.

Voici ce que vous obtenez essentiellement avec des exceptions: une infrastructure de gestion des erreurs déjà intégrée qui est active même si vous ne vous en souciez pas. Avec des tests explicites, vous devez le construire vous-même. Vous devez le construire comme une toute première étape. Commencer à écrire des fonctions qui renvoient des codes d'erreur, sans penser à la situation dans son ensemble jusqu'à ce que vous en soyez aux étapes finales du développement de votre application, est en fait un module complémentaire de gestion des erreurs, voué à l'échec.

Non pas qu'un système intégré aide en aucune façon. La plupart des programmeurs ne savent pas comment gérer les erreurs. C'est juste trop complexe la plupart du temps.

  • Il y a tellement d'erreurs qui peuvent se produire, et chaque erreur nécessite sa propre gestion et ses propres actions et réactions.

  • Même la même erreur peut nécessiter des actions différentes, en fonction du contexte. Qu'en est-il d'un fichier introuvable ou d'une mémoire insuffisante?

    • FNF - Une bibliothèque tierce nécessaire pour que votre application fonctionne? Mettre fin.
    • FNF - Le fichier de configuration de démarrage de votre application? Commencez avec les paramètres par défaut.
    • FNF - Un fichier de données sur un partage réseau que l'utilisateur souhaite que votre application ouvre? Les fichiers disparus peuvent survenir à tout moment, même dans les microsecondes entre Exists () et Open (). Ce n'est même pas une "exception" au sens littéral du mot.
    • OOM - Pour une allocation de 2 Go? Pas de surprise.
    • MOO - Pour une allocation de 100 octets? Vous avez de sérieux ennuis.
  • Erreur lors de la gestion des fuites à travers toutes vos couches d'abstraction soigneusement séparées. Une erreur au niveau le plus bas peut nécessiter de notifier l'utilisateur avec un message GUI. Cela peut nécessiter une décision de l'utilisateur pour savoir quoi faire maintenant. Il peut avoir besoin d'une journalisation. Il peut nécessiter des opérations de récupération dans une autre partie, par exemple ouvrir une base de données ou des connexions réseau. Etc.

  • Et l'état? Lorsque vous appelez une méthode sur un objet qui appelle des changements d'état et génère une erreur:

    • L'objet est-il dans un état incohérent et a besoin d'une reconstruction?
    • L'état de l'objet est-il cohérent mais (partiellement) modifié et a-t-il besoin d'une restauration supplémentaire ou d'une reconstruction?
    • Une restauration est-elle effectuée dans l'objet et reste inchangée pour l'appelant?
    • Où est-il même judicieux de faire quoi?

Montrez-moi juste un livre d'apprentissage où la gestion des erreurs est rigoureusement conçue dès le départ et par conséquent utilisée à travers tous les exemples, sans être laissée de côté pour la brièveté et la lisibilité et comme exercice pour le lecteur. Si cela est applicable à partir d'un point de vue éducatif, c'est une autre question, mais il n'est pas surprenant que la gestion des erreurs soit souvent une seconde ou une troisième pensée quand elle devrait être la toute première.

19
Secure

J'aimerais considérer les exceptions comme ...

Ils vous permettent d'écrire du code de la façon dont il a naturellement tendance à être écrit

Lors de l'écriture de code, le cerveau humain doit jongler avec un certain nombre de choses:

  • Quelle est la logique que j'essaie d'exprimer ici?
  • Comment puis-je réellement accomplir cela dans la langue XXXX?
  • Comment ce code s'intègre-t-il dans le reste de l'application?
  • Pourquoi est-ce que je continue à recevoir ces erreurs de construction?
  • Suis-je en train d'oublier les conditions aux limites?
  • Ce code est-il suffisamment lisible et maintenable?
  • Que se passe-t-il si quelque chose dans ce code échoue?
  • Suis-je en train de produire du code dans un style cohérent et d'utiliser les bonnes conventions de codage?

Chacune de ces balles nécessite une certaine concentration et la concentration est une ressource limitée. C'est pourquoi les gens qui sont nouveaux dans un projet oublient souvent des choses vers le bas de la liste. Ce ne sont pas de mauvaises personnes, mais comme tant de choses peuvent être nouvelles pour eux (langage, projet, erreurs bizarres, etc ...), ils n'ont tout simplement pas la capacité de faire face à d'autres balles.

J'ai fait ma part de revues de code et cette tendance est très apparente. Une personne avec moins d'expérience oubliera plus de choses. Ces choses incluent le style et souvent assez de gestion des erreurs. Lorsque les gens ont du mal à faire fonctionner le code, la gestion des erreurs a tendance à être dans leur esprit. Parfois, ils vont revenir en arrière et ajouter des instructions if ici et là, mais rassurez-vous, ils oublieront tout un tas de choses.

La gestion des exceptions vous permet d'écrire du code là où la chair de votre logique est écrite comme s'il n'y avait pas d'erreur. Vous effectuez un appel de fonction et la ligne suivante peut simplement supposer que la ligne précédente a réussi. Cela a un bonus supplémentaire de produire du code qui est beaucoup plus facile à lire. Avez-vous déjà vu une référence d'API avec des exemples de code avec un commentaire, "// Gestion des erreurs omise pour plus de clarté"? S'ils suppriment la gestion des erreurs dans la documentation pour rendre l'exemple plus facile à lire, ne serait-ce pas génial si vous pouviez faire la même chose dans le code de production et le rendre plus facile à lire également? C'est exactement ce que les exceptions vous permettent de faire.

Maintenant, au moins une personne lisant ce message dira: "pffttt noob ne sait pas coder. Je n'oublie jamais la gestion des erreurs." a) Bon pour vous, mais plus important encore b) comme je l'ai dit ci-dessus, le codage nécessite que votre cerveau se concentre et qu'il n'y ait que peu de cerveau à contourner; cela s'applique à TOUS. Si votre code est facile à lire, c'est moins de concentration. Si vous pouvez mettre try/catch autour d'un gros morceau puis simplement coder la logique d'application, c'est moins de concentration. Vous pouvez maintenant utiliser cette capacité supplémentaire pour des choses plus importantes, comme faire le travail réel beaucoup plus rapidement.

16
DXM

Avantages de la levée d'exception sur le retour du code d'erreur:

  • La valeur de retour d'une fonction peut être utilisée pour ce pour quoi elle a été conçue: retourne le résultat de l'appel de fonction, pas un code qui représente le succès ou l'un des nombreux échecs de la fonction. Cela crée un code plus propre et plus élégant; moins de paramètres de sortie/référence, les affectations sont faites aux choses qui vous intéressent réellement et non aux codes d'état jetables, etc.
  • Les exceptions sont orientées objet, les types d'exception ont donc une signification conceptuelle réutilisable. Que signifie un code retour de -2? Vous devez le rechercher dans la documentation (ce qui vaut mieux être bon ou vous êtes obligé de suivre le code, et si vous n'avez pas de code source, vous êtes vraiment foutu). Que signifie une exception NullPointerException? Que vous avez essayé d'appeler une méthode sur une variable qui ne fait pas référence à une instance. En outre, les exceptions encapsulent les données, afin qu'elles puissent vous dire exactement comment et où votre programme a été vissé de manière facilement lisible par l'homme. Un code retour ne peut que vous indiquer la méthode ratée.
  • Par défaut, les codes retour sont ignorés; les exceptions ne le sont pas. Si vous ne stockez pas et ne vérifiez pas le code retour, votre programme continue joyeusement, corrompant les données, vissant les pointeurs et se froissant généralement en ordures. Si vous n'attrapez pas d'exception, elle est envoyée au système d'exploitation qui met fin à votre programme. "Échec rapide".
  • Les exceptions sont plus facilement standardisées. Étant donné que les exceptions créent davantage de code auto-documenté et que la plupart d'entre nous n'écrivent pas tous nos programmes à 100%, des types d'exceptions couvrant une grande variété de cas généraux sont déjà disponibles. La solution la moins chère est celle que vous avez déjà, donc ces types d'exceptions intégrés sont utilisés dans des situations similaires à celles pour lesquelles ils ont été créés à l'origine. Maintenant, une InvalidOperationException signifie que vous avez essayé de faire quelque chose d'incohérent avec l'état actuel d'un objet (exactement ce qui était détaillé dans le message d'erreur), quelle que soit la fonction à partir de quelle bibliothèque tierce vous tentiez d'appeler.

    Comparez cela avec les codes retour; pour une méthode, -1 peut être une "erreur de pointeur nul", tandis que -2 peut être une "erreur de fonctionnement non valide". Dans la méthode suivante, la condition "opération non valide" a été vérifiée en premier et de sorte que l'erreur a obtenu le code retour -1, tandis que le "pointeur nul" a obtenu -2. Un nombre est un nombre, et vous êtes libre de créer n'importe quelle norme pour attribuer des numéros aux erreurs, tout comme tout le monde, ce qui conduit à BEAUCOUP de normes concurrentes.

  • Les exceptions vous permettent d'être plus optimiste. Au lieu de vérifier de façon pessimiste tout ce qui pourrait se tromper avec une instruction avant d'exécuter réellement l'instruction, vous pouvez simplement essayer exécuter l'instruction, et intercepter toutes les exceptions générées par celle-ci. Si vous pouvez résoudre la cause de l'erreur et réessayer, tant mieux, sinon, très bien; vous n'auriez probablement rien pu faire si vous l'aviez su au préalable.

    La gestion des erreurs basée sur les exceptions crée ainsi un code plus efficace en supposant qu'il fonctionnera jusqu'à ce qu'il ne fonctionne pas. Les relevés de garde qui renvoient le temps du processeur à un coût précoce; ils devraient donc être utilisés principalement lorsque les avantages de la vérification initiale l'emportent sur les coûts; si vous pouvez déterminer en quelques horloges que l'entrée ne produira jamais une sortie valide, mais qu'il faudra plusieurs secondes pour que le corps principal de la fonction parvienne à la même conclusion, puis mettez certainement une clause de garde. Cependant, s'il y a une douzaine de choses qui pourraient mal tourner, dont aucune n'est particulièrement probable et dont la plupart nécessitent une exécution coûteuse, le "chemin heureux" de l'exécution normale du programme sera plusieurs fois plus rapide si vous essayez simplement/catch.

6
KeithS

La différence fondamentale pour moi est la lecture contre l'écriture:

La gestion du code d'erreur a tendance à encombrer l'intention : le code basé sur les exceptions est généralement plus facile à lire car la source se concentre sur les choses qui devrait se produire, plutôt que ce que pourrait .

OTOH, Beaucoup de code basé sur les exceptions est plus difficile à écrire, car pour une analyse d'exactitude, vous devez discuter de détails "non liés", tels que l'ordre de construction/destruction, les objets partiellement construits, la collecte des ordures, etc.

Comment dur? Ce dur.

La génération d'exceptions significatives peut également encombrer la source, j'ai des bases de code où pratiquement toutes les lignes de code sont suivies de deux ou trois lignes créant une exception lettrée.

Pour transférer des exceptions significatives au niveau supérieur, elles doivent souvent être reconditionnées. Lorsque je clique sur "Cellules rebicker", un message d'erreur comme "Partage de la violation sur% tmp%\foo\file536821" n'est pas beaucoup plus utile que "Erreur 77" .


Je n'ai vraiment pas de préférence, les deux options sont tout aussi laides. Le pire: lequel est le meilleur semble dépendre beaucoup du projet, de manière difficile à prévoir. D'après mon expérience, l'interaction matérielle de bas niveau est plus fluide avec les codes d'erreur (peut-être les clients ...), l'accès aux données de bas niveau est meilleur avec des exceptions.

5
peterchen

Mettons de côté les problèmes d'implémentation de nombreuses langues. L'exception des avantages est qu'ils peuvent être centralisés pour gérer toutes sortes (en théorie) de états exceptionnels du programme. Le problème auquel ils s'attaquent est que exceptionnel les choses se produisent et ne peuvent presque jamais être prédites. Ce qui est génial (en théorie) avec des exceptions, c'est que vous êtes en sécurité tant que vous:

  • Écrire le code de sécurité d'exception au plus haut degré possible
  • Attrapez les états exceptionnels via des exceptions
  • Gérez les exceptions en conséquence
3
zxcdw

Le code basé sur les exceptions déplace la gestion des erreurs hors du flux principal du programme. Au lieu de cela, la gestion des erreurs est déplacée vers le destructeur d'objets ou vers la construction catch.

Le plus grand avantage et aussi le plus gros inconvénient à l'exception est qu'il ne vous oblige pas à penser à propos de la gestion des erreurs. À l'exception, vous écrivez souvent du code simple et laissez le destructeur faire son travail, et oubliez ce petit scénario que le destructeur ne gère pas et doit être fait manuellement (ce qui est clairement documenté et c'est donc totalement de votre faute). pour oublier). Le même scénario peut se produire avec un code d'erreur, mais lors de l'écriture de code avec un code d'erreur, vous êtes obligé de vérifier le manuel/la documentation/la source tout le temps et il est moins probable que vous oubliez cette petite condition.

Oui, cela signifie que les exceptions peuvent parfois rendre plus difficile l'écriture du code correct.

Écrire un mauvais code avec un code d'erreur est facile, écrire un bon code avec un code d'erreur est difficile. L'écriture d'un code décent en exception est facile (car toute erreur met fin au programme ou est interceptée par un gestionnaire de très haut niveau qui pourrait avertir du Doom imminent, au lieu d'être transmis silencieusement), écrire un bon code avec exception est vraiment difficile (car vous gérez les erreurs et les bras loin de l'endroit où l'erreur se produit).

2
Lie Ryan
  • Codes de sortie faciles à oublier
  • Code en double. La vérification du code d'erreur et le renvoi du code d'erreur en cas d'erreur entraîneraient la gestion de nombreux doublons et de nombreux changements si le type de retour change. Que faire également lorsque différents types peuvent provoquer une erreur?
  • Les exceptions sont exécutées en dehors du chemin de code, ce qui signifie qu'il n'y a aucun travail à vérifier la valeur de retour. Mais il y a du travail dans le binaire quelque part lorsque le code d'exception est exécuté.
  • Facile à voir quelles fonctions gèrent quel type d'erreurs. Vous n'avez pas à parcourir beaucoup de code, faites simplement défiler catch.
  • Plus de détails (dans certaines langues). Il a intégré des informations telles que la ligne, la fonction, etc. Mettre cela dans une classe d'erreur serait fastidieux. Vous n'écririez probablement que le nom de la fonction et, espérons-le, le type ou la cause de l'erreur.
  • Les débogueurs comprennent que les exceptions sont des erreurs et peuvent s'arrêter lorsqu'elles en rencontrent une pendant le débogage. Outils d'aide :)
1
user2528