Le langage Go de Google n'a pas d'exceptions en tant que choix de conception, et Linus de la renommée de Linux a appelé les exceptions de la merde. Pourquoi?
Le langage Go de Google n'a pas d'exceptions en tant que choix de conception, et Linus de la renommée de Linux a appelé les exceptions de la merde. Pourquoi?
Réponses:
Les exceptions rendent très facile 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 des déclarations que vous faites peuvent potentiellement être lancées et à les gérer correctement. Cela peut être délicat et contre-intuitif.
Considérez quelque chose comme ceci 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 la FrobManager
volonté de delete
la FrobObject
, cela semble OK, non? Ou peut-être pas ... Imaginez alors si l'un FrobManager::HandleFrob()
ou l' autre 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
va avoir un objet potentiellement 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 lancée à 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 la concurrence, 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, variablefoo
peut être incompatible avecbar
, mais votre code finira par rectifier cela. Les exceptions signifient que n'importe laquelle de vos déclarations peut vous interrompre à tout moment. Il incombe à vous 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, contribuent largement à la protection contre cela. Mais ce n'est pas une solution miracle. Il s'assurera que vous libérez des ressources lors d'un lancement, mais ne vous libère pas de devoir penser à la corruption de l'état de l'objet et aux appelants qui voient des valeurs intermédiaires. Donc, pour beaucoup de gens, c'est plus facile à dire, par décision 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 faire une erreur.
Des livres entiers ont été écrits sur le codage sécurisé des exceptions en C ++. De nombreux experts se sont trompés. Si c'est vraiment si complexe et a tant de nuances, c'est peut-être un bon signe que vous devez ignorer cette fonctionnalité. :-)
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 modèles d'exceptions ont été proposés, mais chacun ajoute une complexité significative au langage et à l'exécution. De par leur nature même, les exceptions couvrent les fonctions et peut-être même les 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 la spécification des bibliothèques et des interfaces. Ce serait bien de trouver une conception qui leur permette d'être vraiment exceptionnelles sans encourager les erreurs courantes à se transformer en flux de contrôle spécial qui oblige chaque programmeur à compenser.
Tout comme les génériques, les exceptions restent une question ouverte.
En d'autres termes, ils n'ont pas encore trouvé 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 d'exceptions à une structure de contrôle, comme dans l'idiome try-catch-finally, aboutit à un code alambiqué. Cela a également tendance à encourager les programmeurs à qualifier un trop grand nombre d'erreurs ordinaires, telles que l'échec de l'ouverture d'un fichier, d'exceptionnelles.
Go adopte une approche différente. Pour une gestion simple des erreurs, les retours à valeurs multiples de Go permettent de signaler facilement une erreur sans surcharger la valeur de retour. Un type d'erreur canonique, associé aux autres fonctionnalités de Go, rend la gestion des erreurs agréable mais assez différente de celle d'autres langues.
Go a également quelques fonctions intégrées pour signaler et récupérer des conditions vraiment exceptionnelles. Le mécanisme de récupération est exécuté uniquement dans le cadre de la destruction de l'é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 un retour à valeurs multiples. (Et ils ont de toute façon une forme de gestion des exceptions.)
... et Linus de la renommée Linux a appelé les exceptions de la merde.
Si vous voulez savoir pourquoi Linus pense que les exceptions sont de la merde, le mieux est de chercher ses écrits sur le sujet. La seule chose que j'ai retracée jusqu'à présent est cette citation qui est intégrée dans quelques e-mails 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 n'ont apparemment quelques problèmes qui les rendent difficiles à utiliser correctement.)
Ma conclusion est que Linus n'a pas du tout qualifié les exceptions (en général) de "merde"!
Les exceptions ne sont pas mauvaises en soi, mais si vous savez qu'elles vont arriver souvent, elles peuvent coûter cher en termes de performances.
La règle de base est que les exceptions doivent signaler les conditions exceptionnelles et que vous ne devez pas les utiliser pour contrôler le déroulement du programme.
Je ne suis pas d'accord avec «ne jeter des exceptions que dans une situation exceptionnelle». Bien que généralement vrai, c'est trompeur. Les exceptions concernent les conditions d' erreur (échecs d'exécution).
Quel que soit le langage 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 égal. Quelques citations de la première édition (la 2ème à mon travail):
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.
Du point de vue de golang, je suppose que le fait de ne pas avoir de gestion des exceptions rend 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 de coin. Il est donc logique de refuser les exceptions.
Les exceptions ont du sens dans le code s'il est acceptable de laisser tomber la tâche actuelle sur le sol, et lorsque 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 à la plupart des codes de haut niveau destinés aux utilisateurs, tels que le code d'application Web et de bureau.
Les exceptions en elles-mêmes ne sont pas «mauvaises», c'est la façon dont les exceptions sont parfois gérées qui a tendance à être mauvaise. Plusieurs directives peuvent être appliquées lors de la gestion des exceptions pour aider à résoudre certains de ces problèmes. Certains d'entre eux incluent (mais ne sont sûrement pas limités à):
Option<T>
plutôt que de null
nos jours. Je viens d'être introduit dans Java 8 par exemple, en prenant un indice de Guava (et d'autres).
Les arguments typiques sont qu'il n'y a aucun moyen de dire quelles exceptions sortiront d'un morceau de code particulier (selon la langue) et qu'elles ressemblent trop à goto
s, 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 hard-core comme Linus, les exceptions sont définitivement une mauvaise idée. Un programmeur Java typique se trouve cependant dans une situation très différente.
setjmp
/ longjmp
, ce qui est plutôt mauvais.
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 du C ++. Si vous avez déjà un tas de code qui n'est pas exceptionnellement sûr, alors ils sont mauvais dans ce contexte. Si vous écrivez des logiciels de très bas niveau, comme le système d'exploitation Linux, alors ils sont mauvais. Si vous aimez joncher votre code avec un tas de vérifications de retour d'erreur, alors elles ne sont pas utiles. Si vous n'avez pas de plan pour le contrôle des ressources lorsqu'une exception est levée (que les destructeurs C ++ fournissent), elles sont mauvaises.
Un bon cas d'utilisation des exceptions est donc ...
Supposons que vous soyez sur un projet et que chaque contrôleur (environ 20 principaux différents) étend un seul contrôleur de superclasse avec une méthode d'action. Ensuite, chaque contrôleur fait un tas de choses différentes les unes des autres en appelant les objets B, C, D dans un cas et F, G, D dans un autre cas. Des exceptions viennent à la rescousse dans de nombreux cas où il y avait des tonnes de code de retour et que CHAQUE contrôleur le traitait différemment. J'ai frappé tout ce code, jeté l'exception appropriée de "D", l'ai attrapé dans la méthode d'action du contrôleur de la superclasse et maintenant tous nos contrôleurs sont cohérents. Auparavant, D renvoyait null pour PLUSIEURS cas d'erreur différents dont nous voulons informer l'utilisateur final mais que nous ne pouvions pas et je ne l'ai pas fait '
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 j'ai perdu trop de temps sur quelque chose qui devrait être un simple problème de programmation.
Théoriquement, ils sont vraiment mauvais. Dans un monde mathématique parfait, vous ne pouvez pas avoir 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 penser aux exceptions comme au sucre de syntaxe pour ExceptionSituationObserver. Vous recevez juste 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 rendre cela moins destructeur comme exceptions et plus comme logique d'application. Mais ce n'est que ma supposition.
Le paradigme de gestion des exceptions de C ++, qui forme une base partielle pour celui de Java, et à son tour .net, introduit quelques 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 s'assurer qu'elles satisferont leurs post-conditions ou de lever une exception, et également de s'assurer que tout nettoyage qui doit se produire avant qu'une méthode puisse se fermer, se produira. Malheureusement, les paradigmes de gestion des exceptions de C ++, Java et .net échouent tous à fournir un bon moyen de gérer la situation où des facteurs inattendus empêchent le nettoyage attendu. Cela signifie à son tour qu'il faut soit risquer que tout s'arrête brutalement si quelque chose d'inattendu se produit (l'approche C ++ pour gérer une exception se produit lors du déroulement de la pile),
Même si la gestion des exceptions serait 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 framework ne pourrait pas être conçu avec un paradigme de gestion des exceptions qui pourrait garantir un comportement raisonnable même dans des scénarios de défaillance multiple, mais aucun des principaux langages ou frameworks ne peut encore le faire.
Je n'ai pas lu toutes les autres réponses, donc cela peut déjà avoir été mentionné, mais une critique est qu'elles provoquent la rupture des programmes en longues chaînes, ce qui rend difficile la traçabilité des erreurs lors du débogage du code. Par exemple, si Foo () appelle Bar () qui appelle Wah () qui appelle ToString (), le fait de pousser accidentellement les mauvaises données dans ToString () finit par ressembler à une erreur dans Foo (), une fonction presque totalement indépendante.
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 derrière, elle doit être évitée. Dans les langages de script, ils abandonnent ou surchargent des parties du flux d'application. Cela n'est pas apprécié 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 suffit. J'essaye aussi de redéfinir le comportement et à la throw Phonebooks()
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 d'erreurs ou d'autres voies d'exécution. (Pas encore trouvé de bon cas d'utilisation, mais continuez à essayer.)
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 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 n'échoue JAMAIS" 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 font débat.