Depuis un certain nombre d'années, je n'ai pas pu obtenir une réponse décente à la question suivante: pourquoi certains développeurs sont-ils si opposés aux exceptions vérifiées? J'ai eu de nombreuses conversations, lu des choses sur des blogs, lu ce que Bruce Eckel avait à dire (la première personne que j'ai vue se prononcer contre eux).
J'écris actuellement du nouveau code et je fais très attention à la façon dont je traite les exceptions. J'essaie de voir le point de vue de la foule «nous n'aimons pas les exceptions vérifiées» et je ne le vois toujours pas.
Chaque conversation que j'ai se termine avec la même question sans réponse ... permettez-moi de la configurer:
En général (d'après la conception de Java),
Error
est pour des choses qui ne devraient jamais être attrapées (VM a une allergie aux arachides et quelqu'un a laissé tomber un pot d'arachides dessus)RuntimeException
est pour des choses que le programmeur a mal faites (le programmeur est sorti de la fin d'un tableau)Exception
(saufRuntimeException
) concerne les éléments hors du contrôle du programmeur (le disque se remplit lors de l'écriture dans le système de fichiers, la limite de traitement des fichiers pour le processus a été atteinte et vous ne pouvez plus ouvrir de fichiers)Throwable
est simplement le parent de tous les types d'exceptions.
Un argument commun que j'entends est que si une exception se produit, tout ce que le développeur va faire est de quitter le programme.
Un autre argument commun que j'entends est que les exceptions vérifiées rendent plus difficile la refactorisation du code.
Pour l'argument "tout ce que je vais faire, c'est quitter", je dis que même si vous quittez, vous devez afficher un message d'erreur raisonnable. Si vous tentez simplement de gérer les erreurs, vos utilisateurs ne seront pas trop satisfaits lorsque le programme se fermera sans une indication claire de pourquoi.
Pour la foule «il est difficile de refactoriser», cela indique que le niveau d'abstraction approprié n'a pas été choisi. Plutôt que de déclarer qu'une méthode lance un IOException
, le IOException
devrait être transformé en une exception plus adaptée à ce qui se passe.
Je n'ai pas de problème avec le wrapping de Main avec catch(Exception)
(ou dans certains cas catch(Throwable)
pour m'assurer que le programme peut se terminer correctement - mais j'attrape toujours les exceptions spécifiques dont j'ai besoin. Cela me permet, à tout le moins, d'afficher une Message d'erreur.
La question à laquelle les gens ne répondent jamais est la suivante:
Si vous lancez des
RuntimeException
sous-classes au lieu deException
sous - classes, alors comment savez-vous ce que vous êtes censé attraper?
Si la réponse est catch, Exception
vous traitez également les erreurs de programmation de la même manière que les exceptions système. Cela me semble mal.
Si vous interceptez, Throwable
vous traitez les exceptions système et les erreurs de machine virtuelle (et similaires) de la même manière. Cela me semble mal.
Si la réponse est que vous n'attrapez que les exceptions dont vous savez qu'elles sont levées, comment savez-vous celles qui sont levées? Que se passe-t-il lorsque le programmeur X lève une nouvelle exception et oublie de l'attraper? Cela me semble très dangereux.
Je dirais qu'un programme qui affiche une trace de pile est faux. Les personnes qui n'aiment pas les exceptions vérifiées ne le pensent-elles pas?
Donc, si vous n'aimez pas les exceptions vérifiées, pouvez-vous expliquer pourquoi pas ET répondre à la question qui n'a pas de réponse s'il vous plaît?
Edit: Je ne cherche pas de conseils sur le moment d'utiliser l'un ou l'autre modèle, ce que je cherche, c'est pourquoi les gens s'étendent à partir de RuntimeException
parce qu'ils n'aiment pas s'étendre à partir de Exception
et / ou pourquoi ils attrapent une exception, puis rejettent un RuntimeException
plutôt que d'ajouter des lancers à leur méthode. Je veux comprendre la motivation pour ne pas aimer les exceptions cochées.
la source
Réponses:
Je pense que j'ai lu la même interview de Bruce Eckel que vous avez faite - et cela m'a toujours dérangé. En fait, l'argument a été avancé par la personne interrogée (si c'est effectivement le poste dont vous parlez) Anders Hejlsberg, le génie MS derrière .NET et C #.
Fan que je sois de Hejlsberg et de son travail, cet argument m'a toujours semblé faux. Cela se résume essentiellement à:
Par "autrement présenté à l'utilisateur" je veux dire que si vous utilisez une exception d'exécution, le programmeur paresseux l'ignorera simplement (par opposition à l'attraper avec un bloc catch vide) et l'utilisateur le verra.
Le résumé du résumé de l'argument est que "les programmeurs ne les utiliseront pas correctement et ne pas les utiliser correctement est pire que de ne pas les avoir" .
Il y a une part de vérité dans cet argument et en fait, je soupçonne que la motivation de Goslings pour ne pas mettre des remplacements d'opérateurs en Java vient d'un argument similaire - ils confondent le programmeur car ils sont souvent abusés.
Mais à la fin, je trouve que c'est un faux argument de Hejlsberg et peut-être post-hoc créé pour expliquer le manque plutôt qu'une décision mûrement réfléchie.
Je dirais que, même si la surutilisation des exceptions vérifiées est une mauvaise chose et a tendance à entraîner une gestion bâclée par les utilisateurs, mais leur utilisation appropriée permet au programmeur API de donner un grand avantage au programmeur client API.
Maintenant, le programmeur API doit faire attention à ne pas lancer d'exceptions vérifiées partout, sinon ils ennuieront simplement le programmeur client. Le programmeur client très paresseux aura recours à la capture
(Exception) {}
comme Hejlsberg l'avertit et tous les avantages seront perdus et l'enfer s'ensuivra. Mais dans certaines circonstances, rien ne peut remplacer une bonne exception vérifiée.Pour moi, l'exemple classique est l'API d'ouverture de fichier. Chaque langage de programmation de l'histoire des langages (sur les systèmes de fichiers au moins) possède une API quelque part qui vous permet d'ouvrir un fichier. Et chaque programmeur client utilisant cette API sait qu'il doit faire face au cas où le fichier qu'il essaie d'ouvrir n'existe pas. Permettez-moi de reformuler cela: Chaque programmeur client utilisant cette API doit savoir qu'il doit gérer ce cas. Et il y a le hic: le programmeur d'API peut-il les aider à savoir qu'ils doivent y faire face en commentant seuls ou peut-il en effet insister le client s'en occupe.
En C, l'idiome va quelque chose comme
où
fopen
indique l'échec en renvoyant 0 et C (bêtement) vous permet de traiter 0 comme un booléen et ... Fondamentalement, vous apprenez cet idiome et vous êtes d'accord. Mais si vous êtes un noob et que vous n'avez pas appris l'idiome. Ensuite, bien sûr, vous commencez avecet apprenez à la dure.
Notez que nous ne parlons ici que de langages fortement typés: il y a une idée claire de ce qu'est une API dans un langage fortement typé: c'est un large éventail de fonctionnalités (méthodes) à utiliser avec un protocole clairement défini pour chacune.
Ce protocole clairement défini est généralement défini par une signature de méthode. Ici, fopen requiert que vous lui passiez une chaîne (ou un caractère * dans le cas de C). Si vous lui donnez autre chose, vous obtenez une erreur de compilation. Vous n'avez pas suivi le protocole - vous n'utilisez pas correctement l'API.
Dans certaines langues (obscures), le type de retour fait également partie du protocole. Si vous essayez d'appeler l'équivalent de
fopen()
dans certaines langues sans l'assigner à une variable, vous obtiendrez également une erreur de compilation (vous ne pouvez le faire qu'avec des fonctions void).Le point que j'essaie de faire est le suivant: dans un langage de type statique, le programmeur d'API encourage le client à utiliser correctement l'API en empêchant son code client de se compiler s'il fait des erreurs évidentes.
(Dans un langage typé dynamiquement, comme Ruby, vous pouvez passer n'importe quoi, par exemple un flottant, comme nom de fichier - et il se compilera. Pourquoi harceler l'utilisateur avec des exceptions vérifiées si vous ne contrôlez même pas les arguments de la méthode. les arguments avancés ici ne s'appliquent qu'aux langages de type statique.)
Alors, qu'en est-il des exceptions vérifiées?
Eh bien, voici l'une des API Java que vous pouvez utiliser pour ouvrir un fichier.
Vous voyez cette prise? Voici la signature de cette méthode API:
Notez qu'il
FileNotFoundException
s'agit d'une exception vérifiée .Le programmeur API vous dit ceci: "Vous pouvez utiliser ce constructeur pour créer un nouveau FileInputStream mais vous
a) doit transmettre le nom du fichier sous forme de chaîne
b) doit accepter la possibilité que le fichier ne soit pas trouvé lors de l'exécution "
Et c'est tout le point en ce qui me concerne.
La clé est fondamentalement ce que la question indique comme "des choses qui sont hors du contrôle du programmeur". Ma première pensée a été qu'il veut dire des choses qui ne sont pas dans l' API contrôle des programmeurs d' . Mais en fait, les exceptions vérifiées lorsqu'elles sont utilisées correctement devraient vraiment concerner des choses qui sont hors du contrôle du programmeur client et du programmeur API. Je pense que c'est la clé pour ne pas abuser des exceptions vérifiées.
Je pense que le fichier ouvert illustre bien le point. Le programmeur d'API sait que vous pourriez leur donner un nom de fichier qui s'avère inexistant au moment de l'appel de l'API, et qu'il ne pourra pas vous retourner ce que vous vouliez, mais devra lever une exception. Ils savent également que cela se produira assez régulièrement et que le programmeur client peut s'attendre à ce que le nom de fichier soit correct au moment où il a écrit l'appel, mais il peut aussi être erroné au moment de l'exécution pour des raisons indépendantes de sa volonté.
Donc, l'API le rend explicite: il y aura des cas où ce fichier n'existe pas au moment où vous m'appelez et vous feriez bien mieux de le gérer.
Ce serait plus clair avec un contre-cas. Imaginez que j'écris une API de table. J'ai le modèle de table quelque part avec une API incluant cette méthode:
Maintenant, en tant que programmeur d'API, je sais qu'il y aura des cas où certains clients transmettront une valeur négative pour la ligne ou une valeur de ligne en dehors de la table. Je pourrais donc être tenté de lever une exception vérifiée et d'obliger le client à y faire face:
(Je ne l'appellerais pas vraiment "Checked" bien sûr.)
C'est une mauvaise utilisation des exceptions vérifiées. Le code client va être plein d'appels pour récupérer les données de ligne, chacun d'entre eux devra utiliser un try / catch, et pour quoi faire? Vont-ils signaler à l'utilisateur que la mauvaise ligne a été recherchée? Probablement pas - car quelle que soit l'interface utilisateur entourant ma vue de table, elle ne devrait pas laisser l'utilisateur entrer dans un état où une ligne illégale est demandée. C'est donc un bug de la part du programmeur client.
Le programmeur API peut toujours prédire que le client va coder de tels bogues et devrait le gérer avec une exception d'exécution comme un
IllegalArgumentException
.Avec une exception cochée
getRowData
, c'est clairement un cas qui va conduire le programmeur paresseux de Hejlsberg à simplement ajouter des captures vides. Lorsque cela se produit, les valeurs de ligne illégales ne seront pas évidentes même pour le testeur ou le débogage du développeur client, mais elles entraîneront plutôt des erreurs d'activation difficiles à localiser. Les roquettes Arianne exploseront après leur lancement.D'accord, voici donc le problème: je dis que l'exception vérifiée
FileNotFoundException
n'est pas seulement une bonne chose mais un outil essentiel dans la boîte à outils des programmeurs d'API pour définir l'API de la manière la plus utile pour le programmeur client. MaisCheckedInvalidRowNumberException
c'est un gros inconvénient, conduisant à une mauvaise programmation et doit être évité. Mais comment faire la différence.Je suppose que ce n'est pas une science exacte et je suppose que cela sous-tend et peut-être justifie dans une certaine mesure l'argument de Hejlsberg. Mais je ne suis pas content de jeter le bébé avec l'eau du bain ici, alors permettez-moi d'extraire quelques règles ici pour distinguer les bonnes exceptions vérifiées des mauvaises:
Hors du contrôle du client ou fermé vs ouvert:
Les exceptions cochées ne doivent être utilisées que lorsque le cas d'erreur est hors de contrôle à la fois de l'API et du programmeur client. Cela a à voir avec l' ouverture ou la fermeture du système. Dans une interface utilisateur contrainte où le programmeur client a le contrôle, par exemple, sur tous les boutons, commandes clavier, etc. qui ajoutent et suppriment des lignes de la vue de table (un système fermé), il s'agit d'un bogue de programmation client s'il tente d'extraire des données de une ligne inexistante. Dans un système d'exploitation basé sur des fichiers où un nombre illimité d'utilisateurs / applications peuvent ajouter et supprimer des fichiers (un système ouvert), il est concevable que le fichier que le client demande a été supprimé à leur insu, de sorte qu'ils devraient être censés y faire face. .
Ubiquité:
Les exceptions cochées ne doivent pas être utilisées sur un appel d'API effectué fréquemment par le client. Par fréquemment, je veux dire de beaucoup d'endroits dans le code client - pas souvent à temps. Donc, un code client n'a pas tendance à essayer d'ouvrir beaucoup le même fichier, mais ma vue de table se fait
RowData
partout à partir de différentes méthodes. En particulier, je vais écrire beaucoup de code commeet il sera douloureux de devoir en finir avec try / catch à chaque fois.
Informer l'utilisateur:
Les exceptions cochées doivent être utilisées dans les cas où vous pouvez imaginer qu'un message d'erreur utile soit présenté à l'utilisateur final. C'est le "et que ferez-vous quand cela se produira?" question que j'ai soulevée ci-dessus. Il se rapporte également au point 1. Puisque vous pouvez prévoir que quelque chose en dehors de votre système API client pourrait empêcher le fichier d'être là, vous pouvez raisonnablement en informer l'utilisateur:
Étant donné que votre numéro de ligne illégal a été provoqué par un bogue interne et sans faute de l'utilisateur, il n'y a vraiment aucune information utile que vous pouvez leur donner. Si votre application ne laisse pas les exceptions d'exécution passer par la console, elle finira probablement par leur donner un message laid comme:
En bref, si vous ne pensez pas que votre programmeur client peut expliquer votre exception d'une manière qui aide l'utilisateur, vous ne devriez probablement pas utiliser d'exception vérifiée.
Voilà donc mes règles. Un peu artificiel, et il y aura sans doute des exceptions (veuillez m'aider à les affiner si vous le souhaitez). Mais mon argument principal est qu'il existe des cas comme ceux
FileNotFoundException
où l'exception vérifiée est une partie du contrat API aussi importante et utile que les types de paramètres. Nous ne devons donc pas nous en passer simplement parce qu'il est mal utilisé.Désolé, je ne voulais pas rendre cela si long et gaufré. Permettez-moi de terminer par deux suggestions:
R: Programmeurs d'API: utilisez les exceptions vérifiées avec parcimonie pour préserver leur utilité. En cas de doute, utilisez une exception non vérifiée.
B: Programmeurs clients: prenez l'habitude de créer une exception encapsulée (google it) dès le début de votre développement. JDK 1.4 et versions ultérieures fournissent un constructeur
RuntimeException
pour cela, mais vous pouvez également créer facilement le vôtre. Voici le constructeur:Ensuite, prenez l'habitude de gérer une exception vérifiée et vous vous sentez paresseux (ou vous pensez que le programmeur d'API était trop zélé en utilisant l'exception vérifiée en premier lieu), ne vous contentez pas d'avaler l'exception, enveloppez-la et le repousser.
Mettez-le dans l'un des petits modèles de code de votre IDE et utilisez-le lorsque vous vous sentez paresseux. De cette façon, si vous avez vraiment besoin de gérer l'exception vérifiée, vous serez obligé de revenir et de le traiter après avoir vu le problème lors de l'exécution. Parce que, croyez-moi (et Anders Hejlsberg), vous ne reviendrez jamais à ce TODO dans votre
la source
La chose au sujet des exceptions vérifiées est qu'elles ne sont pas vraiment des exceptions par la compréhension habituelle du concept. Il s'agit plutôt de valeurs de retour alternatives de l'API.
L'idée générale des exceptions est qu'une erreur lancée quelque part en bas de la chaîne d'appels peut bouillonner et être gérée par du code quelque part plus haut, sans que le code intermédiaire n'ait à s'en soucier. Les exceptions vérifiées, en revanche, nécessitent que chaque niveau de code entre le lanceur et le receveur déclare qu'il connaît toutes les formes d'exceptions qui peuvent les traverser. C'est très peu différent dans la pratique si les exceptions vérifiées n'étaient que des valeurs de retour spéciales que l'appelant devait vérifier. par exemple. [pseudocode]:
Puisque Java ne peut pas faire de valeurs de retour alternatives, ou de simples tuples en ligne comme valeurs de retour, les exceptions vérifiées sont une réponse raisonnable.
Le problème est que beaucoup de code, y compris une grande partie de la bibliothèque standard, utilise à mauvais escient les exceptions vérifiées pour des conditions exceptionnelles réelles que vous pourriez très bien vouloir rattraper plusieurs niveaux. Pourquoi IOException n'est-il pas une RuntimeException? Dans toutes les autres langues, je peux laisser une exception d'E / S se produire, et si je ne fais rien pour la gérer, mon application s'arrêtera et j'obtiendrai une trace de pile pratique à consulter. C'est la meilleure chose qui puisse arriver.
Peut-être deux méthodes par rapport à l'exemple que vous souhaitez capturer toutes les IOExceptions de l'ensemble du processus d'écriture en flux, abandonner le processus et sauter dans le code de rapport d'erreurs; en Java, vous ne pouvez pas faire cela sans ajouter «jette IOException» à chaque niveau d'appel, même les niveaux qui eux-mêmes ne font pas d'E / S. De telles méthodes ne devraient pas avoir besoin de connaître la gestion des exceptions; avoir à ajouter des exceptions à leurs signatures:
Et puis il y a beaucoup d'exceptions de bibliothèques tout simplement ridicules comme:
Lorsque vous devez encombrer votre code avec des saletés ridicules comme celle-ci, il n'est pas étonnant que les exceptions vérifiées reçoivent un tas de haine, même si ce n'est vraiment qu'une mauvaise conception d'API.
Un autre mauvais effet particulier est sur l'inversion de contrôle, où le composant A fournit un rappel au composant générique B. Le composant A veut pouvoir laisser une exception renvoyer de son rappel à l'endroit où il a appelé le composant B, mais il ne peut pas car cela changerait l'interface de rappel qui est fixée par B. A ne peut le faire qu'en encapsulant la véritable exception dans une RuntimeException, qui est encore plus un passe-partout de gestion des exceptions à écrire.
Les exceptions vérifiées telles qu'implémentées dans Java et sa bibliothèque standard signifient passe-partout, passe-partout, passe-partout. Dans une langue déjà verbeuse, ce n'est pas une victoire.
la source
Plutôt que de ressasser toutes les (nombreuses) raisons contre les exceptions vérifiées, je n'en choisirai qu'une. J'ai perdu le compte du nombre de fois où j'ai écrit ce bloc de code:
99% du temps, je ne peux rien y faire. Enfin, les blocs effectuent tout nettoyage nécessaire (ou du moins ils le devraient).
J'ai également perdu le compte du nombre de fois où j'ai vu ceci:
Pourquoi? Parce que quelqu'un devait y faire face et était paresseux. C'était mal? Sûr. Cela arrive-t-il? Absolument. Et si c'était une exception non vérifiée à la place? L'application vient de mourir (ce qui est préférable à une exception).
Et puis nous avons un code exaspérant qui utilise des exceptions comme une forme de contrôle de flux, comme le fait java.text.Format . Bzzzt. Faux. Un utilisateur mettant "abc" dans un champ numérique d'un formulaire ne fait pas exception.
Ok, je suppose que c'était trois raisons.
la source
Je sais que c'est une vieille question, mais j'ai passé un certain temps à lutter avec les exceptions vérifiées et j'ai quelque chose à ajouter. Veuillez me pardonner pour la longueur de celui-ci!
Mon bœuf principal, avec des exceptions vérifiées, est qu'il ruine le polymorphisme. Il est impossible de les faire jouer correctement avec des interfaces polymorphes.
Prenez la bonne
List
interface Java . Nous avons des implémentations en mémoire communes commeArrayList
etLinkedList
. Nous avons également la classe skeletalAbstractList
qui facilite la conception de nouveaux types de liste. Pour une liste en lecture seule, nous devons implémenter seulement deux méthodes:size()
etget(int index)
.Cet exemple de
WidgetList
classe lit certains objets de taille fixe de typeWidget
(non représentés) dans un fichier:En exposant les Widgets à l'aide de l'
List
interface familière , vous pouvez récupérer des éléments (list.get(123)
) ou itérer une liste (for (Widget w : list) ...
) sans avoir besoin de se connaîtreWidgetList
. On peut transmettre cette liste à toutes les méthodes standard qui utilisent des listes génériques, ou l'encapsuler dans unCollections.synchronizedList
. Le code qui l'utilise n'a besoin ni de savoir ni de se soucier si les "Widgets" sont constitués sur place, proviennent d'un tableau, ou sont lus à partir d'un fichier, ou d'une base de données, ou à travers le réseau, ou à partir d'un futur relais sous-espace. Cela fonctionnera toujours correctement car l'List
interface est correctement implémentée.Sauf que ce n'est pas le cas. La classe ci-dessus ne se compile pas car les méthodes d'accès aux fichiers peuvent lever une
IOException
exception vérifiée que vous devez "attraper ou spécifier". Vous ne pouvez pas le spécifier comme jeté - le compilateur ne vous le permettra pas car cela violerait le contrat de l'List
interface. Et il n'y a aucun moyen utile quiWidgetList
puisse gérer l'exception (comme je l'expliquerai plus tard).Apparemment, la seule chose à faire est d'attraper et de renvoyer les exceptions vérifiées comme une exception non vérifiée:
((Edit: Java 8 a ajouté une
UncheckedIOException
classe pour exactement ce cas: pour attraper et repousser lesIOException
s à travers les frontières des méthodes polymorphes. Cela prouve mon point de vue!))Les exceptions vérifiées ne fonctionnent tout simplement pas dans des cas comme celui-ci. Tu ne peux pas les jeter. Idem pour un intelligent
Map
soutenu par une base de données, ou une implémentation dejava.util.Random
connecté à une source d'entropie quantique via un port COM. Dès que vous essayez de faire quelque chose de nouveau avec l'implémentation d'une interface polymorphe, le concept d'exceptions vérifiées échoue. Mais les exceptions vérifiées sont si insidieuses qu'elles ne vous laisseront toujours pas en paix, car vous devez toujours intercepter et renvoyer l'une des méthodes de niveau inférieur, encombrant le code et encombrant la trace de la pile.Je trouve que l'omniprésent
Runnable
interface est souvent sauvegardée dans ce coin, si elle appelle quelque chose qui lève des exceptions vérifiées. Il ne peut pas lever l'exception tel quel, donc tout ce qu'il peut faire est d'encombrer le code en attrapant et en relançant en tant queRuntimeException
.En fait, vous pouvez lever des exceptions cochées non déclarées si vous recourez à des hacks. La JVM, au moment de l'exécution, ne se soucie pas des règles d'exception vérifiées, nous devons donc tromper uniquement le compilateur. La façon la plus simple de procéder consiste à abuser des génériques. C'est ma méthode pour cela (nom de classe affiché car (avant Java 8) il est requis dans la syntaxe d'appel pour la méthode générique):
Hourra! En utilisant cela, nous pouvons lancer une exception vérifiée à n'importe quelle profondeur de la pile sans la déclarer, sans l'envelopper dans un
RuntimeException
, et sans encombrer la trace de la pile! En utilisant à nouveau l'exemple "WidgetList":Malheureusement, la dernière insulte des exceptions vérifiées est que le compilateur refuse de vous autoriser à intercepter une exception vérifiée si, à son avis erroné, elle n'aurait pas pu être levée. (Les exceptions non vérifiées n'ont pas cette règle.) Pour intercepter l'exception levée sournoisement, nous devons le faire:
C'est un peu gênant, mais du côté positif, il est toujours légèrement plus simple que le code pour extraire une exception vérifiée qui a été enveloppée dans un
RuntimeException
.Heureusement, la
throw t;
déclaration est légale ici, même si le type det
est vérifié, grâce à une règle ajoutée dans Java 7 sur la reprise des exceptions interceptées.Lorsque les exceptions vérifiées rencontrent le polymorphisme, le cas contraire est également un problème: lorsqu'une méthode est spécifiée comme pouvant déclencher une exception vérifiée, mais pas une implémentation remplacée. Par exemple, la classe abstraite
OutputStream
« swrite
méthodes spécifient tousthrows IOException
.ByteArrayOutputStream
est une sous-classe qui écrit dans un tableau en mémoire au lieu d'une véritable source d'E / S. Seswrite
méthodes substituées ne peuvent pas provoquer deIOException
s, elles n'ont donc pas dethrows
clause, et vous pouvez les appeler sans vous soucier de l'exigence catch-or-specify.Sauf pas toujours. Supposons qu'il
Widget
dispose d'une méthode pour l'enregistrer dans un flux:Déclarer cette méthode pour accepter une plaine
OutputStream
est la bonne chose à faire, elle peut donc être utilisée de manière polymorphe avec toutes sortes de sorties: fichiers, bases de données, réseau, etc. Et des tableaux en mémoire. Avec un tableau en mémoire, cependant, il existe une exigence fallacieuse pour gérer une exception qui ne peut pas réellement se produire:Comme d'habitude, les exceptions vérifiées gênent. Si vos variables sont déclarées comme un type de base ayant des exigences d'exception plus ouvertes, vous devez ajouter des gestionnaires pour ces exceptions même si vous savez qu'elles ne se produiront pas dans votre application.
Mais attendez, les exceptions vérifiées sont si ennuyeuses qu'elles ne vous permettront même pas de faire l'inverse! Imaginez que vous interceptiez actuellement tout
IOException
jeté par deswrite
appels sur unOutputStream
, mais vous voulez changer le type déclaré de la variable en unByteArrayOutputStream
, le compilateur vous réprimandera pour avoir tenté de capturer une exception vérifiée qui, selon lui, ne peut pas être levée.Cette règle pose des problèmes absurdes. Par exemple, l'une des trois
write
méthodes deOutputStream
n'est pas remplacée parByteArrayOutputStream
. Plus précisément,write(byte[] data)
est une méthode pratique qui écrit le tableau complet en appelantwrite(byte[] data, int offset, int length)
avec un décalage de 0 et la longueur du tableau.ByteArrayOutputStream
remplace la méthode à trois arguments mais hérite la méthode de commodité à un argument telle quelle. La méthode héritée fait exactement ce qu'il faut, mais elle inclut unethrows
clause indésirable . C'était peut-être une erreur dans la conception deByteArrayOutputStream
, mais ils ne peuvent jamais y remédier car cela romprait la compatibilité des sources avec tout code qui intercepte l'exception - l'exception qui n'a jamais, n'est jamais et ne sera jamais levée!Cette règle est également gênante lors de l'édition et du débogage. Par exemple, parfois je commenterai temporairement un appel de méthode, et s'il aurait pu lever une exception vérifiée, le compilateur se plaindra maintenant de l'existence du local
try
et descatch
blocs. Je dois donc les commenter également, et maintenant lors de la modification du code à l'intérieur, l'IDE se mettra en retrait au mauvais niveau car les{
et}
sont commentés. Gah! C'est une petite plainte, mais il semble que la seule chose que les exceptions vérifiées fassent est de causer des problèmes.J'ai presque fini. Ma dernière frustration avec les exceptions vérifiées est que dans la plupart des sites d'appels , il n'y a rien d'utile à faire avec eux. Idéalement, en cas de problème, nous aurions un gestionnaire d'application spécifique compétent qui pourrait informer l'utilisateur du problème et / ou terminer ou réessayer l'opération selon le cas. Seul un gestionnaire en haut de la pile peut le faire car c'est le seul qui connaît l'objectif global.
Au lieu de cela, nous obtenons l'idiome suivant, qui est endémique comme moyen de fermer le compilateur:
Dans une interface graphique ou un programme automatisé, le message imprimé ne sera pas vu. Pire, il continue avec le reste du code après l'exception. L'exception n'est-elle pas réellement une erreur? Alors ne l'imprimez pas. Sinon, quelque chose d'autre va exploser dans un instant, date à laquelle l'objet d'exception d'origine aura disparu. Cet idiome n'est pas meilleur que BASIC
On Error Resume Next
ou PHPerror_reporting(0);
.Appeler une sorte de classe logger n'est pas beaucoup mieux:
C'est tout aussi paresseux que
e.printStackTrace();
et continue de travailler avec du code dans un état indéterminé. De plus, le choix d'un système de journalisation particulier ou d'un autre gestionnaire est spécifique à l'application, ce qui nuit à la réutilisation du code.Mais attendez! Il existe un moyen simple et universel de trouver le gestionnaire spécifique à l'application. Il est plus haut dans la pile des appels (ou il est défini comme gestionnaire d'exceptions non intercepté du thread ). Donc, dans la plupart des endroits, tout ce que vous avez à faire est de lever l'exception plus haut dans la pile . Par exemple,
throw e;
. Les exceptions vérifiées ne font qu'empêcher.Je suis sûr que les exceptions vérifiées semblaient être une bonne idée lorsque le langage a été conçu, mais dans la pratique, je les ai trouvées gênantes et sans aucun avantage.
la source
RuntimeException
. Notez qu'une routine peut être déclarée simultanémentthrows IOException
et spécifier également que toutIOException
appel provenant d'un appel imbriqué doit être considéré comme inattendu et encapsulé.if (false)
pour cela. Cela évite le problème de la clause throw et l'avertissement m'aide à revenir en arrière plus rapidement. +++ Cela dit, je suis d'accord avec tout ce que vous avez écrit. Les exceptions vérifiées ont une certaine valeur, mais cette valeur est négligeable par rapport à leur coût. Presque toujours, ils gênent.Eh bien, il ne s'agit pas d'afficher une trace de pile ou de planter silencieusement. Il s'agit de pouvoir communiquer des erreurs entre les couches.
Le problème avec les exceptions vérifiées est qu'elles encouragent les gens à avaler des détails importants (à savoir, la classe d'exception). Si vous choisissez de ne pas avaler ce détail, vous devez continuer à ajouter des déclarations de lancers sur l'ensemble de votre application. Cela signifie 1) qu'un nouveau type d'exception affectera de nombreuses signatures de fonction, et 2) que vous pouvez manquer une instance spécifique de l'exception que vous souhaitez réellement (par exemple, ouvrir un fichier secondaire pour une fonction qui écrit des données dans un fichier). Le fichier secondaire est facultatif, vous pouvez donc ignorer ses erreurs, mais à cause de la signature
throws IOException
, il est facile de l'ignorer).Je fais face à cette situation maintenant dans une application. Nous avons reconditionné presque des exceptions comme AppSpecificException. Cela a rendu les signatures vraiment propres et nous n'avons pas eu à nous soucier d'exploser
throws
dans les signatures.Bien sûr, nous devons maintenant spécialiser la gestion des erreurs aux niveaux supérieurs, en implémentant une logique de nouvelle tentative, etc. Tout est AppSpecificException, cependant, nous ne pouvons donc pas dire "Si une IOException est levée, réessayez" ou "Si ClassNotFound est levé, abandonnez complètement". Nous ne disposons pas d'un moyen fiable pour atteindre la vraie exception, car les choses sont reconditionnées encore et encore lorsqu'elles passent entre notre code et le code tiers.
C'est pourquoi je suis un grand fan de la gestion des exceptions en python. Vous ne pouvez attraper que les choses que vous voulez et / ou pouvez gérer. Tout le reste bouillonne comme si vous l'aviez revu vous-même (ce que vous avez fait de toute façon).
J'ai constaté à maintes reprises et tout au long du projet que j'ai mentionné que la gestion des exceptions se divise en 3 catégories:
la source
throws
déclarations.SNR
Premièrement, les exceptions vérifiées diminuent le "rapport signal / bruit" pour le code. Anders Hejlsberg parle également de la programmation impérative vs déclarative qui est un concept similaire. Dans tous les cas, tenez compte des extraits de code suivants:
Mettre à jour l'interface utilisateur à partir d'un thread non UI en Java:
Mettre à jour l'interface utilisateur à partir d'un thread non UI en C #:
Ce qui me semble beaucoup plus clair. Lorsque vous commencez à faire de plus en plus d'interface utilisateur dans Swing, les exceptions vérifiées commencent à devenir vraiment ennuyeuses et inutiles.
Pause de prison
Pour implémenter même les implémentations les plus élémentaires, telles que l'interface de liste de Java, les exceptions vérifiées en tant qu'outil de conception par contrat tombent en panne. Considérez une liste qui est soutenue par une base de données ou un système de fichiers ou toute autre implémentation qui lève une exception vérifiée. La seule implémentation possible consiste à intercepter l'exception vérifiée et à la renvoyer en tant qu'exception non vérifiée:
Et maintenant, vous devez vous demander quel est l'intérêt de tout ce code? Les exceptions vérifiées ajoutent simplement du bruit, l'exception a été interceptée mais pas gérée et la conception par contrat (en termes d'exceptions vérifiées) est tombée en panne.
Conclusion
Je blogué sur ce auparavant .
la source
Artima a publié une interview avec l'un des architectes de .NET, Anders Hejlsberg, qui couvre avec acuité les arguments contre les exceptions vérifiées. Un petit avant-goût:
la source
J'ai d'abord été d'accord avec vous, car j'ai toujours été en faveur des exceptions vérifiées, et j'ai commencé à réfléchir à la raison pour laquelle je n'aime pas ne pas avoir vérifié les exceptions dans .Net. Mais je me suis alors rendu compte que je ne fais pas comme des exceptions vérifiées.
Pour vous répondre, oui, j'aime que mes programmes montrent des traces de pile, de préférence vraiment laides. Je veux que l'application explose en un horrible tas de messages d'erreur les plus laids que vous puissiez souhaiter voir.
Et la raison en est que, si cela fait cela, je dois le réparer, et je dois le réparer tout de suite. Je veux savoir immédiatement qu'il y a un problème.
Combien de fois gérez-vous réellement les exceptions? Je ne parle pas d'attraper des exceptions - je parle de les gérer? Il est trop facile d'écrire ce qui suit:
Et je sais que vous pouvez dire que c'est une mauvaise pratique, et que «la réponse» est de faire quelque chose avec l'exception (laissez-moi deviner, enregistrez-vous?), Mais dans le monde réel (tm), la plupart des programmeurs ne font tout simplement pas il.
Alors oui, je ne veux pas faire d'exception si je ne suis pas obligé de le faire, et je veux que mon programme explose de façon spectaculaire quand je foirerai. Échouer en silence est le pire résultat possible.
la source
RuntimeExceptions
effet que vous n'avez pas à attraper, et je conviens que vous devriez laisser le programme exploser. Les exceptions que vous devez toujours intercepter et gérer sont les exceptions vérifiées commeIOException
. Si vous obtenez unIOException
, il n'y a rien à corriger dans votre code; votre programme ne devrait pas exploser simplement parce qu'il y a eu un hoquet réseau.L'article Exceptions Java efficaces explique bien quand utiliser les exceptions non cochées et quand utiliser les exceptions cochées. Voici quelques citations de cet article pour souligner les principaux points:
(SO n'autorise pas les tableaux, vous voudrez peut-être lire ce qui suit à partir de la page d'origine ...)
la source
args[]
? S'agit-il d'une éventualité ou d'une faute?File#openInputStream
retourEither<InputStream, Problem>
- si c'est ce que vous voulez dire, alors nous pouvons être d'accord.En bref:
Les exceptions sont une question de conception d'API. -- Ni plus ni moins.
L'argument des exceptions vérifiées:
Pour comprendre pourquoi les exceptions vérifiées peuvent ne pas être une bonne chose, tournons la question et demandons: Quand ou pourquoi les exceptions vérifiées sont-elles attrayantes, c'est-à-dire pourquoi voudriez-vous que le compilateur applique la déclaration d'exceptions?
La réponse est évidente: Parfois, vous devez intercepter une exception, et cela n'est possible que si le code appelé offre une classe d'exception spécifique pour l'erreur qui vous intéresse.
Par conséquent, l'argument pour les exceptions vérifiées est que le compilateur oblige les programmeurs à déclarer les exceptions levées et, espérons- le , le programmeur documentera également les classes d'exceptions spécifiques et les erreurs qui les provoquent.
En réalité cependant, trop souvent, un package
com.acme
ne lanceAcmeException
qu'une sous-classe plutôt que spécifique. Les appelants doivent alors gérer, déclarer ou re-signalerAcmeExceptions
, mais ne peuvent toujours pas être sûrs siAcmeFileNotFoundError
unAcmePermissionDeniedError
.Donc, si vous n'êtes intéressé que par un
AcmeFileNotFoundError
, la solution consiste à déposer une demande de fonctionnalité auprès des programmeurs ACME et à leur dire d'implémenter, déclarer et documenter cette sous-classe deAcmeException
.Alors pourquoi s'embêter?
Par conséquent, même avec des exceptions vérifiées, le compilateur ne peut pas forcer les programmeurs à lever des exceptions utiles . Ce n'est encore qu'une question de qualité de l'API.
En conséquence, les langues sans exceptions vérifiées ne se comportent généralement pas bien pire. Les programmeurs pourraient être tentés de lancer des instances non spécifiques d'une
Error
classe générale plutôt que d'unAcmeException
, mais s'ils se soucient du tout de la qualité de leur API, ils apprendront à introduire unAcmeFileNotFoundError
après tout.Dans l'ensemble, la spécification et la documentation des exceptions ne diffèrent pas beaucoup de la spécification et de la documentation, disons, des méthodes ordinaires. Celles-ci sont également une question de conception de l'API, et si un programmeur a oublié d'implémenter ou d'exporter une fonctionnalité utile, l'API doit être améliorée afin que vous puissiez l'utiliser avec efficacité.
Si vous suivez cette ligne de raisonnement, il devrait être évident que le "tracas" de déclarer, intercepter et relancer des exceptions qui est si courant dans des langages comme Java ajoute souvent peu de valeur.
Il convient également de noter que la machine virtuelle Java n'a pas d'exceptions vérifiées - seul le compilateur Java les vérifie et les fichiers de classe avec les déclarations d'exceptions modifiées sont compatibles au moment de l'exécution. La sécurité de la machine virtuelle Java n'est pas améliorée par les exceptions vérifiées, uniquement par le style de codage.
la source
AcmeException
plutôt que laAcmeFileNotFoundError
chance de découvrir ce que vous avez fait de mal et où vous devez l'attraper. Les exceptions vérifiées offrent aux programmeurs un minimum de protection contre une mauvaise conception d'API.J'ai travaillé avec plusieurs développeurs au cours des trois dernières années dans des applications relativement complexes. Nous avons une base de code qui utilise assez souvent les exceptions vérifiées avec une gestion correcte des erreurs, et d'autres qui ne le font pas.
Jusqu'à présent, j'ai trouvé plus facile de travailler avec la base de code avec les exceptions vérifiées. Lorsque j'utilise l'API de quelqu'un d'autre, il est agréable de voir exactement à quel type de conditions d'erreur je peux m'attendre lorsque j'appelle le code et les traite correctement, soit en enregistrant, en affichant ou en ignorant (Oui, il existe des cas valides pour ignorer exceptions, comme une implémentation de ClassLoader). Cela donne au code que j'écris l'occasion de récupérer. Toutes les exceptions d'exécution que je propage jusqu'à ce qu'elles soient mises en cache et gérées avec un code générique de gestion des erreurs. Lorsque je trouve une exception vérifiée que je ne veux pas vraiment gérer à un niveau spécifique, ou que je considère comme une erreur de logique de programmation, je l'enveloppe dans une RuntimeException et la laisse bouillonner. Jamais, jamais avaler une exception sans une bonne raison (et les bonnes raisons de le faire sont plutôt rares)
Lorsque je travaille avec la base de code qui n'a pas d'exceptions vérifiées, cela me rend un peu plus difficile de savoir à l'avance à quoi puis-je m'attendre lors de l'appel de la fonction, ce qui peut briser terriblement certaines choses.
C'est bien sûr une question de préférence et de compétence de développeur. Les deux méthodes de programmation et de gestion des erreurs peuvent être tout aussi efficaces (ou inefficaces), donc je ne dirais pas qu'il existe The One Way.
Dans l'ensemble, je trouve plus facile de travailler avec Checked Exceptions, en particulier dans les grands projets avec beaucoup de développeurs.
la source
Catégories d'exceptions
Lorsque je parle d'exceptions, je me réfère toujours à l' article du blog sur les exceptions d'Eex Lippert . Il place des exceptions dans ces catégories:
OutOfMemoryError
ouThreadAbortException
.ArrayIndexOutOfBoundsException
,NullPointerException
ou toutIllegalArgumentException
.NumberFormatException
auInteger.parseInt
lieu de fournir uneInteger.tryParseInt
méthode qui renvoie un faux booléen en cas d'échec de l'analyse.FileNotFoundException
,.Un utilisateur d'API:
Exceptions vérifiées
Le fait que l'utilisateur de l'API doive gérer une exception particulière fait partie du contrat de la méthode entre l'appelant et l'appelé. Le contrat spécifie, entre autres: le nombre et les types d'arguments attendus par l'appelé, le type de valeur de retour auquel l'appelant peut s'attendre et les exceptions que l'appelant doit gérer .
Étant donné que les exceptions de vexation ne doivent pas exister dans une API, seules ces exceptions exogènes doivent être vérifiées pour faire partie du contrat de la méthode. Relativement peu d'exceptions sont exogènes , donc toute API devrait avoir relativement peu d'exceptions vérifiées.
Une exception vérifiée est une exception qui doit être gérée . Gérer une exception peut être aussi simple que de l'avaler. Là! L'exception est gérée. Période. Si le développeur veut le gérer de cette façon, très bien. Mais il ne peut ignorer l'exception et a été prévenu.
Problèmes d'API
Mais toute API qui a vérifié les dérangements et les exceptions fatales (par exemple le JCL) mettra une pression inutile sur les utilisateurs de l'API. De telles exceptions doivent être gérées, mais soit l'exception est si courante qu'elle n'aurait pas dû être une exception en premier lieu, soit rien ne peut être fait lors de sa gestion. Et cela amène les développeurs Java à détester les exceptions vérifiées.
De plus, de nombreuses API n'ont pas de hiérarchie de classe d'exception appropriée, ce qui fait que toutes sortes de causes d'exception non exogènes sont représentées par une seule classe d'exception vérifiée (par exemple
IOException
). Et cela amène également les développeurs Java à détester les exceptions vérifiées.Conclusion
Les exceptions exogènes sont celles qui ne sont pas de votre faute, qui n'auraient pas pu être évitées et qui devraient être traitées. Ceux-ci forment un petit sous-ensemble de toutes les exceptions qui peuvent être levées. Les API ne doivent avoir vérifié que les exceptions exogènes et toutes les autres exceptions décochées. Cela améliorera les API, mettra moins de pression sur l'utilisateur de l'API et réduira donc la nécessité de tout intercepter, d'avaler ou de rejeter des exceptions non contrôlées.
Ne détestez donc pas Java et ses exceptions vérifiées. Au lieu de cela, détestez les API qui surutilisent les exceptions vérifiées.
la source
D'accord ... Les exceptions vérifiées ne sont pas idéales et ont une mise en garde, mais elles servent à quelque chose. Lors de la création d'une API, il existe des cas spécifiques de défaillances contractuelles de cette API. Lorsque, dans le contexte d'un langage fortement typé statiquement tel que Java, si l'on n'utilise pas d'exceptions vérifiées, on doit s'appuyer sur une documentation et une convention ad hoc pour évoquer la possibilité d'erreur. Cela supprime tous les avantages que le compilateur peut apporter en cas d'erreur de gestion et vous êtes entièrement laissé à la bonne volonté des programmeurs.
Donc, on supprime l'exception Checked, comme cela a été fait en C #, comment peut-on alors transmettre par programme et structure la possibilité d'erreur? Comment informer le code client que telles et telles erreurs peuvent survenir et doivent être traitées?
J'entends toutes sortes d'horreurs lorsqu'il s'agit d'exceptions vérifiées, elles sont mal utilisées, c'est certain, mais les exceptions non contrôlées le sont aussi. Je dis attendre quelques années lorsque les API sont empilées sur plusieurs couches et que vous implorerez le retour d'une sorte de moyen structuré pour transmettre les échecs.
Prenons le cas lorsque l'exception a été levée quelque part au bas des couches API et vient de se propager parce que personne ne savait qu'il était même possible que cette erreur se produise, ceci même s'il s'agissait d'un type d'erreur très plausible lorsque le code appelant l'a jeté (FileNotFoundException par exemple par opposition à VogonsTrashingEarthExcept ... auquel cas cela n'aurait pas d'importance si nous le gérons ou non car il n'y a plus rien pour le gérer).
Beaucoup ont soutenu que ne pas pouvoir charger le fichier était presque toujours la fin du monde pour le processus et qu'il devait mourir d'une mort horrible et douloureuse. Alors oui ... bien sûr ... ok .. vous construisez une API pour quelque chose et il charge le fichier à un moment donné ... Moi, en tant qu'utilisateur de ladite API, je ne peux que répondre ... "Qui diable êtes-vous pour décider quand mon programme devrait planter! " Bien sûr, étant donné le choix où les exceptions sont englouties et ne laissent aucune trace ou l'exception EletroFlabbingChunkFluxManifoldChuggingException avec une trace de pile plus profonde que la tranchée Marianna, je prendrais cette dernière sans une pincée d'hésitation, mais cela signifie-t-il que c'est la façon souhaitable de traiter l'exception ? Ne pouvons-nous pas être quelque part au milieu, où l'exception serait refondue et enveloppée à chaque fois qu'elle traverserait un nouveau niveau d'abstraction pour que cela signifie réellement quelque chose?
Enfin, la plupart de l'argument que je vois est "Je ne veux pas traiter des exceptions, beaucoup de gens ne veulent pas traiter des exceptions. Les exceptions vérifiées m'obligent à les traiter ainsi je déteste les exceptions vérifiées" Pour éliminer complètement ce mécanisme et le reléguer au gouffre de l'enfer est tout simplement idiot et manque de jugement et de vision.
Si nous éliminons l'exception vérifiée, nous pourrions également éliminer le type de retour pour les fonctions et toujours renvoyer une variable "anytype" ... Cela simplifierait la vie maintenant, n'est-ce pas?
la source
En effet, les exceptions vérifiées d'une part augmentent la robustesse et l'exactitude de votre programme (vous êtes obligé de faire des déclarations correctes de vos interfaces - les exceptions qu'une méthode lève sont fondamentalement un type de retour spécial). D'un autre côté, vous êtes confronté au problème que, puisque les exceptions "bouillonnent", vous devez très souvent changer beaucoup de méthodes (tous les appelants et les appelants des appelants, etc.) lorsque vous modifiez une exception. jette la méthode.
Les exceptions vérifiées en Java ne résolvent pas ce dernier problème; C # et VB.NET jettent le bébé avec l'eau du bain.
Une belle approche qui prend la route du milieu est décrite dans ce document OOPSLA 2005 (ou le rapport technique connexe .)
En bref, il vous permet de dire:,
method g(x) throws like f(x)
ce qui signifie que g lève toutes les exceptions f lève. Voila, vérifié les exceptions sans le problème des changements en cascade.Bien qu'il s'agisse d'un article académique, je vous encourage à le lire (en partie), car il explique bien les avantages et les inconvénients des exceptions vérifiées.
la source
Ce n'est pas un argument contre le concept pur d'exceptions vérifiées, mais la hiérarchie de classes que Java utilise pour elles est un spectacle bizarre. Nous appelons toujours les choses simplement «exceptions» - ce qui est correct, car la spécification du langage les appelle aussi - mais comment une exception est-elle nommée et représentée dans le système de types?
Par la classe
Exception
qu'on imagine? Eh bien non, parce queException
s sont des exceptions, et de même les exceptions sontException
s, à l'exception des exceptions qui ne sont pasException
s, car d'autres exceptions sont en fait desError
s, qui sont l'autre type d'exception, une sorte d'exception extra-exceptionnelle qui ne devrait jamais se produire sauf quand il le fait, et que vous ne devriez jamais attraper, sauf parfois. Sauf que ce n'est pas tout, car vous pouvez également définir d'autres exceptions qui ne sont niException
s niError
s mais simplement desThrowable
exceptions.Quelles sont les exceptions "vérifiées"?
Throwable
les s sont des exceptions vérifiées, sauf s'il s'agit également deError
s, qui sont des exceptions non contrôlées, et puis il y a lesException
s, qui sont également desThrowable
s et sont le type principal d'exception vérifiée, sauf qu'il y a une exception à cela aussi, qui est que s'ils sont égalementRuntimeException
s, car c'est l'autre type d'exception non contrôlée.À quoi servent les
RuntimeException
s? Eh bien, comme son nom l'indique, ce sont des exceptions, comme tous lesException
s, et elles se produisent au moment de l'exécution, comme toutes les exceptions en fait, sauf que lesRuntimeException
s sont exceptionnelles par rapport aux autres s d'exécutionException
car elles ne sont pas censées se produire sauf lorsque vous faites une erreur stupide, bien que lesRuntimeException
s ne soient jamais desError
s, ils sont donc pour des choses exceptionnellement erronées mais qui ne sont pas réellement desError
s. Sauf pourRuntimeErrorException
, qui est vraiment unRuntimeException
pour l'Error
art. Mais toutes les exceptions ne sont-elles pas censées représenter des circonstances erronées de toute façon? Oui, tous. À l'exception deThreadDeath
, une exception exceptionnellement exceptionnelle, car la documentation explique qu'il s'agit d'une «occurrence normale» et que «Error
Quoi qu'il en soit, puisque nous divisons toutes les exceptions au milieu en
Error
s (qui sont pour les exceptions d'exécution exceptionnelles, donc non cochées) etException
s (qui sont pour les erreurs d'exécution moins exceptionnelles, donc vérifiées sauf lorsqu'elles ne le sont pas), nous avons maintenant besoin de deux différents types de chacune de plusieurs exceptions. Nous avons donc besoin deIllegalAccessError
etIllegalAccessException
, etInstantiationError
etInstantiationException
, etNoSuchFieldError
etNoSuchFieldException
, etNoSuchMethodError
etNoSuchMethodException
, etZipError
etZipException
.Sauf que même lorsqu'une exception est vérifiée, il existe toujours des moyens (assez faciles) de tromper le compilateur et de le lancer sans qu'il soit vérifié. Si vous vous faites, vous pouvez obtenir un
UndeclaredThrowableException
, sauf dans d'autres cas, où il pourrait apparaître comme unUnexpectedException
, ou unUnknownException
(qui n'est pas lié àUnknownError
, ce qui n'est que pour des "exceptions graves"), ou unExecutionException
, ou unInvocationTargetException
, ou unExceptionInInitializerError
.Oh, et nous ne devons pas oublier le nouveau snazzy de Java 8
UncheckedIOException
, qui est uneRuntimeException
exception conçue pour vous permettre de jeter le concept de vérification des exceptions par la fenêtre en enveloppant lesIOException
exceptions vérifiées causées par des erreurs d'E / S (qui ne provoquent pas d'IOError
exceptions, bien que cela existe aussi) qui sont exceptionnellement difficiles à manipuler et vous avez donc besoin qu'elles ne soient pas vérifiées.Merci Java!
la source
Le problème
Le pire problème que je vois avec le mécanisme de gestion des exceptions est qu'il introduit une duplication de code à grande échelle ! Soyons honnêtes: dans la plupart des projets dans 95% du temps, tout ce que les développeurs doivent vraiment faire, sauf exception, est de le communiquer d'une manière ou d'une autre à l'utilisateur (et, dans certains cas, à l'équipe de développement également, par exemple en envoyant un e -mail avec la trace de la pile). Donc, généralement, la même ligne / bloc de code est utilisé à chaque endroit où l'exception est gérée.
Supposons que nous enregistrions simplement chaque bloc catch pour un certain type d'exception vérifiée:
S'il s'agit d'une exception courante, il peut y avoir même plusieurs centaines de ces blocs try-catch dans une base de code plus grande. Supposons maintenant que nous devons introduire une gestion des exceptions basée sur une boîte de dialogue contextuelle au lieu de la journalisation de la console ou commencer à envoyer un e-mail à l'équipe de développement.
Attendez un instant ... allons-nous vraiment modifier tout cela plusieurs centaines d'emplacements dans le code?! Vous obtenez mon point :-).
La solution
Ce que nous avons fait pour résoudre ce problème a été d'introduire le concept de gestionnaires d'exceptions (auquel je ferai référence plus loin sous le nom d'EH) pour centraliser la gestion des exceptions. Pour chaque classe qui a besoin de suspendre des exceptions, une instance de gestionnaire d'exceptions est injectée par notre infrastructure d'injection de dépendances . Ainsi, le modèle typique de gestion des exceptions ressemble maintenant à ceci:
Maintenant, pour personnaliser notre gestion des exceptions, il nous suffit de changer le code en un seul endroit (code EH).
Bien sûr, pour les cas plus complexes, nous pouvons implémenter plusieurs sous-classes d'EH et tirer parti des fonctionnalités que notre infrastructure DI nous fournit. En modifiant la configuration de notre infrastructure DI, nous pouvons facilement basculer l'implémentation EH à l'échelle mondiale ou fournir des implémentations spécifiques d'EH à des classes ayant des besoins de gestion des exceptions spécifiques (par exemple en utilisant l'annotation Guice @Named).
De cette façon, nous pouvons différencier le comportement de gestion des exceptions dans le développement et la version de publication de l'application (par exemple, développement - journalisation de l'erreur et arrêt de l'application, prod - journalisation de l'erreur avec plus de détails et laisser l'application continuer son exécution) sans effort.
Dernière chose
Enfin et surtout, il peut sembler que le même type de centralisation peut être obtenu en passant simplement nos exceptions "jusqu'à" jusqu'à ce qu'elles arrivent à une classe de gestion des exceptions de niveau supérieur. Mais cela conduit à l'encombrement du code et des signatures de nos méthodes et introduit des problèmes de maintenance mentionnés par d'autres dans ce fil.
la source
catch
blocs de ce projet réel fait quelque chose de vraiment "utile" avec les exceptions? 10% serait bien. Les problèmes habituels qui génèrent des exceptions sont comme essayer de lire la configuration à partir d'un fichier qui n'existe pas, OutOfMemoryErrors, NullPointerExceptions, les erreurs d'intégrité des contraintes de base de données, etc., etc. Essayez-vous vraiment de récupérer gracieusement à partir de tous? Je ne te crois pas :). Souvent, il n'y a tout simplement aucun moyen de récupérer.Anders parle des pièges des exceptions vérifiées et pourquoi il les a laissés hors de C # dans l' épisode 97 de la radio Software Engineering.
la source
Ma rédaction sur c2.com est toujours pratiquement inchangée par rapport à sa forme d'origine: CheckedExceptionsAreIncompatibleWithVisitorPattern
En résumé:
Le modèle de visiteur et ses parents sont une classe d'interfaces où l'appelant indirect et l'implémentation d'interface connaissent tous deux une exception mais l'interface et l'appelant direct forment une bibliothèque qui ne peut pas savoir.
L'hypothèse fondamentale de CheckedExceptions est que toutes les exceptions déclarées peuvent être levées à partir de n'importe quel point qui appelle une méthode avec cette déclaration. Le VisitorPattern révèle que cette hypothèse est erronée.
Le résultat final des exceptions vérifiées dans des cas comme ceux-ci est beaucoup de code autrement inutile qui supprime essentiellement la contrainte d'exception vérifiée du compilateur lors de l'exécution.
Quant au problème sous-jacent:
Mon idée générale est que le gestionnaire de niveau supérieur doit interpréter l'exception et afficher un message d'erreur approprié. Je vois presque toujours des exceptions d'E / S, des exceptions de communication (pour une raison quelconque, les API distinguent) ou des erreurs fatales pour les tâches (bogues de programme ou problème grave sur le serveur de support), donc cela ne devrait pas être trop difficile si nous autorisons une trace de pile pour une grave problème de serveur.
la source
Pour tenter de répondre uniquement à la question sans réponse:
La question contient un raisonnement spécieux à mon humble avis. Ce n'est pas parce que l'API vous dit ce qu'elle lance que vous la traitez de la même manière dans tous les cas. En d'autres termes, les exceptions que vous devez intercepter varient en fonction du contexte dans lequel vous utilisez le composant lançant l'exception.
Par exemple:
Si j'écris un testeur de connexion pour une base de données ou quelque chose pour vérifier la validité d'un XPath entré par l'utilisateur, je voudrais probablement attraper et signaler toutes les exceptions vérifiées et non vérifiées qui sont levées par l'opération.
Cependant, si j'écris un moteur de traitement, je traiterai probablement une XPathException (cochée) de la même manière qu'un NPE: je la laisserais remonter jusqu'en haut du thread de travail, ignorer le reste de ce lot, enregistrer le problème (ou l'envoyer à un service d'assistance pour diagnostic) et laisser des commentaires pour que l'utilisateur puisse contacter l'assistance.
la source
Cet article est le meilleur texte que j'ai jamais lu sur la gestion des exceptions en Java.
Il privilégie les exceptions non contrôlées par rapport aux exceptions vérifiées, mais ce choix est expliqué très attentivement et sur la base d'arguments solides.
Je ne veux pas citer trop le contenu de l'article ici (il est préférable de le lire dans son ensemble) mais il couvre la plupart des arguments des défenseurs des exceptions non vérifiées de ce fil. Cet argument (qui semble assez populaire) est particulièrement couvert:
L'auteur "réponses":
Et la pensée ou l'article principal est:
Donc, si " personne ne savait qu'il était même possible que cette erreur se produise ", il y a un problème avec ce projet. Une telle exception doit être gérée par au moins le gestionnaire d'exceptions le plus générique (par exemple, celui qui gère toutes les exceptions non gérées par des gestionnaires plus spécifiques) comme le suggère l'auteur.
Tellement triste peu de gens semblent découvrir ce grand article :-(. Je recommande de tout cœur à tous ceux qui hésitent quelle approche est préférable de prendre un peu de temps et de la lire.
la source
Les exceptions vérifiées étaient, dans leur forme originale, une tentative de gérer les imprévus plutôt que les échecs. L'objectif louable était de mettre en évidence des points spécifiques prévisibles (impossible de se connecter, fichier introuvable, etc.) et de garantir que les développeurs les gèrent.
Ce qui n'a jamais été inclus dans le concept d'origine, a été de forcer la déclaration d'une vaste gamme de défaillances systémiques et irrécupérables. Ces échecs n'étaient jamais corrects pour être déclarés comme exceptions vérifiées.
Les échecs sont généralement possibles dans le code, et les conteneurs EJB, web et Swing / AWT répondent déjà à cela en fournissant un gestionnaire d'exceptions de «demande ayant échoué» le plus à l'extérieur. La stratégie correcte la plus élémentaire consiste à annuler la transaction et à renvoyer une erreur.
Un point crucial est que les exceptions d'exécution et vérifiées sont fonctionnellement équivalentes. Il n'y a aucune manipulation ou récupération que les exceptions vérifiées peuvent faire, contrairement aux exceptions d'exécution.
Le plus grand argument contre les exceptions «vérifiées» est que la plupart des exceptions ne peuvent pas être corrigées. Le simple fait est que nous ne possédons pas le code / sous-système qui s'est cassé. Nous ne pouvons pas voir l'implémentation, nous n'en sommes pas responsables et ne pouvons pas la corriger.
Si notre application n'est pas une base de données .. nous ne devrions pas essayer de réparer la base de données. Cela violerait le principe de l'encapsulation .
Les domaines JDBC (SQLException) et RMI pour EJB (RemoteException) ont été particulièrement problématiques. Plutôt que d'identifier les éventualités pouvant être résolues conformément au concept original d '«exception vérifiée», ces problèmes de fiabilité systémique omniprésents, qui ne sont pas réellement réparables, doivent être largement déclarés.
L'autre grave faille de la conception Java est que la gestion des exceptions doit être correctement placée au niveau "métier" ou "demande" le plus élevé possible. Le principe ici est "lancer tôt, attraper tard". Les exceptions vérifiées ne font pas grand chose, mais gênent cela.
Nous avons un problème évident en Java d'exiger des milliers de blocs try-catch ne rien faire, avec une proportion importante (40% +) mal codée. Presque aucun de ceux-ci ne met en œuvre une véritable manipulation ou fiabilité, mais impose des frais généraux de codage importants.
Enfin, les "exceptions vérifiées" sont à peu près incompatibles avec la programmation fonctionnelle FP.
Leur insistance sur le «traitement immédiat» est en contradiction avec les meilleures pratiques de «gestion tardive» de la gestion des exceptions et avec toute structure FP qui résume les boucles / ou le flux de contrôle.
Beaucoup de gens parlent de "gérer" les exceptions vérifiées, mais parlent à travers leurs chapeaux. Continuer après un échec avec des données nulles, incomplètes ou incorrectes pour faire semblant de succès ne gère rien. C'est faute professionnelle d'ingénierie / fiabilité de la forme la plus basse.
À défaut, c'est la stratégie correcte la plus élémentaire pour gérer une exception. Annuler la transaction, consigner l'erreur et signaler une réponse «échec» à l'utilisateur est une bonne pratique - et surtout, empêche la validation de données commerciales incorrectes dans la base de données.
D'autres stratégies de gestion des exceptions sont "réessayer", "reconnecter" ou "ignorer", au niveau de l'entreprise, du sous-système ou de la demande. Tous ces éléments sont des stratégies de fiabilité générales et fonctionnent bien / mieux avec des exceptions d'exécution.
Enfin, il est de loin préférable d’échouer que d’exécuter avec des données incorrectes. Poursuivre entraînera soit des erreurs secondaires, éloignées de la cause d'origine et plus difficiles à déboguer; ou entraînera éventuellement la validation de données erronées. Les gens se font virer pour ça.
Voir:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/
la source
Comme les gens l'ont déjà dit, les exceptions vérifiées n'existent pas dans le bytecode Java. Ils sont simplement un mécanisme de compilation, un peu comme les autres vérifications de syntaxe. Je vois vérifié exceptions beaucoup comme je vois le compilateur se plaindre d'un conditionnel redondant:
if(true) { a; } b;
. C'est utile, mais j'aurais pu le faire exprès, alors laissez-moi ignorer vos avertissements.Le fait est que vous ne pourrez pas forcer chaque programmeur à "faire la bonne chose" si vous appliquez des exceptions vérifiées et que tout le monde est maintenant un dommage collatéral qui vous déteste pour la règle que vous avez établie.
Réparez les mauvais programmes! N'essayez pas de corriger la langue pour ne pas les autoriser! Pour la plupart des gens, «faire quelque chose à propos d'une exception» revient vraiment à en informer l'utilisateur. Je peux également informer l'utilisateur d'une exception non vérifiée, alors gardez vos classes d'exceptions vérifiées hors de mon API.
la source
Un problème avec les exceptions vérifiées est que les exceptions sont souvent attachées aux méthodes d'une interface si même une implémentation de cette interface l'utilise.
Un autre problème avec les exceptions vérifiées est qu'elles ont tendance à être mal utilisées. L'exemple parfait de ceci est dans
java.sql.Connection
laclose()
méthode de. Il peut lancer unSQLException
, même si vous avez déjà explicitement déclaré que vous avez terminé avec la connexion. Quelles informations pourraient fermer () éventuellement transmettre qui vous intéressent?Habituellement, lorsque je ferme () une connexion
*
, cela ressemble à ceci:De plus, ne me lancez pas sur les différentes méthodes d'analyse et NumberFormatException ... TryParse de .NET, qui ne lève pas d'exceptions, est tellement plus facile à utiliser qu'il est pénible de devoir revenir à Java (nous utilisons à la fois Java et C # où je travaille).
*
En tant que commentaire supplémentaire, Connection.close () de PooledConnection ne ferme même pas une connexion, mais vous devez toujours intercepter l'exception SQLException car il s'agit d'une exception vérifiée.la source
Le programmeur doit connaître toutes les exceptions qu'une méthode peut déclencher, afin de l'utiliser correctement. Donc, le battre au-dessus de la tête avec seulement quelques-unes des exceptions n'aide pas nécessairement un programmeur imprudent à éviter les erreurs.
L'avantage mince est contrebalancé par le coût onéreux (en particulier dans les bases de code plus grandes et moins flexibles où la modification constante des signatures d'interface n'est pas pratique).
L'analyse statique peut être agréable, mais une analyse statique vraiment fiable requiert souvent un travail rigoureux de la part du programmeur. Il existe un calcul coûts-avantages et la barre doit être placée haute pour une vérification qui conduit à une erreur de temps de compilation. Il serait plus utile que l'EDI joue le rôle de communiquer les exceptions qu'une méthode peut lancer (y compris celles qui sont inévitables). Bien que ce ne soit peut-être pas aussi fiable sans déclarations d'exceptions forcées, la plupart des exceptions seraient toujours déclarées dans la documentation, et la fiabilité d'un avertissement IDE n'est pas si cruciale.
la source
Je pense que c'est une excellente question et pas du tout argumentative. Je pense que les bibliothèques tierces devraient (en général) lever des exceptions non contrôlées . Cela signifie que vous pouvez isoler vos dépendances sur la bibliothèque (c'est-à-dire que vous n'avez pas besoin de relancer leurs exceptions ou de lever
Exception
- généralement une mauvaise pratique). La couche DAO de Spring en est un excellent exemple.D'un autre côté, les exceptions de l'API Java de base doivent en général être vérifiées si elles peuvent jamais être gérées. Prenez
FileNotFoundException
ou (mon préféré)InterruptedException
. Ces conditions doivent presque toujours être traitées de manière spécifique (c'est-à-dire que votre réaction à unInterruptedException
n'est pas la même que votre réaction à unIllegalArgumentException
). Le fait que vos exceptions soient vérifiées oblige les développeurs à se demander si une condition est gérable ou non. (Cela dit, je l'ai rarement vuInterruptedException
correctement!)Une dernière chose - un
RuntimeException
n'est pas toujours "où un développeur a quelque chose de mal". Une exception d'argument illégal est levée lorsque vous essayez de créer uneenum
utilisationvalueOf
et il n'y a pasenum
de ce nom. Ce n'est pas nécessairement une erreur du développeur!la source
enum
noms de membres, simplement parce qu'ils utilisent desenum
objets à la place. Un mauvais nom ne peut donc provenir que de l'extérieur, que ce soit un fichier d'importation ou autre. Une façon possible de gérer ces noms est d'appelerMyEnum#valueOf
et d'attraper l'IAE. Une autre façon consiste à utiliser un pré-rempliMap<String, MyEnum>
, mais ce sont des détails d'implémentation.Voici un argument contre les exceptions vérifiées (de joelonsoftware.com):
la source
goto
mot - clé. Avec une boucle, vous pouvez voir l'accolade de fermeture ou le motbreak
-continue
clé ou . Tous sautent à un point dans la méthode actuelle. Mais vous ne pouvez pas toujours voir lethrow
, car souvent ce n'est pas dans la méthode actuelle mais dans une autre méthode qu'il appelle (éventuellement indirectement.)Le bon prouve que l'exception vérifiée n'est pas nécessaire:
J'étais heureux que java me donne le choix de ce que j'utiliserais lorsque je travaillerais avec des bibliothèques de base, comme les E / S. Comme fournit deux copies des mêmes classes - une enveloppée avec RuntimeEception. Ensuite, nous pouvons comparer ce que les gens utiliseraient . Pour l'instant, cependant, beaucoup de gens préfèrent opter pour un framework en haut sur java, ou un langage différent. Comme Scala, JRuby que ce soit. Beaucoup croient simplement que SUN avait raison.
la source
RuntimeException
avec une exception interne appropriée). Il est malheureux qu'il soit plus concis d'avoir la méthode externethrows
une exception à la méthode interne, que de la faire envelopper les exceptions de la méthode interne, alors que la dernière ligne de conduite serait plus souvent correcte.Une chose importante que personne n'a mentionnée est la façon dont il interfère avec les interfaces et les expressions lambda.
Disons que vous définissez un
MyAppException extends Exception
. Il s'agit de l'exception de niveau supérieur héritée par toutes les exceptions levées par votre application. Chaque méthode déclarethrows MyAppException
ce qui est un peu de nuissance, mais gérable. Un gestionnaire d'exceptions consigne l'exception et notifie l'utilisateur d'une manière ou d'une autre.Tout semble OK jusqu'à ce que vous souhaitiez implémenter une interface qui n'est pas la vôtre. De toute évidence, il ne déclare pas l'intention de lancer
MyApException
, donc le compilateur ne vous permet pas de lever l'exception à partir de là.Cependant, si votre exception s'étend
RuntimeException
, il n'y aura aucun problème avec les interfaces. Vous pouvez mentionner volontairement l'exception dans JavaDoc si vous le souhaitez. Mais à part cela, il ne fait que bouillonner silencieusement à travers quoi que ce soit, pour être pris dans votre couche de gestion des exceptions.la source
Nous avons vu quelques références à l'architecte en chef de C #.
Voici un autre point de vue d'un type Java sur le moment d'utiliser les exceptions vérifiées. Il reconnaît bon nombre des points négatifs que d'autres ont mentionnés: Exceptions efficaces
la source
J'ai lu beaucoup de choses sur la gestion des exceptions, même si (la plupart du temps) je ne peux pas vraiment dire que je suis heureux ou triste de l'existence d'exceptions vérifiées, c'est mon point de vue: les exceptions vérifiées dans le code de bas niveau (IO, mise en réseau , OS, etc.) et les exceptions non vérifiées dans les API / niveau d'application de haut niveau.
Même s'il n'est pas si facile de tracer une ligne entre eux, je trouve qu'il est vraiment ennuyeux / difficile d'intégrer plusieurs API / bibliothèques sous le même toit sans encapsuler tout le temps beaucoup d'exceptions vérifiées mais d'un autre côté, parfois est utile / préférable d'être obligé d'attraper une exception et d'en fournir une autre qui a plus de sens dans le contexte actuel.
Le projet sur lequel je travaille prend beaucoup de bibliothèques et les intègre sous la même API, API qui est entièrement basée sur des exceptions non vérifiées.Ces frameworks fournissent une API de haut niveau qui au début était pleine d'exceptions vérifiées et n'en avait que plusieurs non vérifiées exceptions (Initialization Exception, ConfigurationException, etc.) et je dois dire que ce n'était pas très sympathique . La plupart du temps, vous avez dû intercepter ou relancer des exceptions que vous ne savez pas comment gérer, ou vous ne vous en souciez même pas (à ne pas confondre avec vous, vous devez ignorer les exceptions), en particulier du côté client, où un seul le clic pourrait déclencher 10 exceptions possibles (vérifiées).
La version actuelle (3e) n'utilise que des exceptions non vérifiées, et elle dispose d'un gestionnaire d'exceptions global qui est responsable de gérer tout ce qui n'est pas intercepté. L'API fournit un moyen d'enregistrer les gestionnaires d'exceptions, qui décideront si une exception est considérée comme une erreur (la plupart du temps c'est le cas), ce qui signifie enregistrer et notifier quelqu'un, ou cela peut signifier autre chose - comme cette exception, AbortException, ce qui signifie casser le thread d'exécution actuel et ne pas enregistrer d'erreur car il est souhaitable de ne pas le faire. Bien sûr, pour travailler, tous les threads personnalisés doivent gérer la méthode run () avec un try {...} catch (all).
public void run () {
}
Cela n'est pas nécessaire si vous utilisez WorkerService pour planifier des travaux (Runnable, Callable, Worker), qui gère tout pour vous.
Bien sûr, ce n'est que mon opinion, et ce n'est peut-être pas la bonne, mais cela me semble être une bonne approche. Je verrai après que je publierai le projet si ce que je pense est bon pour moi, c'est bon pour les autres aussi ... :)
la source