web-dev-qa-db-fra.com

Pourquoi la gestion des exceptions est-elle mauvaise?

Le langage Go de Google n'a pas d'exceptions comme choix de conception, et Linus of Linux a appelé la merde d'exceptions. Pourquoi?

87
joemoe

Les exceptions facilitent vraiment l'écriture de code où une exception levée cassera les invariants et laissera les objets dans un état incohérent. Ils vous obligent essentiellement à vous rappeler que la plupart de vos déclarations peuvent potentiellement être lancées et gérées correctement. Cela peut être délicat et contre-intuitif.

Considérez quelque chose comme ça comme un exemple simple:

class Frobber
{
    int m_NumberOfFrobs;
    FrobManager m_FrobManager;

public:
    void Frob()
    {
        m_NumberOfFrobs++;

        m_FrobManager.HandleFrob(new FrobObject());
    }
};

En supposant que le FrobManager sera delete le FrobObject, cela semble OK, non? Ou peut-être pas ... Imaginez alors si FrobManager::HandleFrob() ou operator new Lève une exception. Dans cet exemple, l'incrément de m_NumberOfFrobs N'est pas annulé. Ainsi, toute personne utilisant cette instance de Frobber aura un objet éventuellement corrompu.

Cet exemple peut sembler stupide (ok, j'ai dû m'étirer un peu pour en construire un :-)), mais le point à retenir est que si un programmeur ne pense pas constamment aux exceptions et s'assure que chaque permutation d'état est roulée retour chaque fois qu'il y a des lancers, vous avez des ennuis de cette façon.

À titre d'exemple, vous pouvez y penser comme vous pensez aux mutex. Dans une section critique, vous comptez sur plusieurs instructions pour vous assurer que les structures de données ne sont pas corrompues et que les autres threads ne peuvent pas voir vos valeurs intermédiaires. Si l'une de ces déclarations ne s'exécute pas au hasard, vous vous retrouvez dans un monde de douleur. Maintenant, supprimez les verrous et les accès concurrents, et pensez à chaque méthode comme ça. Considérez chaque méthode comme une transaction de permutations sur l'état de l'objet, si vous voulez. Au début de votre appel de méthode, l'objet doit être à l'état propre et à la fin, il doit également y avoir un état propre. Entre les deux, la variable foo peut être incompatible avec bar, mais votre code finira par rectifier cela. Les exceptions signifient que n'importe laquelle de vos déclarations peut vous interrompre à tout moment. Il vous incombe dans chaque méthode individuelle de faire les choses correctement et de revenir en arrière lorsque cela se produit, ou d'ordonner vos opérations afin que les lancers n'affectent pas l'état de l'objet. Si vous vous trompez (et il est facile de faire ce genre d'erreur), l'appelant finit par voir vos valeurs intermédiaires.

Des méthodes comme RAII, que les programmeurs C++ aiment mentionner comme la solution ultime à ce problème, protègent grandement contre cela. Mais ce n'est pas une solution miracle. Il vous assurera de libérer des ressources lors d'un lancer, mais ne vous libère pas de la nécessité de penser à la corruption de l'état des objets et aux appelants de voir des valeurs intermédiaires. Donc, pour beaucoup de gens, il est plus facile de dire, par fiat de style de codage, sans exception. Si vous limitez le type de code que vous écrivez, il est plus difficile d'introduire ces bogues. Sinon, il est assez facile de se tromper.

Des livres entiers ont été écrits sur le codage sûr d'exception en C++. Beaucoup d'experts se sont trompés. Si c'est vraiment aussi complexe et a tellement de nuances, c'est peut-être un bon signe que vous devez ignorer cette fonctionnalité. :-)

75
asveikau

La raison pour laquelle Go n'a pas d'exceptions est expliquée dans la FAQ sur la conception du langage Go:

Les exceptions sont une histoire similaire. Un certain nombre de conceptions d'exceptions ont été proposées, mais chacune ajoute une complexité importante au langage et à l'exécution. De par leur nature même, les exceptions couvrent des fonctions et peut-être même des goroutines; ils ont de vastes implications. On s'inquiète également de l'effet qu'ils auraient sur les bibliothèques. Ils sont, par définition, exceptionnels, mais l'expérience avec d'autres langages qui les prennent en charge montre qu'ils ont un effet profond sur les spécifications de bibliothèque et d'interface. Ce serait bien de trouver un design qui leur permette d'être vraiment exceptionnel sans encourager les erreurs courantes à se transformer en un flux de contrôle spécial qui nécessite que chaque programmeur compense.

Comme les génériques, les exceptions restent un problème ouvert.

En d'autres termes, ils n'ont pas encore compris comment prendre en charge les exceptions dans Go d'une manière qu'ils jugent satisfaisante. Ils ne disent pas que les exceptions sont mauvaises en soi;

MISE À JOUR - mai 2012

Les concepteurs de Go sont maintenant descendus de la clôture. Leur FAQ dit maintenant ceci:

Nous pensons que le couplage des exceptions à une structure de contrôle, comme dans l'idiome try-catch-finally, entraîne un code alambiqué. Cela tend également à encourager les programmeurs à étiqueter trop d'erreurs ordinaires, telles que l'échec de l'ouverture d'un fichier, comme exceptionnelles.

Go adopte une approche différente. Pour une gestion simple des erreurs, les retours multi-valeurs de Go permettent de signaler facilement une erreur sans surcharger la valeur de retour. Un type d'erreur canonique, couplé aux autres fonctionnalités de Go, rend la gestion des erreurs agréable mais très différente de celle des autres langues.

Go a également quelques fonctions intégrées pour signaler et se remettre de conditions vraiment exceptionnelles. Le mécanisme de récupération n'est exécuté que dans le cadre de la suppression d'un état d'une fonction après une erreur, ce qui est suffisant pour gérer une catastrophe mais ne nécessite aucune structure de contrôle supplémentaire et, lorsqu'il est bien utilisé, peut entraîner un code de gestion des erreurs propre.

Consultez l'article Différer, paniquer et récupérer pour plus de détails.

Donc, la réponse courte est qu'ils peuvent le faire différemment en utilisant le retour multi-valeurs. (Et ils ont quand même une forme de gestion des exceptions.)


... et Linus de la renommée Linux a appelé la merde d'exceptions.

Si vous voulez savoir pourquoi Linus pense que les exceptions sont de la merde, la meilleure chose à faire est de chercher ses écrits sur le sujet. La seule chose que j'ai retrouvée jusqu'à présent est cette citation qui est intégrée dans quelques courriels sur C++ :

"L'ensemble de la gestion des exceptions C++ est fondamentalement cassé. Il est particulièrement cassé pour les noyaux."

Vous remarquerez qu'il parle d'exceptions C++ en particulier, et non d'exceptions en général. (Et les exceptions C++ oui ont apparemment des problèmes qui les rendent difficiles à utiliser correctement.)

Ma conclusion est que Linus n'a pas du tout qualifié d'exceptions (en général) de "merde"!

49
Stephen C

Les exceptions ne sont pas mauvaises en soi, mais si vous savez qu'elles se produiront souvent, elles peuvent coûter cher en termes de performances.

La règle générale est que les exceptions doivent signaler des conditions exceptionnelles et que vous ne devez pas les utiliser pour contrôler le flux du programme.

29
Robert Harvey

Je ne suis pas d'accord avec "ne lever des exceptions que dans une situation exceptionnelle". Bien que généralement vrai, il est trompeur. Les exceptions concernent les conditions d'erreur (échecs d'exécution).

Quelle que soit la langue que vous utilisez, procurez-vous une copie de Framework Design Guidelines : Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition). Le chapitre sur le lancement d'exceptions est sans pair. Quelques citations de la première édition (la 2e à mon travail):

  • NE PAS renvoyer des codes d'erreur.
  • Les codes d'erreur peuvent être facilement ignorés, et le sont souvent.
  • Les exceptions sont le principal moyen de signaler les erreurs dans les cadres.
  • Une bonne règle de base est que si une méthode ne fait pas ce que son nom l'indique, elle doit être considérée comme un échec au niveau de la méthode, entraînant une exception.
  • NE PAS utiliser des exceptions pour le flux normal de contrôle, si possible.

Il y a des pages de notes sur les avantages des exceptions (cohérence de l'API, choix de l'emplacement du code de gestion des erreurs, robustesse améliorée, etc.) Il y a une section sur les performances qui comprend plusieurs modèles (Tester-Doer, Try-Parse).

Les exceptions et la gestion des exceptions ne sont pas mauvaises. Comme toute autre fonctionnalité, ils peuvent être mal utilisés.

24
TrueWill

Les exceptions en elles-mêmes ne sont pas "mauvaises", c'est la façon dont les exceptions sont parfois traitées qui a tendance à être mauvaise. Plusieurs directives peuvent être appliquées lors de la gestion des exceptions pour aider à atténuer certains de ces problèmes. Certains d'entre eux comprennent (mais ne se limitent certainement pas à):

  1. N'utilisez pas d'exceptions pour contrôler le flux du programme - c'est-à-dire ne vous fiez pas aux instructions "catch" pour modifier le flux de la logique. Non seulement cela a tendance à cacher divers détails autour de la logique, mais cela peut conduire à de mauvaises performances.
  2. Ne lancez pas d'exceptions à l'intérieur d'une fonction lorsqu'un "statut" renvoyé aurait plus de sens - ne lancez des exceptions que dans une situation exceptionnelle. La création d'exceptions est une opération coûteuse et gourmande en performances. Par exemple, si vous appelez une méthode pour ouvrir un fichier et que ce fichier n'existe pas, lève une exception "FileNotFound". Si vous appelez une méthode qui détermine si un compte client existe, renvoyez une valeur booléenne, ne renvoyez pas d'exception "CustomerNotFound".
  3. Lorsque vous déterminez s'il faut gérer une exception ou non, n'utilisez pas de clause "try ... catch" à moins que vous ne puissiez faire quelque chose d'utile avec l'exception. Si vous n'êtes pas en mesure de gérer l'exception, vous devez simplement la laisser bouillonner dans la pile des appels. Sinon, les exceptions peuvent être "avalées" par le gestionnaire et les détails seront perdus (sauf si vous relancez l'exception).
11
Jeff Bramwell

Du point de vue de golang, je suppose que le fait de ne pas avoir d'exception gère le processus de compilation simple et sûr.

Du point de vue de Linus, je comprends que le code du noyau est TOUT sur les cas d'angle. Il est donc logique de refuser les exceptions.

Les exceptions sont logiques dans le code si vous pouvez laisser tomber la tâche en cours et où le code de cas commun a plus d'importance que la gestion des erreurs. Mais ils nécessitent la génération de code à partir du compilateur.

Par exemple, ils conviennent parfaitement à la plupart des codes de haut niveau accessibles aux utilisateurs, tels que le code d'application Web et de bureau.

11
ddaa

Les arguments typiques sont qu'il n'y a aucun moyen de savoir quelles exceptions sortiront d'un morceau de code particulier (selon la langue) et qu'elles ressemblent trop à gotos, ce qui rend difficile le suivi mental de l'exécution.

http://www.joelonsoftware.com/items/2003/10/13.html

Il n'y a certainement pas de consensus sur cette question. Je dirais que du point de vue d'un programmeur C dur comme Linus, les exceptions sont définitivement une mauvaise idée. Un programmeur typique Java est dans une situation très différente, cependant.

9
Tim Sylvester

Les exceptions ne sont pas mauvaises. Ils s'intègrent bien avec le modèle RAII de C++, qui est la chose la plus élégante à propos de C++. Si vous avez déjà un tas de code qui n'est pas à l'abri des exceptions, ils sont mauvais dans ce contexte. Si vous écrivez des logiciels de très bas niveau, comme le système d'exploitation Linux, ils sont mauvais. Si vous aimez joncher votre code avec un tas de contrôles de retour d'erreur, alors ils ne sont pas utiles. Si vous n'avez pas de plan de contrôle des ressources lorsqu'une exception est levée (fournie par les destructeurs C++), alors ils sont mauvais.

7
paul

Un excellent cas d'utilisation pour les exceptions est donc ...

Supposons que vous êtes sur un projet et que chaque contrôleur (environ 20 principaux) étend un contrôleur de superclasse unique avec une méthode d'action. Ensuite, chaque contrôleur fait un tas de choses différentes les unes des autres appelant les objets B, C, D dans un cas et F, G, D dans un autre cas. Des exceptions viennent à la rescousse ici dans de nombreux cas où il y avait des tonnes de code retour et CHAQUE contrôleur le traitait différemment. J'ai détruit tout ce code, jeté l'exception appropriée de "D", l'ai attrapé dans la méthode d'action du contrôleur de superclasse et maintenant tous nos contrôleurs sont cohérents. Auparavant, D renvoyait la valeur null pour PLUSIEURS cas d'erreur différents dont nous voulons informer l'utilisateur final, mais que nous ne pouvions pas et je ne voulais pas transformer le StreamResponse en un méchant objet ErrorOrStreamResponse (mélanger une structure de données avec des erreurs à mon avis est une mauvaise odeur et je vois beaucoup de code renvoyer un "Stream" ou un autre type d'entité avec des informations d'erreur intégrées (cela devrait vraiment être la fonction renvoie la structure de réussite OR la structure d'erreur qui Je peux le faire avec des exceptions par rapport aux codes de retour) .... bien que la manière C # de réponses multiples soit quelque chose que je pourrais parfois considérer, bien que dans de nombreux cas, l'exception puisse ignorer beaucoup de couches (couches que je n'ai pas besoin de nettoyer les ressources sur l'un ou l'autre).

oui, nous devons nous soucier de chaque niveau et de tout nettoyage/fuite de ressources, mais en général aucun de nos contrôleurs n'avait de ressources à nettoyer après.

dieu merci, nous avons eu des exceptions ou j'aurais été dans un énorme refactor et perdu trop de temps sur quelque chose qui devrait être un simple problème de programmation.

4
Dean Hiller

Théoriquement, ils sont vraiment mauvais. Dans un monde mathématique parfait, vous ne pouvez pas obtenir de situations d'exception. Regardez les langages fonctionnels, ils n'ont pas d'effets secondaires, donc ils n'ont pratiquement pas de source pour des situations non exceptionnelles.

Mais, la réalité est une autre histoire. Nous avons toujours des situations "inattendues". C'est pourquoi nous avons besoin d'exceptions.

Je pense que nous pouvons considérer les exceptions comme du sucre de syntaxe pour ExceptionSituationObserver. Vous recevez simplement des notifications d'exceptions. Rien de plus.

Avec Go, je pense qu'ils introduiront quelque chose qui traitera des situations "inattendues". Je peux deviner qu'ils essaieront de le rendre moins destructif en tant qu'exceptions et plus en tant que logique d'application. Mais c'est juste ma supposition.

2
Mike Chaliy

Je n'ai pas lu toutes les autres réponses, donc cela peut déjà être mentionné, mais une critique est qu'elles provoquent la rupture des programmes en longues chaînes, ce qui rend difficile la recherche d'erreurs lors du débogage du code. Par exemple, si Foo () appelle Bar () qui appelle Wah () qui appelle ToString (), puis en poussant accidentellement les mauvaises données dans ToString () finit par ressembler à une erreur dans Foo (), une fonction presque complètement indépendante.

1
kingfrito_5005

Le paradigme de gestion des exceptions de C++, qui constitue une base partielle pour celle de Java, et à son tour .net, présente de bons concepts, mais présente également de sérieuses limitations. L'une des principales intentions de conception de la gestion des exceptions est de permettre aux méthodes de garantir qu'elles satisferont à leurs post-conditions ou de lever une exception, et également de garantir que tout nettoyage qui doit avoir lieu avant qu'une méthode puisse se terminer se produise. Malheureusement, les paradigmes de gestion des exceptions de C++, Java et .net ne parviennent pas à fournir de bons moyens de gérer la situation où des facteurs inattendus empêchent l'exécution du nettoyage attendu. Cela signifie à son tour que l'on doit soit risquer de tout arrêter brutalement si quelque chose d'inattendu se produit (l'approche C++ pour gérer une exception se produit pendant le déroulement de la pile), accepter la possibilité qu'une condition qui ne peut pas être résolue en raison d'un problème qui s'est produit pendant le nettoyage de déroulement de pile sera confondu avec celui qui peut être résolu (et aurait pu être, si le nettoyage avait réussi), ou accepter la possibilité qu'un problème insoluble dont le nettoyage de déroulement de pile déclenche une exception qui serait typiquement résolvable, puisse aller inaperçu car le code qui gère ce dernier problème le déclare "résolu".

Même si la gestion des exceptions est généralement bonne, il n'est pas déraisonnable de considérer comme inacceptable un paradigme de gestion des exceptions qui ne fournit pas un bon moyen de gérer les problèmes qui surviennent lors du nettoyage après d'autres problèmes. Cela ne veut pas dire qu'un cadre ne pourrait pas être conçu avec un paradigme de gestion des exceptions qui pourrait garantir un comportement sensé même dans des scénarios de défaillance multiple, mais aucun des principaux langages ou cadres ne peut le faire pour l'instant.

1
supercat

D'accord, réponse ennuyeuse ici. Je suppose que cela dépend vraiment de la langue. Lorsqu'une exception peut laisser des ressources allouées, elles doivent être évitées. Dans les langages de script, ils désertent ou dépassent simplement des parties du flux d'application. C'est détestable en soi, mais échapper à des erreurs presque fatales avec des exceptions est une idée acceptable.

Pour la signalisation d'erreur, je préfère généralement les signaux d'erreur. Tout dépend de l'API, du cas d'utilisation et de la gravité, ou si la journalisation est suffisante. J'essaie également de redéfinir le comportement et throw Phonebooks() à la place. L'idée étant que les "exceptions" sont souvent des impasses, mais un "répertoire" contient des informations utiles sur la récupération des erreurs ou les autres voies d'exécution. (Pas encore trouvé un bon cas d'utilisation, mais continuez à essayer.)

0
mario

Pour moi, le problème est très simple. De nombreux programmeurs utilisent le gestionnaire d'exceptions de manière inappropriée. Plus de ressources linguistiques, c'est mieux. Être capable de gérer les exceptions, c'est bien. Un exemple de mauvaise utilisation est une valeur qui doit être un entier non vérifiée, ou une autre entrée qui peut se diviser et ne pas être vérifiée pour la division de zéro ... la gestion des exceptions peut être un moyen facile d'éviter plus de travail et de réflexion, le programmeur peut vouloir faire un raccourci sale et appliquer une gestion des exceptions ... La déclaration: "un code professionnel NE JAMAIS échoue" peut être illusoire, si certains des problèmes traités par l'algorithme sont incertains par sa propre nature. Peut-être que dans les situations inconnues, par nature, il est bon d'entrer en jeu le gestionnaire d'exceptions. Les bonnes pratiques de programmation sont un sujet de débat.

0
iuri
  • L'exception qui n'est pas gérée est généralement mauvaise.
  • L'exception mal gérée est mauvaise (bien sûr).
  • Le "bien/mal" du traitement des exceptions dépend du contexte/de la portée et de la pertinence, et non pour le faire.
0
o.k.w