En Java, à quoi servent les exceptions vérifiées? [fermé]

14

Les exceptions vérifiées de Java ont obtenu une mauvaise presse au fil des ans. Un signe révélateur est que c'est littéralement la seule langue au monde qui les possède (pas même d'autres langues JVM comme Groovy et Scala). Des bibliothèques Java importantes comme Spring et Hibernate ne les utilisent pas non plus.

J'ai personnellement trouvé une utilisation pour eux ( dans la logique métier entre les couches), mais sinon je suis des exceptions assez anti-vérifiées.

Y a-t-il d'autres utilisations que je ne réalise pas?

Brad Cupit
la source
Toutes les bibliothèques de base Java utilisent assez largement les exceptions vérifiées.
Joonas Pulakka
3
@Joonas je sais, et c'est pénible!
Brad Cupit

Réponses:

22

Tout d'abord, comme tout autre paradigme de programmation, vous devez le faire correctement pour qu'il fonctionne bien.

Pour moi, l'avantage des exceptions vérifiées est que les auteurs de la bibliothèque d'exécution Java ont DÉJÀ décidé pour moi quels problèmes courants on pourrait raisonnablement s'attendre à pouvoir gérer au point d' appel (par opposition à un catch-print de niveau supérieur) mourir) et réfléchissez le plus tôt possible à la manière de traiter ces problèmes.

J'aime les exceptions vérifiées car elles rendent mon code plus robuste en me forçant à penser à la récupération d'erreur le plus tôt possible.

Pour être plus précis, pour moi cela rend mon code plus robuste que cela me oblige à considérer les cas d'angle étrange très tôt dans le processus , par opposition à dire « Oops, mon code ne gère pas si le fichier n'existe pas encore » basé sur une erreur de production, que vous devez ensuite retravailler votre code pour gérer. L'ajout de la gestion des erreurs au code existant peut être une tâche non triviale - et donc coûteuse - lors de la maintenance plutôt que de le faire dès le début.

Il se peut que le fichier manquant soit fatal et doive provoquer le plantage du programme en flammes, mais ensuite vous prenez cette décision avec

} catch (FileNotFoundException e) {
  throw new RuntimeException("Important file not present", e);
}

Cela montre également un effet secondaire très important. Si vous encapsulez une exception, vous pouvez ajouter une explication qui va dans le stack-trace ! C'est extrêmement puissant car vous pouvez ajouter des informations sur, par exemple, le nom du fichier manquant, ou les paramètres passés à cette méthode ou d'autres informations de diagnostic, et ces informations sont présentes directement dans la trace de la pile, ce qui est souvent la seule chose que vous obtenir lorsqu'un programme est tombé en panne.

Les gens peuvent dire "nous pouvons simplement exécuter cela dans le débogueur pour reproduire", mais j'ai constaté que très souvent, les erreurs de production ne peuvent pas être reproduites plus tard, et nous ne pouvons pas exécuter les débogueurs en production, sauf dans les cas très désagréables où essentiellement votre travail est en jeu.

Plus il y a d'informations dans votre trace de pile, mieux c'est. Les exceptions vérifiées m'aident à obtenir ces informations là-bas, et tôt.


EDIT: Cela vaut également pour les concepteurs de bibliothèques. Une bibliothèque que j'utilise quotidiennement contient de nombreuses exceptions vérifiées qui auraient pu être conçues beaucoup mieux, ce qui la rendrait moins fastidieuse à utiliser.


la source
+1. Les concepteurs de Spring ont fait du bon travail en traduisant un grand nombre des exceptions Hibernate en exceptions non contrôlées.
Fil
FYI: Frank Sommers a mentionné dans ce fil que les logiciels tolérants aux pannes reposent souvent sur le même mécanisme. Vous pouvez peut-être subdiviser votre réponse en cas récupérable et cas mortel.
rwong
@rwong, pourriez-vous s'il vous plaît expliquer plus en détail ce que vous avez en tête?
11

Vous avez deux bonnes réponses qui expliquent quelles exceptions vérifiées sont devenues dans la pratique. (+1 aux deux.) Mais il serait également utile d'examiner ce à quoi ils étaient destinés en théorie, car l'intention en vaut la peine.

Les exceptions vérifiées sont en fait destinées à rendre le langage plus sûr. Considérez une méthode simple comme la multiplication d'entiers. Vous pourriez penser que le type de résultat de cette méthode serait un entier, mais, à strictement parler, le résultat est soit un entier soit une exception de dépassement de capacité. La prise en compte du résultat entier en tant que type de retour de la méthode n'exprime pas la plage complète de la fonction.

Vu sous cet angle, il n'est pas strictement vrai de dire que les exceptions vérifiées n'ont pas trouvé leur chemin dans d'autres langues. Ils n'ont tout simplement pas pris la forme utilisée par Java. Dans les applications Haskell, il est courant d'utiliser des types de données algébriques pour distinguer entre la réussite et l'échec de la fonction. Bien qu'il ne s'agisse pas d'une exception, en soi, l'intention est très similaire à une exception vérifiée; il s'agit d'une API conçue pour forcer le consommateur d'API à gérer à la fois le succès en cas d'échec, par exemple:

data Foo a =
     Success a
   | DidNotWorkBecauseOfA
   | DidNotWorkBecauseOfB

Cela indique au programmeur que la fonction a deux résultats possibles supplémentaires en plus du succès.

Craig Stuntz
la source
+1 pour les exceptions vérifiées concernant la sécurité des types. Je n'avais jamais entendu cette idée auparavant, mais c'est tellement logique.
Ixrec
11

OMI, les exceptions vérifiées sont une implémentation imparfaite de ce qui aurait pu être une idée assez décente (dans des circonstances complètement différentes - mais ce n'est vraiment pas une bonne idée dans les circonstances actuelles).

La bonne idée est qu'il serait bien que le compilateur vérifie que toutes les exceptions qu'un programme a le potentiel de lever seront interceptées. L'implémentation imparfaite est que pour ce faire, Java vous oblige à traiter l'exception directement dans la classe qui appelle tout ce qui est déclaré pour lever une exception vérifiée. L'une des idées (et avantages) les plus élémentaires de la gestion des exceptions est qu'en cas d'erreur, elle permet aux couches intermédiaires de code qui n'ont rien à voir avec une erreur particulière, d'ignorer complètement son existence même. Les exceptions vérifiées (mises en œuvre en Java) ne permettent pas cela, détruisant ainsi un avantage majeur (le principal?) De la gestion des exceptions.

En théorie, ils auraient pu rendre les exceptions vérifiées utiles en les appliquant uniquement à l'échelle du programme - c'est-à-dire, tout en "construisant" le programme, construisez une arborescence de tous les chemins de contrôle dans le programme. Divers nœuds de l'arborescence seraient annotés avec 1) des exceptions qu'ils peuvent générer et 2) des exceptions qu'ils peuvent intercepter. Pour vous assurer que le programme était correct, vous parcourriez alors l'arbre, en vérifiant que chaque exception levée à n'importe quel niveau de l'arbre a été interceptée à un niveau supérieur dans l'arbre.

La raison pour laquelle ils ne l'ont pas fait (je suppose, de toute façon) est que cela serait atrocement difficile - en fait, je doute que quiconque ait écrit un système de construction qui puisse le faire pour autre chose que extrêmement simple (à la limite de "jouet") ") langages / systèmes. Pour Java, des choses comme le chargement dynamique des classes le rendent encore plus difficile que dans de nombreux autres cas. Pire, (contrairement à quelque chose comme C), Java n'est pas (au moins normalement) compilé / lié statiquement à un exécutable. Cela signifie que rien dans le système n'a vraiment la conscience mondiale d' essayer même de faire ce type d'application jusqu'à ce que l'utilisateur final commence réellement à charger le programme pour l'exécuter.

L'application de la loi à ce stade n'est pas pratique pour plusieurs raisons. Tout d'abord, même au mieux, cela allongerait les temps de démarrage, qui sont déjà l'une des plaintes les plus importantes et les plus courantes à propos de Java. Deuxièmement, pour faire beaucoup de bien, vous devez vraiment détecter le problème suffisamment tôt pour qu'un programmeur puisse le résoudre. Lorsque l'utilisateur charge le programme, il est trop tard pour faire beaucoup de bien - vos seuls choix à ce stade sont d'ignorer le problème ou de refuser de charger / exécuter le programme, au cas où il pourrait y avoir une exception. cela n'a pas été pris.

En l'état, les exceptions vérifiées sont pires qu'inutiles, mais les conceptions alternatives pour les exceptions vérifiées sont encore pires. Bien que l'idée de base des exceptions vérifiées semble bonne, elle n'est vraiment pas très bonne et s'avère être particulièrement mauvaise pour Java. Pour quelque chose comme C ++, qui est normalement compilé / lié en un exécutable complet avec rien de tel que le chargement de classe de réflexion / dynamique, vous auriez un peu plus de chance (bien que, franchement, même en les appliquant correctement sans le même genre de gâchis qu'ils actuellement à Java serait probablement encore au-delà de l'état actuel de la technique).

Jerry Coffin
la source
Votre toute première déclaration initiale sur la gestion directe de l'exception est déjà fausse; vous pouvez simplement ajouter une instruction throws, et cette instruction throws peut être de type parent. De plus, si vous ne pensez vraiment pas que vous devez le gérer, vous pouvez simplement le convertir en RuntimeException à la place. Les IDE vous aident généralement dans les deux tâches.
Maarten Bodewes
2
@owlstead: Merci - j'aurais probablement dû être plus prudent avec le libellé. Le point n'était pas tellement que vous deviez en fait attraper l'exception car c'était que chaque niveau intermédiaire de code devait être au courant de chaque exception qui pouvait la traverser. Pour le reste: avoir un outil qui rend rapide et facile de faire un gâchis de votre code ne change pas le fait qu'il gâche le code.
Jerry Coffin du
Les exceptions vérifiées étaient destinées aux imprévus - résultats prévisibles et récupérables autres que le succès, par opposition aux échecs - problèmes de bas niveau / ou système imprévisibles. FileNotFound ou une erreur d'ouverture d'une connexion JDBC étaient tous deux correctement considérés comme des imprévus. Malheureusement, les concepteurs de bibliothèques Java ont fait une erreur totale et ont également attribué toutes les autres exceptions IO et SQL à la classe «vérifiée»; bien que ceux-ci soient presque complètement irrécupérables. literatejava.com/exceptions/…
Thomas W
@owlstead: Les exceptions vérifiées ne devraient traverser les couches intermédiaires de la pile d'appels que si elles sont levées dans les cas attendus par le code d'appel intermédiaire . À mon humble avis, les exceptions vérifiées auraient été une bonne chose si les appelants devaient les attraper ou indiquer s'ils étaient attendus ou non ; les exceptions inattendues doivent être automatiquement enveloppées dans un UnexpectedExceptiontype dérivé de RuntimeException. Notez que "jette" et "ne s'attend pas" ne sont pas contradictoires; si foojette BozExceptionet appelle bar, cela ne signifie pas qu'il s'attend barà lancer BozException.
supercat
10

Ils sont vraiment bons pour ennuyer les programmeurs et encourager la déglutition d'exceptions. La moitié des exceptions est pour que vous ayez une manière saine et par défaut de gérer les erreurs et que vous n'ayez pas à penser à propager explicitement les conditions d'erreur que vous ne pouvez pas gérer. (La valeur par défaut est que si vous ne gérez jamais l'erreur nulle part dans votre code, vous avez effectivement affirmé que cela ne pouvait pas se produire et vous vous retrouvez avec une sortie saine et une trace de pile si vous vous trompez.) Les exceptions vérifiées sont vaincues. cette. Ils ont l'impression de devoir propager explicitement les erreurs en C.

dsimcha
la source
4
Solution facile: throws FileNotFoundException. Correctif:catch (FileNotFoundException e) { throw RuntimeException(e); }
Thomas Eding
5

Je pense qu'il est juste de dire que les exceptions vérifiées étaient un peu une expérience ratée. Là où ils ont mal tourné, c'est qu'ils ont cassé les couches:

  • Le module A peut être construit au-dessus du module B, où
  • Le module B peut être construit au-dessus du module C.
  • Le module C peut savoir comment identifier une erreur, et
  • Le module A peut savoir comment gérer l'erreur.

La belle séparation des concernés est rompue, car maintenant la connaissance des erreurs qui auraient pu être limitées à A et C doit maintenant être dispersée sur B.

Il y a une belle discussion sur les exceptions vérifiées sur Wikipedia.

Et Bruce Eckel a également un bel article à ce sujet. Voici une citation:

[L] es règles plus aléatoires que vous empilez sur le programmeur, des règles qui n'ont rien à voir avec la résolution du problème, plus le programmeur peut produire lentement. Et cela ne semble pas être un facteur linéaire, mais exponentiel

Je crois que pour le cas de C ++, si toutes les méthodes ont la throwsclause de spécification, alors vous pouvez avoir de belles optimisations. Je ne pense pas que Java puisse avoir ces avantages de toute façon, car RuntimeExceptionsils ne sont pas vérifiés. Un JIT moderne devrait être en mesure d'optimiser le code très bien de toute façon.

Une caractéristique intéressante d'AspectJ est l'adoucissement des exceptions: vous pouvez transformer les exceptions cochées en exceptions non vérifiées, en particulier pour améliorer la superposition.

Il est peut-être ironique que Java ait traversé tous ces problèmes, alors qu'il aurait pu vérifier à la nullplace, ce qui aurait pu être beaucoup plus précieux .

Macneil
la source
haha, je n'ai jamais pensé à la recherche d'une chose nulle comme une décision qu'ils n'ont pas prise ... Mais j'ai finalement compris que ce n'était PAS un problème inné après avoir travaillé dans Objective-C, qui permet aux nulls d'avoir des méthodes appelées.
Dan Rosenstark
3
la vérification des valeurs nulles est une douleur beaucoup plus grande - vous devez TOUJOURS vérifier - en plus, elle ne dit pas la raison pour laquelle une exception bien nommée le fait.
5

J'adore les exceptions.

Je suis cependant constamment perplexe devant leur mauvaise utilisation.

  1. Ne les utilisez pas pour la gestion des flux. Vous ne voulez pas de code qui essaie de faire quelque chose et utilise une exception comme déclencheur pour faire autre chose à la place, car les exceptions sont des objets qui doivent être créés et utiliser de la mémoire, etc., simplement parce que vous ne pourriez pas être dérangé d'en écrire sorte de vérifier si () avant de faire votre appel. C'est juste paresseux et donne à la glorieuse Exception une mauvaise réputation.

  2. Ne les avalez pas. Déjà. Dans n'importe quelle circonstance. Au minimum, vous collez quelque chose dans votre fichier journal pour indiquer qu'une exception s'est produite. Essayer de suivre votre chemin à travers le code qui avale les exceptions est un cauchemar. Encore une fois, maudissez-vous les avaleurs d'exceptions pour avoir discrédité la puissante exception!

  3. Traitez les exceptions avec votre chapeau OO. Créez vos propres objets Exception avec des hiérarchies significatives. Superposez votre logique métier et vos exceptions main dans la main. J'avais l'habitude de constater que si je commençais sur une nouvelle zone d'un système, je trouverais la nécessité de sous-classer l'exception dans la demi-heure suivant le codage, donc ces jours-ci, je commence par écrire les classes d'exception.

  4. Si vous écrivez des morceaux de code «frameworky», assurez-vous que toutes vos interfaces initiales sont écrites pour lancer vos propres nouvelles exceptions le cas échéant ... et assurez-vous que vos codeurs ont un exemple de code à l'endroit où faire quand leur code lève une IOException mais l'interface vers laquelle ils écrivent veut lever une «ReportingApplicationException».

Une fois que vous aurez essayé de faire fonctionner les exceptions pour vous, vous ne regarderez jamais en arrière.

DanW
la source
2
+1 pour de bonnes instructions. En outre, le cas échéant, utilisez initCauseou initialisez l'exception avec l'exception qui l'a provoquée. Exemple: vous disposez d'un utilitaire d'E / S dont vous n'avez besoin que d'une exception si l'écriture échoue. Cependant, il existe plusieurs raisons pour lesquelles une écriture peut échouer (impossible d'accéder, erreur d'écriture, etc.) Lancez votre exception personnalisée avec la cause une afin que le problème d'origine ne soit pas avalé.
Michael K
3
Je ne veux pas être impoli, mais qu'est-ce que cela a à voir avec les exceptions CHECKED? :)
palto
Peut être réécrit pour écrire votre propre hiérarchie d' exceptions vérifiées .
Maarten Bodewes
4

Les exceptions vérifiées se trouvent également dans ADA.

(Attention, ce post contient des croyances fermement ancrées que vous pourriez rencontrer.)

Les programmeurs ne les aiment pas et se plaignent, ou écrivent du code de déglutition d'exception.

Les exceptions vérifiées existent parce que les choses peuvent non seulement ne pas fonctionner, vous pouvez faire une analyse du mode de défaillance / effets et déterminer cela à l'avance.

Les lectures de fichiers peuvent échouer. Les appels RPC peuvent échouer. Les E / S réseau peuvent échouer. Les données peuvent être mal formatées lors de l'analyse.

Le "chemin heureux" pour le code est facile.

Je connaissais un gars à l'Université qui pouvait écrire du bon code "happy path". Aucun des cas de bord n'a jamais fonctionné. Ces jours-ci, il fait du Python pour une entreprise open source. Dit Nuff.

Si vous ne voulez pas gérer les exceptions vérifiées, ce que vous dites vraiment, c'est

While I'm writing this code, I don't want to consider obvious failure modes.

The User will just have to like the program crashing or doing weird things.

But that's okay with me because 

I'm so much more important than the people who will have to use the software

in the real, messy, error-prone world.

After all, I write the code once, you use it all day long.

Les exceptions vérifiées ne seront donc pas appréciées par les programmeurs, car cela signifie plus de travail.

Bien sûr, d'autres personnes auraient pu souhaiter que ce travail soit fait.

Ils auraient pu vouloir la bonne réponse même si le serveur de fichiers est tombé en panne / la clé USB est morte.

C'est une étrange croyance dans la communauté de la programmation que vous devriez utiliser un langage de programmation qui vous facilite la vie, que vous appréciez, lorsque votre travail consiste à écrire des logiciels. Votre travail consiste à résoudre le problème de quelqu'un, ne vous permettant pas de vous lancer dans l'improvisation programmatique de Jazz.

Si vous êtes un programmeur amateur (pas de programmation pour de l'argent), n'hésitez pas à programmer en C # ou dans un autre langage sans aucune exception vérifiée. Heck, découpez l'homme du milieu et programmez en Logo. Vous pouvez dessiner de jolis motifs sur le sol avec la tortue.

Tim Williscroft
la source
6
Belle diatribe tu y es arrivé.
Adam Lear
2
+1 "Aucun des cas limites n'a jamais fonctionné. Ces jours-ci, il fait du Python pour une entreprise open source. Nuff a dit." Classique
NimChimpsky
1
@palto: Java vous oblige à considérer le chemin difficile. Voilà la grande différence. Pour C #, vous pourriez dire «l'exception est documentée» et vous en laver les mains. Mais les programmeurs sont sujets aux erreurs. Ils oublient des choses. Lorsque j'écris du code, je veux me souvenir à 100% des fissures qu'un compilateur peut m'informer.
Thomas Eding
2
@ThomasEding Java m'oblige à gérer l'exception où la méthode est appelée. Il est très rare que je veuille réellement faire quelque chose à ce sujet dans le code appelant et généralement je veux faire remonter l'exception à la couche de gestion des erreurs de mon application. Je dois ensuite y remédier en enveloppant l'exception dans une exception Runtime ou en créant ma propre exception. Les programmeurs paresseux ont une troisième option: ignorez-la simplement parce que je m'en fiche. De cette façon, personne ne sait que l'erreur s'est produite et le logiciel a des bugs vraiment bizarres. Ce 3ème ne se produit pas avec des exceptions non contrôlées.
palto
3
@ThomasEding Faire cela changera l'interface de tout ce qui précède qui révèle inutilement les détails de l'implémentation à chaque couche de l'application. Vous ne pouvez le faire que si l'exception est quelque chose que vous souhaitez récupérer et que cela a du sens dans l'interface. Je ne veux pas ajouter d'exception IOException à ma méthode getPeople car certains fichiers de configuration sont peut-être manquants. Cela n'a tout simplement pas de sens.
palto
4

Les exceptions vérifiées auraient dû encourager les utilisateurs à gérer les erreurs proches de leur source. Plus vous vous éloignez de la source, plus les fonctions sont terminées au milieu de l'exécution et plus il est probable que le système soit entré dans un état incohérent. De plus, il est plus probable que vous tombiez sur la mauvaise exception par accident.

Malheureusement, les gens ont tendance à ignorer les exceptions ou à ajouter des spécifications de lancer toujours plus importantes. Ces deux éléments ne permettent pas de savoir ce que les exceptions vérifiées sont censées encourager. Ils sont comme transformer du code procédural pour utiliser de nombreuses fonctions Getter et Setter, ce qui en fait soi-disant OO.

Essentiellement, les exceptions vérifiées tentent de prouver un certain degré d'exactitude dans votre code. C'est à peu près la même idée que la frappe statique, en ce sens qu'elle tente de prouver que certaines choses sont correctes avant même que le code ne s'exécute. Le problème est que toutes ces preuves supplémentaires demandent un effort et est-ce que l'effort en vaut la peine?

Winston Ewert
la source
2
"Ignorer les exceptions" est souvent correct - la meilleure réponse à une exception est souvent de laisser l'application se bloquer. Pour une base théorique de ce point de vue, lisez la Construction Logicielle Orientée Objet de Bertrand Meyer . Il montre que si les exceptions sont utilisées correctement (pour les violations de contrat), il existe essentiellement deux réponses correctes: (1) laisser l'application se bloquer ou (2) résoudre le problème qui a provoqué l'exception et redémarrer le processus. Lisez Meyer pour plus de détails; il est difficile de résumer dans un commentaire.
Craig Stuntz
1
@Craig Stuntz, en ignorant les exceptions, je voulais les avaler.
Winston Ewert
Ah, c'est différent. Je suis d'accord que vous ne devriez pas les avaler.
Craig Stuntz du
"Ignorer les exceptions est souvent correct - la meilleure réponse à une exception est souvent de laisser l'application se bloquer.": Cela dépend vraiment de l'application. Si vous avez une exception non interceptée dans une application Web, le pire qui puisse arriver est que votre client signale avoir vu un message d'erreur sur une page Web et vous pouvez déployer un correctif de bogue en quelques minutes. Si vous avez une exception pendant qu'un avion atterrit, vous feriez mieux de le gérer et d'essayer de récupérer.
Giorgio
@Giorgio, très vrai. Bien que je me demande si dans le cas d'un avion ou similaire si la meilleure façon de récupérer est de redémarrer le système.
Winston Ewert