De nombreuses exigences sont nécessaires pour qu'un système transmette et gère correctement les exceptions. Il existe également de nombreuses options pour une langue à choisir pour implémenter le concept.
Conditions d'exceptions (sans ordre particulier):
Documentation : un langage doit avoir un moyen de documenter les exceptions qu'une API peut lancer. Idéalement, ce support de documentation devrait être utilisable par la machine pour permettre aux compilateurs et aux IDE de fournir une assistance au programmeur.
Transmettre des situations exceptionnelles : celle-ci est évidente, pour permettre à une fonction de transmettre des situations qui empêchent la fonctionnalité appelée d'exécuter l'action attendue. À mon avis, il existe trois grandes catégories de telles situations:
2.1 Bogues dans le code qui rendent certaines données invalides.
2.2 Problèmes de configuration ou d'autres ressources externes.
2.3 Ressources intrinsèquement peu fiables (réseau, systèmes de fichiers, bases de données, utilisateurs finaux, etc.). Ce sont un peu un cas de coin, car leur nature peu fiable devrait nous faire attendre leurs échecs sporadiques. Dans ce cas, ces situations doivent-elles être considérées comme exceptionnelles?
Fournissez suffisamment d'informations pour que le code le gère : les exceptions doivent fournir suffisamment d'informations à l'appelé pour qu'il puisse réagir et éventuellement gérer la situation. les informations doivent également être suffisantes pour que, lorsqu'elles sont consignées, ces exceptions fournissent suffisamment de contexte à un programmeur pour identifier et isoler les déclarations incriminées et fournir une solution.
Donner confiance au programmeur quant à l'état actuel de l'état d'exécution de son code : les capacités de gestion des exceptions d'un système logiciel doivent être suffisamment présentes pour fournir les garanties nécessaires tout en restant à l'écart du programmeur afin qu'il puisse rester concentré sur la tâche à main.
Pour les couvrir, les méthodes suivantes ont été mises en œuvre dans différentes langues:
Exceptions vérifiées Fournir un excellent moyen de documenter les exceptions, et théoriquement, lorsqu'elles sont implémentées correctement, elles devraient rassurer amplement que tout va bien. Cependant, le coût est tel que beaucoup pensent qu'il est plus productif de simplement contourner soit en avalant des exceptions, soit en les renvoyant en tant qu'exceptions non contrôlées. Lorsqu'elles sont utilisées de manière inappropriée, les exceptions perdent à peu près tout son utilité. De plus, les exceptions vérifiées rendent difficile la création d'une API stable dans le temps. Les implémentations d'un système générique dans un domaine spécifique entraîneront une charge de situation exceptionnelle qui deviendrait difficile à maintenir en utilisant uniquement des exceptions vérifiées.
Exceptions non vérifiées - beaucoup plus polyvalentes que les exceptions vérifiées, elles ne documentent pas correctement les situations exceptionnelles possibles d'une implémentation donnée. Ils s'appuient sur une documentation ad hoc le cas échéant. Cela crée des situations où la nature peu fiable d'un support est masquée par une API qui donne l'apparence de la fiabilité. De plus, lorsqu'elles sont levées, ces exceptions perdent leur sens lorsqu'elles remontent à travers les couches d'abstraction. Étant donné qu'ils sont mal documentés, un programmeur ne peut pas les cibler spécifiquement et doit souvent lancer un réseau beaucoup plus large que nécessaire pour garantir que les systèmes secondaires, en cas de défaillance, n'effondrent pas tout le système. Ce qui nous ramène au problème de déglutition des exceptions vérifiées fournies.
Types de retour multi-états Ici, il s'agit de s'appuyer sur un ensemble disjoint, un tuple ou un autre concept similaire pour renvoyer le résultat attendu ou un objet représentant l'exception. Ici pas de déroulement de pile, pas de coupure du code, tout s'exécute normalement mais la valeur de retour doit être validée pour erreur avant de continuer. Je n'ai pas encore vraiment travaillé avec cela, donc je ne peux pas commenter par l'expérience.Je reconnais que cela résout certains problèmes, les exceptions contournant le flux normal, mais il souffrira à peu près des mêmes problèmes que les exceptions vérifiées, car il est fatigant et constamment "en face".
La question est donc:
Quelle est votre expérience en la matière et quel est, selon vous, le meilleur candidat pour faire un bon système de gestion des exceptions pour une langue?
EDIT: Quelques minutes après avoir écrit cette question, je suis tombé sur ce post , effrayant!
la source
noexcept
histoire en C ++ peut également fournir de très bonnes informations pour EH en C # et Java.)Réponses:
Dans les premiers jours du C ++, nous avons découvert que sans une sorte de programmation générique, les langages fortement typés étaient extrêmement lourds. Nous avons également découvert que les exceptions vérifiées et la programmation générique ne fonctionnaient pas bien ensemble, et les exceptions vérifiées étaient essentiellement abandonnées.
Les types de retour multisets sont excellents, mais ne remplacent pas les exceptions. Sans exception, le code est plein de bruit de vérification d'erreur.
L'autre problème avec les exceptions vérifiées est qu'un changement dans les exceptions levées par une fonction de bas niveau force une cascade de changements dans tous les appelants et leurs appelants, etc. La seule façon d'empêcher cela est que chaque niveau de code intercepte les exceptions levées par les niveaux inférieurs et les encapsule dans une nouvelle exception. Encore une fois, vous vous retrouvez avec un code très bruyant.
la source
Pendant longtemps les langages OO, l'utilisation d'exceptions a été de facto la norme pour communiquer les erreurs. Mais les langages de programmation fonctionnels offrent la possibilité d'une approche différente, par exemple en utilisant des monades (que je n'ai pas utilisées), ou la "programmation orientée ferroviaire" plus légère, comme décrit par Scott Wlaschin.
Dans cet article de blog
Dans cette présentation au NDC 2014
C'est vraiment une variante du type de résultat multi-états.
Le type de résultat pourrait être déclaré comme ceci
Ainsi, le résultat d'une fonction qui renvoie ce type serait soit un,
Success
soit unFail
type. Ça ne peut pas être les deux.Dans les langages de programmation plus impératifs, ce type de style peut nécessiter une grande quantité de code sur le site de l'appelant. Mais la programmation fonctionnelle vous permet de construire des fonctions de liaison ou des opérateurs pour lier plusieurs fonctions afin que la vérification des erreurs ne prenne pas la moitié du code. Par exemple:
La
updateUser
fonction appelle chacune de ces fonctions successivement et chacune d'entre elles peut échouer. S'ils réussissent tous, le résultat de la dernière fonction appelée est renvoyé. Si l'une des fonctions échoue, le résultat de cette fonction sera le résultat de laupdateUser
fonction globale . Tout cela est géré par l'opérateur >> = personnalisé.Dans l'exemple ci-dessus, les types d'erreur peuvent être
Si l'appelant de
updateUser
ne gère pas explicitement toutes les erreurs possibles de la fonction, le compilateur émet un avertissement. Vous avez donc tout documenté.Dans Haskell, il existe une
do
notation qui peut rendre le code encore plus propre.la source
do
notation de Haskell , qui rend le code résultant encore plus propre.Railway Oriented Programming
comportement est exactement monadique.Je trouve la réponse de Pete très bonne et j'aimerais ajouter quelques considérations et un exemple. Une discussion très intéressante concernant l'utilisation des exceptions par rapport au retour de valeurs d'erreur spéciales peut être trouvée dans Programmation en standard ML, par Robert Harper , à la fin de la section 29.3, page 243, 244.
Le problème est d'implémenter une fonction partielle
f
retournant une valeur d'un certain typet
. Une solution consiste à avoir le type de fonctionet lève une exception lorsqu'il n'y a aucun résultat possible. La deuxième solution consiste à implémenter une fonction de type
et retour
SOME v
sur succès etNONE
sur échec.Voici le texte du livre, avec une petite adaptation faite par moi-même pour rendre le texte plus général (le livre fait référence à un exemple particulier). Le texte modifié est écrit en italique .
Ceci en ce qui concerne le choix entre les exceptions et les types de retour d'options.
En ce qui concerne l'idée que représenter une erreur dans le type de retour conduit à des vérifications d'erreurs réparties dans tout le code: ce n'est pas nécessairement le cas. Voici un petit exemple à Haskell qui illustre cela.
Supposons que nous voulions analyser deux nombres, puis diviser le premier par le second. Il pourrait donc y avoir une erreur lors de l'analyse de chaque nombre, ou lors de la division (division par zéro). Nous devons donc vérifier une erreur après chaque étape.
L'analyse et la division sont effectuées dans le
let ...
bloc. Notez qu'en utilisant laMaybe
monade et lado
notation, seul le chemin de réussite est spécifié: la sémantique de laMaybe
monade propage implicitement la valeur d'erreur (Nothing
). Pas de frais généraux pour le programmeur.la source
Either
type serait plus approprié. Que faites-vous si vous arrivezNothing
ici? Vous obtenez simplement le message "erreur". Pas très utile pour le débogage.Je suis devenu un grand fan des exceptions vérifiées et je voudrais partager ma règle générale sur le moment de les utiliser.
Je suis arrivé à la conclusion qu'il y a essentiellement 2 types d'erreurs que mon code doit traiter. Il y a des erreurs qui peuvent être testées avant que le code s'exécute et il y a des erreurs qui ne sont pas testables avant que le code s'exécute. Un exemple simple pour une erreur qui peut être testée avant l'exécution du code dans une NullPointerException.
Un simple test aurait pu éviter l'erreur comme ...
Il y a des moments dans l'informatique où vous pouvez exécuter un ou plusieurs tests avant d'exécuter le code pour vous assurer que vous êtes en sécurité ET VOUS OBTIENDREZ TOUJOURS UNE EXCEPTION. Par exemple, vous pouvez tester un système de fichiers pour vous assurer qu'il y a suffisamment d'espace disque sur le disque dur avant d'écrire vos données sur le lecteur. Dans un système d'exploitation multiprocesseur, comme ceux utilisés aujourd'hui, votre processus pourrait tester l'espace disque et le système de fichiers renverra une valeur indiquant qu'il y a suffisamment d'espace, puis un changement de contexte vers un autre processus pourrait écrire les octets restants disponibles pour le système d'exploitation. système. Lorsque le contexte du système d'exploitation revient à votre processus en cours où vous écrivez votre contenu sur le disque, une exception se produit simplement parce qu'il n'y a pas assez d'espace disque sur le système de fichiers.
Je considère le scénario ci-dessus comme un cas parfait pour une exception vérifiée. C'est une exception dans le code qui vous oblige à faire face à quelque chose de mauvais même si votre code peut être parfaitement écrit. Si vous choisissez de faire de mauvaises choses comme «avaler l'exception», vous êtes le mauvais programmeur. Soit dit en passant, j'ai trouvé des cas où il est raisonnable d'avaler l'exception, mais veuillez laisser un commentaire dans le code expliquant pourquoi l'exception a été avalée. Le mécanisme de gestion des exceptions n'est pas à blâmer. Je plaisante souvent en disant que je préférerais que mon stimulateur cardiaque soit écrit avec un langage qui a des exceptions vérifiées.
Il y a des moments où il devient difficile de décider si le code est testable ou non. Par exemple, si vous écrivez un interpréteur et qu'une SyntaxException est levée lorsque le code ne s'exécute pas pour une raison syntaxique, la SyntaxException doit-elle être une exception vérifiée ou (en Java) une RuntimeException? Je répondrais que si l'interpréteur vérifie la syntaxe du code avant que le code ne soit exécuté, l'exception devrait être une RuntimeException. Si l'interpréteur exécute simplement le code «à chaud» et frappe simplement une erreur de syntaxe, je dirais que l'exception devrait être une exception vérifiée.
Je dois admettre que je ne suis pas toujours heureux de devoir attraper ou lancer une exception vérifiée car il y a du temps où je ne sais pas quoi faire. Les exceptions vérifiées sont un moyen de forcer un programmeur à être conscient du problème potentiel qui peut se produire. L'une des raisons pour lesquelles je programme en Java est qu'il contient des exceptions vérifiées.
la source
Je suis actuellement au milieu d'un projet / API basé sur la POO plutôt grand et j'ai utilisé cette disposition des exceptions. Mais tout dépend vraiment de la profondeur à laquelle vous voulez aller avec la gestion des exceptions et autres.
ExpectedException
- AuthorisedException
- EmptySetException
- NoRemainingException
- NoRowsException
- NotFoundException
- ValidationException
Exception inattendue
- ConnectivityException
- EnvironmentException
- ProgrammerException
- SQLException
EXEMPLE
la source
NAN
ouNULL
.