Je pense avoir lu la même interview de Bruce Eckel que vous - et cela m'a toujours dérangé. En fait, l'argument a été avancé par l'interviewé (si c'est bien le post dont vous parlez) Anders Hejlsberg, le génie de MS derrière .NET et C#.
http://www.artima.com/intv/handcuffs.html
Bien que je sois fan de Hejlsberg et de son travail, cet argument m'a toujours paru bidon. Il se résume essentiellement à :
"Les exceptions vérifiées sont mauvaises parce que les programmeurs en abusent en les attrapant toujours et en les rejetant, ce qui conduit à cacher et à ignorer des problèmes qui seraient autrement présentés à l'utilisateur".
Par "autrement présenté à l'utilisateur" Je veux dire que si vous utilisez une exception d'exécution, le programmeur paresseux l'ignorera (au lieu de l'attraper avec un bloc catch vide) et l'utilisateur la verra.
Le résumé de l'argument est le suivant "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 Gosling pour ne pas mettre de surcharges d'opérateurs en Java vient d'un argument similaire - elles déroutent le programmeur parce qu'elles sont souvent mal utilisées.
Mais en fin de compte, je trouve que c'est un argument bidon de Hejlsberg et peut-être un argument post-hoc créé pour expliquer le manque plutôt qu'une décision bien réfléchie.
Je dirais que si la surutilisation des exceptions vérifiées est une mauvaise chose et tend à conduire à une gestion négligée par les utilisateurs, leur utilisation correcte permet au programmeur de l'API d'offrir de grands avantages au programmeur du client de l'API.
Désormais, le programmeur de l'API doit veiller à ne pas lancer des exceptions vérifiées partout, sinon elles ne feront qu'ennuyer le programmeur du client. Le programmeur client très paresseux aura recours à catch (Exception) {}
comme le prévient Hejlsberg, tout bénéfice sera perdu et l'enfer s'ensuivra. Mais dans certaines circonstances, il n'y a pas de substitut à une bonne exception vérifiée.
Pour moi, l'exemple classique est l'API d'ouverture de fichiers. Chaque langage de programmation dans 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 cette phrase : Chaque programmeur client qui utilise cette API devrait savoir qu'ils doivent s'occuper de cette affaire. Et c'est là que le bât blesse : le programmeur de l'API peut-il les aider à savoir qu'ils doivent traiter ce cas par le biais de commentaires uniquement ou peut-il en effet insister le client s'en occupe.
En C, l'idiome donne quelque chose comme
if (f = fopen("goodluckfindingthisfile")) { ... }
else { // file not found ...
donde fopen
indique l'échec en retournant 0 et C (bêtement) vous laisse traiter 0 comme un booléen et... En gros, vous apprenez cet idiome et tout va bien. Mais que faire si vous êtes un noob et que vous n'avez pas appris l'idiome. Alors, bien sûr, vous commencez avec
f = fopen("goodluckfindingthisfile");
f.read(); // BANG!
et apprendre à la dure.
Notez que nous ne parlons ici que des 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 assortiment de fonctionnalités (méthodes) que vous pouvez utiliser avec un protocole clairement défini pour chacune d'entre elles.
Ce protocole clairement défini est généralement défini par une signature de méthode. Ici, fopen exige que vous lui passiez une chaîne de caractères (ou un char* dans le cas du C). Si vous lui fournissez autre chose, vous obtenez une erreur de compilation. Vous n'avez pas suivi le protocole - vous n'utilisez pas l'API correctement.
Dans certains langages (obscurs), le type de retour fait également partie du protocole. Si vous essayez d'appeler l'équivalent de fopen()
dans certains langages, sans l'assigner à une variable, vous obtiendrez également une erreur de compilation (vous ne pouvez le faire qu'avec les fonctions void).
Ce que j'essaie de dire c'est que : Dans un langage à typage statique, le programmeur de l'API encourage le client à utiliser correctement l'API en empêchant la compilation de son code client s'il commet 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 sera compilé. Pourquoi embêter 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 à typage statique).
Alors, qu'en est-il des exceptions contrôlées ?
Voici l'une des API Java que vous pouvez utiliser pour ouvrir un fichier.
try {
f = new FileInputStream("goodluckfindingthisfile");
}
catch (FileNotFoundException e) {
// deal with it. No really, deal with it!
... // this is me dealing with it
}
Tu vois cette prise ? Voici la signature de cette méthode API :
public FileInputStream(String name)
throws FileNotFoundException
Notez que FileNotFoundException
est un vérifié exception.
C'est le programmeur de l'API qui vous le dit : " Vous pouvez utiliser ce constructeur pour créer un nouveau FileInputStream mais vous
a) doit passe dans le nom du fichier comme un chaîne de caractères
b) doit accepter le la possibilité que le fichier ne soit pas être trouvé au moment de l'exécution"
Et c'est toute la question, en ce qui me concerne.
La clé est essentiellement ce que la question énonce comme "Les choses qui sont hors du contrôle du programmeur". Ma première pensée était qu'il/elle voulait dire les choses qui sont hors de la API le contrôle des programmeurs. Mais en fait, les exceptions contrôlées, lorsqu'elles sont utilisées correctement, doivent être réservées à des choses qui échappent au contrôle du programmeur du client et du programmeur de l'API. Je pense que c'est la clé pour ne pas abuser des exceptions contrôlées.
Je pense que l'ouverture du fichier illustre bien le propos. Le programmeur de l'API sait que vous pouvez lui donner un nom de fichier qui s'avère inexistant au moment où l'API est appelée, et qu'il ne pourra pas vous renvoyer ce que vous vouliez, mais qu'il devra lever une exception. Il sait é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 qu'il peut aussi être erroné au moment de l'exécution pour des raisons indépendantes de sa volonté.
L'API le rend donc explicite : Il y aura des cas où ce fichier n'existe pas au moment où vous m'appelez et vous feriez bien de vous en occuper.
Cela serait plus clair avec un contre cas. Imaginez que j'écrive une API de table. J'ai le modèle de table quelque part avec une API comprenant cette méthode :
public RowData getRowData(int row)
En tant que programmeur d'API, je sais qu'il y aura des cas où un client transmettra une valeur négative pour la ligne ou une valeur de ligne en dehors du tableau. Je pourrais donc être tenté de lancer une exception vérifiée et de forcer le client à s'en occuper :
public RowData getRowData(int row) throws CheckedInvalidRowNumberException
(Je n'appellerais pas vraiment ça "Vérifié", bien sûr.)
C'est une mauvaise utilisation des exceptions vérifiées. Le code client va être rempli d'appels pour récupérer des données de ligne, chacun d'entre eux devant 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 qui entoure ma vue de table, elle ne devrait pas laisser l'utilisateur se mettre dans un état où une ligne illégale est demandée. Il s'agit donc d'un bug de la part du programmeur du client.
Le programmeur de l'API peut toujours prévoir que le client codera de tels bogues et devrait les traiter avec une exception d'exécution comme une erreur d'exécution. IllegalArgumentException
.
Avec une exception vérifiée dans getRowData
il s'agit clairement d'un cas qui conduira le programmeur paresseux de Hejlsberg à ajouter simplement des prises vides. Dans ce cas, les valeurs de ligne illégales ne seront pas évidentes, même pour le testeur ou le développeur client qui débogue, mais elles entraîneront des erreurs en chaîne dont il sera difficile de déterminer la source. Les fusées Arianne explosent après le lancement.
Ok, alors voilà 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. Mais le CheckedInvalidRowNumberException
est un gros inconvénient qui conduit à 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 n'aime pas jeter le bébé avec l'eau du bain, alors permettez-moi de dégager quelques règles pour distinguer les bonnes exceptions contrôlées des mauvaises :
-
Hors du contrôle du client ou fermé vs ouvert :
Les exceptions vérifiées ne doivent être utilisées que lorsque le cas d'erreur échappe au contrôle de l'API. y le programmeur du client. Cela a à voir avec la façon dont ouvrir ou fermé le système est. Dans un contraint Dans une interface utilisateur où le programmeur client a le contrôle, par exemple, de tous les boutons, des commandes clavier, etc. qui ajoutent et suppriment des lignes dans la vue tableau (un système fermé), il s'agit d'un bogue de programmation client s'il tente d'extraire des données d'une ligne inexistante. Dans un système d'exploitation basé sur des fichiers où un nombre quelconque d'utilisateurs/applications peuvent ajouter et supprimer des fichiers (un système ouvert), il est concevable que le fichier demandé par le client ait été supprimé à son insu et qu'il doive donc s'en occuper.
-
Ubiquité :
Les exceptions vérifiées ne doivent pas être utilisées sur un appel d'API qui est effectué fréquemment par le client. Par fréquemment, j'entends à partir de nombreux endroits du code client, et non pas fréquemment dans le temps. Ainsi, un code client n'a pas tendance à essayer d'ouvrir le même fichier à plusieurs reprises, mais ma vue de la table reçoit des exceptions. RowData
partout, à partir de différentes méthodes. En particulier, je vais écrire beaucoup de code du type
if (model.getRowData().getCell(0).isEmpty())
et il sera douloureux de devoir envelopper dans try/catch à chaque fois.
-
Informer l'utilisateur :
Les exceptions vérifiées doivent être utilisées dans les cas où vous pouvez imaginer qu'un message d'erreur utile soit présenté à l'utilisateur final. Il s'agit de l'exception "et que ferez-vous quand ça arrivera ?" question que j'ai soulevée ci-dessus. Elle est également liée au point 1. Puisque vous pouvez prévoir que quelque chose d'extérieur à votre système client-API pourrait entraîner l'absence du fichier, vous pouvez raisonnablement en informer l'utilisateur :
"Error: could not find the file 'goodluckfindingthisfile'"
Étant donné que votre numéro de ligne illégal a été causé par un bogue interne et sans que l'utilisateur en soit responsable, il n'y a vraiment aucune information utile que vous puissiez leur donner. Si votre application ne permet pas aux exceptions d'exécution de se répercuter sur la console, elle finira probablement par leur donner un message affreux du genre :
"Internal error occured: IllegalArgumentException in ...."
En bref, si vous pensez que le programmeur de votre client n'est pas en mesure d'expliquer votre exception de manière à aider l'utilisateur, vous ne devriez probablement pas utiliser d'exception vérifiée.
Ce sont donc mes règles. Elles sont quelque peu inventées, et il y aura sans doute des exceptions (aidez-moi à les affiner si vous le souhaitez). Mais mon argument principal est qu'il existe des cas tels que FileNotFoundException
où l'exception vérifiée est une partie du contrat de l'API aussi importante et utile que les types de paramètres. Nous ne devrions donc pas nous en passer simplement parce qu'elle est mal utilisée.
Désolé, je ne voulais pas que ce soit aussi long et verbeux. 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 enveloppée (googlez-le) dès le début de votre développement. Le JDK 1.4 et les versions ultérieures fournissent un constructeur dans la section RuntimeException
pour cela, mais vous pouvez facilement créer le vôtre aussi. Voici le constructeur :
public RuntimeException(Throwable cause)
Ensuite, prenez l'habitude, chaque fois que vous devez gérer une exception vérifiée et que vous vous sentez paresseux (ou que vous pensez que le programmeur de l'API a fait un excès de zèle en utilisant l'exception vérifiée en premier lieu), de ne pas simplement avaler l'exception, de l'envelopper et de la relancer.
try {
overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
throw new RuntimeException(exception);
}
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 vous en occuper après avoir vu le problème au moment de l'exécution. Parce que, croyez-moi (et Anders Hejlsberg), vous ne reviendrez jamais à ce TODO dans votre fichier
catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
47 votes
Je ne pense pas que ce soit complètement subjectif - c'est une fonctionnalité de la langue qui a été conçue pour avoir une utilisation spécifique, plutôt que pour que chacun puisse décider de ce à quoi elle sert pour lui-même. Et ce n'est pas spécialement argumentatif, ça répond à l'avance à des réfutations spécifiques que les gens auraient pu facilement trouver.
7 votes
Allez. Considéré comme une caractéristique de la langue, ce sujet a été et peut être abordé de manière objective.
6 votes
@cletus "répondre à votre propre question" si j'avais la réponse je n'aurais pas posé la question !
2 votes
Je suis d'accord pour que ce soit CW. Il n'y a pas de "bonne" réponse ; c'est une question de méthodologie et de pratiques. Je ne suis cependant pas d'accord pour dire qu'il devrait être fermé. C'est un débat important, et plus il y a d'opinions sur le sujet, mieux c'est, selon moi.
2 votes
Je ne demande pas la "bonne réponse sur la méthodologie et les pratiques", je demande le raisonnement des personnes qui choisissent cette méthodologie particulière. Je n'ai pas demandé les mérites relatifs entre les différentes façons de traiter les exceptions. Ce n'est pas non plus CW IMO, mais une comparaison entre les deux.
9 votes
Excellente question. En C++, il n'y a pas du tout d'exceptions vérifiées et, à mon avis, cela rend la fonction d'exception inutilisable. Vous vous retrouvez dans une situation où vous devez mettre un catch autour de chaque appel de fonction que vous faites, parce que vous ne savez pas s'il pourrait lancer quelque chose.
1 votes
@TofuBeer : vous êtes chemin Réflexion excessive : posez-vous la question suivante : où les exceptions vérifiées entrent-elles en jeu au niveau OOA/OOD et comment se fait-il que 95% des langages soient parfaitement satisfaits sans le concept brisé des exceptions vérifiées ? Les exceptions vérifiées sont une idiosynchrasie Java avec laquelle les gens doivent composer parce qu'il y a des API cassées qui ont été écrites autour de ce concept cassé. Une base de code de 200KLOC ici. Nous avons défini zéro vérifié l'exception et nous jetons zéro exception vérifiée. Et notre OOP est proche de notre OOA/OOD. C'est pourquoi nous ne les aimons pas.
3 votes
Le C++ est cassé sans exceptions vérifiées (IMO) - combien de fois voyez-vous catch(...) juste pour s'assurer que les choses ne se plantent pas. Donc, puisque vous avez une grande base de code, répondez s'il vous plaît à la question simple - comment vous assurez-vous que vous ne manquez aucune exception ? Disons que vous ajoutez une nouvelle "FooException" - comment vous assurez-vous que votre programme ne se plante pas si vous ne l'attrapez pas ? Comment l'attraper à tous les bons endroits ?
1 votes
L'utilisation du TDD et des exceptions de rutime résout le problème des exceptions vérifiées (si l'on craint de manquer quelque chose). Mais malheureusement, les librairies centrales de Java elles-mêmes vous obligent à attraper ces exceptions, ce qui rend votre code plus gros, souvent sans raison. Je pense que l'utilisation de AOP/spring peut également résoudre certains problèmes - une sorte de quelque chose au-dessus de java.
2 votes
@ses TDD ne résolvent pas le problème à moins de regarder l'ensemble du code source pour savoir ce qu'il faut tester. Cela fonctionne pour la personne qui écrit le code qui lance l'exception RuntimeException, mais pas pour la personne qui écrit le code qui appelle le code qui la lance. Partez du principe que votre programme ne peut pas se planter, jamais, alors quel est l'intérêt des RuntimeExceptions ?
0 votes
@TofuBeer La personne qui écrit le code, ne sait pas avec certitude que la personne (client) qui utiliserait ce code devrait vérifier cette exception. parce que cela dépend du cas ce que le client pense à ce sujet. Le client lui-même peut décider, en lisant la définition de la méthode qui lance RuntimeException où il veut l'attraper ou non. C'est juste. C'est le respect du temps du client et du code du clinet. En particulier, je ne vois pas la raison d'utiliser CheckedExceptions dans les méthodes d'interface, quand 1000 classes l'implémentent, dont 500 ne lèvent pas cette exception du tout, mais que le client doit quand même l'attraper.
0 votes
@TofuBeer si le client du code utilise TDD, cela l'aide à éviter les mauvais cas.
2 votes
@ses clients ne seront pas nécessairement au courant de l'exception d'exécution, il n'y a aucune obligation de documenter les exceptions d'exécution. Si le client ne sait pas, comment peut-il prendre une décision éclairée ? De plus, je pars du principe que le programme ne peut jamais se planter. Les exceptions d'exécution ne s'y prêtent pas.
0 votes
@TofuBeer Le client pourrait aussi ne pas savoir comment s'y prendre. C'est ce qui arrive tout le temps. Le client avale tout jusqu'à ce qu'il ait un problème. Alors le client devrait connaître l'exception / et ce qu'il va utiliser - et ce que fait la méthode. Dès qu'il en a connaissance, il peut décider de ce qu'il veut en faire.
2 votes
Et le client sera informé de l'exception comment exactement ? Suivez-le jusqu'à la conclusion logique. Je vous donne la bibliothèque X, vous appelez la méthode y de la classe Z. Quelles exceptions d'exécution devez-vous attraper ?
0 votes
Personnellement, j'aime beaucoup les exceptions vérifiées, elles m'aident à reconnaître très tôt les problèmes potentiels dans mon code. Mais ce que je déteste, c'est que la déclaration throws fait également partie des signatures de fonctions. Cela peut vraiment vous mettre dans un problème sans solution du tout. C'est la chose que je pense que la plupart des programmeurs n'aiment pas.
0 votes
En lisant son contrat ? Les exceptions font partie du contrat d'une méthode, qu'elles soient vérifiées ou non. Vous attrapez les exceptions pour lesquelles vous pouvez réellement faire quelque chose.
2 votes
C'est le problème @Kevin, si vous ajoutez une bibliothèque tierce qui ne documente pas les exceptions, que se passe-t-il ? Le but des exceptions vérifiées est d'éviter les erreurs des programmeurs... utiliser des exceptions non vérifiées pour des choses qui devraient être traitées (des choses qui ne sont pas des erreurs de programmeurs) annule cela.
0 votes
@TofuBeer Même si vous utilisez des exceptions vérifiées, si vous ne documentez pas les exceptions (en dehors de l'en-tête de la méthode elle-même) et les conditions spécifiques dans lesquelles elles se produisent, ils ne sauront pas non plus comment traiter vos exceptions. Le problème n'est pas celui des exceptions vérifiées ou non vérifiées, mais celui de la documentation.
3 votes
Les exceptions vérifiées encouragent la documentation et, dans le cas où elles ne sont pas documentées, elles sont toujours connues. Les exceptions non vérifiées peuvent plus facilement ne pas être documentées et, pire, ne pas être connues jusqu'à ce qu'elles se produisent dans un environnement de production.
0 votes
@DimitriC. C'est ce que je ressens en tant que développeur C# maintenant !
0 votes
"La VM est allergique aux cacahuètes et quelqu'un a fait tomber un pot de cacahuètes sur elle" - Oui, vous voulez vraiment que votre machine meure sans possibilité de récupération aux moments les plus critiques... Je ne vois rien de mal à ça... NE JAMAIS UTILISER L'ERREUR
0 votes
Il semble que personne n'ait remarqué le problème des interfaces. Si vous implémentez une interface, vous ne pouvez lancer que les exceptions qui sont déclarées dans l'interface. Il est donc impossible de réagir à une erreur avec une exception vérifiée dans de nombreux cas différents. Voir ma réponse.
6 votes
L'argument le plus fort que je connaisse pour Les exceptions vérifiées sont qu'elles n'existaient pas à l'origine dans Java, et que lorsqu'elles ont été introduites, elles ont permis de découvrir des centaines de bogues dans le JDK. Ceci est quelque peu antérieur à Java 1.0. Personnellement, je ne m'en passerais pas, et je suis en désaccord total avec Bruce Eckel et d'autres sur ce point.
1 votes
@user207421 D'après mon expérience, presque tout ce que Bruce Eckel dit est faux quand il s'agit de Java...
1 votes
Voici la question. De nombreuses bibliothèques ne lèvent pas d'exceptions vérifiées, de sorte que le programmeur n'est pas conscient de tout ce qui peut mal tourner et ne peut pas s'y préparer. A moins qu'il/elle ne lise le code source, mais qui lit une quelconque documentation ? À moins qu'il ne teste très soigneusement, mais qui élabore des tests comme s'il s'agissait d'une chose très importante ? Aux deux questions, je pense que la réponse est très peu.