web-dev-qa-db-fra.com

Le cas contre les exceptions vérifiées

Depuis plusieurs années, je ne parviens pas à obtenir une réponse décente à la question suivante: pourquoi certains développeurs refusent-ils les exceptions vérifiées? J'ai eu de nombreuses conversations, lu des articles sur des blogs, lu ce que Bruce Eckel avait à dire (la première personne que j'ai vue s'est exprimée contre eux).

J'écris actuellement un nouveau code et porte une attention toute particulière à la manière 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 je mène se termine par la même question qui reste sans réponse ... laissez-moi la mettre en place:

En général (d'après comment Java a été conçu),

  • L'erreur 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 les choses que le programmeur a mal faites (le programmeur a quitté la fin d'un tableau)
  • Exception (sauf RuntimeException) 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 descripteur de fichier 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 courant 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 courant que j'entends est que les exceptions vérifiées rendent plus difficile le 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 vous contentez des erreurs de manipulation, vos utilisateurs ne seront pas trop heureux lorsque le programme se terminera sans indiquer clairement pourquoi.

Pour la foule "cela rend 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 lève une exception IOException, celle-ci doit être transformée en une exception mieux adaptée à ce qui se passe.

Je n'ai pas de problème avec le fait d'encapsuler Main avec catch (Exception) (ou dans certains cas, catch (Throwable)) pour que le programme puisse se terminer correctement, mais j'attrape toujours les exceptions spécifiques dont je ai besoin. au minimum, affichez un message d'erreur approprié.

La question à laquelle les gens ne répondent jamais est la suivante:

Si vous lancez des sous-classes RuntimeException au lieu de sous-classes Exception, comment savoir ce que vous êtes censé intercepter?

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 faux.

Si vous attrapez Throwable, vous traitez les exceptions système et les VM erreurs (et autres erreurs similaires) de la même manière. Cela me semble faux.

Si la réponse est que vous ne saisissez que les exceptions que vous savez être levées, comment savoir quelles sont celles qui sont lancé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 se sentent-elles pas de la sorte?

Donc, si vous n'aimez pas les exceptions vérifiées, pouvez-vous expliquer pourquoi ne pas ET répondre à la question à laquelle il n'est pas répondu s'il vous plaît?

Edit: Je ne cherche pas à savoir quand utiliser l'un ou l'autre modèle. Ce que je recherche, c'est pourquoi, les gens sortent de RuntimeException parce qu'ils n'aiment pas sortir d'Exception et/ou pourquoi ils interceptent une exception. puis renvoyez une exception RuntimeException plutôt que d'ajouter des jets à leur méthode. Je veux comprendre la motivation pour ne pas aimer les exceptions vérifiées.

427
TofuBeer

Je pense avoir lu la même interview que vous avez faite avec Bruce Eckel - et elle m'a toujours dérangée. En fait, l'argument a été présenté par la personne interrogée (s'il s'agit bien du message dont vous parlez), Anders Hejlsberg, le génie MS derrière .NET et C #.

http://www.artima.com/intv/handcuffs.html

Fan, bien que je sois de Hejlsberg et de son travail, cet argument m’a toujours paru faux. Cela revient essentiellement à:

"Les exceptions cochées sont mauvaises parce que les programmeurs en abusent simplement en les attrapant et en les écartant, ce qui conduit à des problèmes cachés et ignorés 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 (par opposition à l'attraper avec un bloc catch vide ) et l’utilisateur le verra.

Le résumé du 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" .

Cet argument a quelque chose de véridique et, en fait, je soupçonne que Goslings est motivé pour ne pas inclure d'opérateur prioritaire dans Java provient d'un argument similaire: ils confondent le programmeur car ils sont souvent maltraités.

Mais au final, je trouve que c'est un faux argument de Hejlsberg et peut-être un post-hoc créé pour expliquer le manque plutôt qu'une décision bien réfléchie.

Je dirais que bien que l'utilisation excessive d'exceptions vérifiées soit une mauvaise chose et tende à conduire les utilisateurs à une gestion peu soignée, mais leur utilisation correcte permet au programmeur d'API de donner un grand avantage au programmeur client d'API.

Maintenant, le programmeur d'API doit faire attention à ne pas jeter des exceptions vérifiées partout, sinon il va simplement ennuyer le programmeur client. Le programmeur client très paresseux aura recours à attraper (Exception) {} Comme Hejlsberg le prévient et tous les avantages seront perdus et un enfer s'ensuivra. Mais dans certaines circonstances, rien ne remplace 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 (au moins sur les systèmes de fichiers) possède une API qui vous permet, quelque part, d’ouvrir un fichier. Et chaque programmeur client utilisant cette API sait qu’il doit gérer le cas où le fichier qu’il tente d’ouvrir n’existe pas. Permettez-moi de reformuler la phrase suivante: Chaque programmeur client utilisant cette API doit savoir qu'il doit traiter ce cas. Et puis il y a un problème: le programmeur d'API peut-il les aider à savoir qu'ils doivent le gérer en commentant seuls ou peut-il en effet insister auprès du client?.

En C l'idiome va quelque chose comme

  if (f = fopen("goodluckfindingthisfile")) { ... } 
  else { // file not found ...

fopen indique un échec en renvoyant 0 et C (bêtement) vous permet de traiter 0 comme un booléen et ... En gros, vous apprenez cet idiome et tout va bien. Mais que se passe-t-il si vous êtes un noob et que vous n'avez pas appris l'idiome? Ensuite, bien sûr, vous commencez avec

   f = fopen("goodluckfindingthisfile");
   f.read(); // BANG! 

et apprendre à la dure.

Notez que nous ne parlons ici que de langages fortement typés: Il existe une idée claire de ce qu’est une API dans un langage fortement typé: c’est un ensemble de fonctionnalités (méthodes) que vous pouvez utiliser avec un protocole clairement défini pour chaque langage.

Ce protocole clairement défini est généralement défini par une signature de méthode. Ici, fopen nécessite 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'affecter à une variable, vous obtiendrez également une erreur de compilation (vous ne pouvez le faire qu'avec des fonctions void).

Ce que je veux dire, c’est que: Dans un langage à typage statique, le programmeur d’API encourage le client à utiliser correctement l’API en empêchant la compilation de son code client s’il comporte des erreurs évidentes.

(Dans un langage typé dynamiquement, comme Ruby, vous pouvez passer n'importe quoi, par exemple un float, en tant que nom de fichier - et il se compilera. Pourquoi harceler l'utilisateur avec des exceptions vérifiées si vous n'allez même pas contrôler les arguments de la méthode. Les arguments présentés ici s’appliquent uniquement aux langues à typage statique.)

Alors, qu'en est-il des exceptions vérifié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 une exception vérifiée .

Le programmeur de l'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é au moment de l'exécution "

Et c'est tout le problème en ce qui me concerne.

La clé est fondamentalement ce que la question énonce comme "des choses qui sont hors du contrôle du programmeur". Ma première pensée a été qu'il/elle parle de choses qui sont hors du contrôle des programmeurs [~ # ~] api [~ # ~] . 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 d'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 pouvez lui attribuer un nom de fichier inexistant au moment de l'appel de l'API et qu'il ne pourra pas vous retourner ce que vous vouliez, mais qu'il 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 du fichier soit correct au moment où il a appelé l'appel, mais il se peut également qu'il se trompe lors 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 que vous auriez mieux fait 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:

public RowData getRowData(int row) 

Maintenant, 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 de la table. Je pourrais donc être tenté de lancer une exception vérifiée et d’obliger le client à la gérer:

public RowData getRowData(int row) throws CheckedInvalidRowNumberException

(Je n'appellerais pas vraiment cela "vérifié" bien sûr.)

C'est une mauvaise utilisation des exceptions vérifiées. Le code client sera plein d'appels pour extraire des données de ligne, chacun devant utiliser un essai/un accroc, et pour quoi? 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 d'API peut toujours prédire que le client codera de tels bogues et devra le gérer avec une exception d'exécution telle qu'un IllegalArgumentException.

Avec une exception vérifiée dans getRowData, il s'agit clairement d'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 plus évidentes, même pour le testeur ou le débogage du développeur client; elles entraîneront plutôt des erreurs de déclenchement difficiles à identifier avec précision. Les fusées Arianne vont exploser après le lancement.

Bon, voici le problème: je dis que l'exception vérifiée FileNotFoundException n'est pas simplement une bonne chose, mais un outil essentiel de 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, conduisant à une mauvaise programmation et devrait être évité. Mais comment faire la différence.

Je suppose que ce n’est pas une science exacte et que cela sous-tend et peut-être justifie dans une certaine mesure l’argument de Hejlsberg. Mais je ne suis pas heureux de jeter le bébé avec l'eau du bain ici, alors permettez-moi d'extraire ici quelques règles pour distinguer les bonnes exceptions vérifiées des mauvaises:

  1. 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 échappe à la fois à l'API et au programmeur client . Cela a à voir avec comment ouvrir ou fermer le système est. Dans une interface utilisateur contrainte où le programmeur client a le contrôle, disons, de tous les boutons, commandes clavier, etc. qui ajoutent et suppriment des lignes de la vue tableau ( 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 illimité d'utilisateurs/d'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, de sorte qu'ils devraient pouvoir s'en occuper. .

  2. Ubiquité:

    Les exceptions cochées ne doivent pas être utilisées sur un appel d'API effectué fréquemment par le client. Souvent, j'entends beaucoup d'endroits dans le code client - pas souvent dans le temps. Donc, un code client n'a pas tendance à essayer d'ouvrir le même fichier beaucoup, mais ma vue sous forme de tableau obtient RowData partout avec des méthodes différentes. En particulier, je vais écrire beaucoup de code comme

    if (model.getRowData().getCell(0).isEmpty())
    

    et il sera pénible de devoir envelopper/essayer à chaque fois.

  3. Informer l'utilisateur:

    Les exceptions cochées doivent être utilisées dans les cas où vous pouvez imaginer un message d'erreur utile présenté à l'utilisateur final. C'est la "et que ferez-vous quand cela arrivera?" la question que j'ai soulevée ci-dessus. Cela concerne également le point 1. Etant donné que vous pouvez prédire qu'un élément extérieur à votre système d'API client pourrait empêcher le fichier de s'y trouver, vous pouvez en informer raisonnablement 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 qu’il n’ya aucune faute de la part de l’utilisateur, il n’ya vraiment aucune information utile que vous puissiez lui donner. Si votre application ne laisse pas les exceptions d'exécution transpercer la console, elle finira probablement par leur envoyer un message laid, comme:

    "Internal error occured: IllegalArgumentException in ...."
    

    En bref, si vous pensez que votre programmeur client ne peut pas expliquer votre exception de manière à aider l'utilisateur, vous ne devriez probablement pas utiliser une exception cochée.

Donc, ce sont mes règles. Un peu artificiel, et il y aura sans doute des exceptions (aidez-moi à les raffiner si vous le souhaitez). Mais mon argument principal est qu'il existe des cas comme FileNotFoundException où l'exception vérifiée est aussi importante et utile dans le contrat d'API que les types de paramètres. Nous ne devrions donc pas nous en passer uniquement parce qu’il est utilisé à mauvais escient.

Désolé, je ne voulais pas faire ça si long et si mal. Permettez-moi de terminer avec deux suggestions:

A: Programmeurs d'API: utilisez les exceptions vérifiées avec parcimonie pour préserver leur utilité. En cas de doute, utilisez une exception non contrôlée.

B: Les 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 dans RuntimeException pour cela, mais vous pouvez également créer le vôtre également. Voici le constructeur:

public RuntimeException(Throwable cause)

Prenez ensuite l'habitude de gérer une exception vérifiée et que vous vous sentez paresseux (ou si vous pensez que le programmeur d'API était trop zélé pour utiliser l'exception vérifiée en premier lieu), ne vous contentez pas d'avaler l'exception, de l'envelopper. et le relancer.

try {
  overzealousAPI(thisArgumentWontWork);
}
catch (OverzealousCheckedException exception) {
  throw new RuntimeException(exception);  
}

Mettez ceci dans l'un des petits modèles de code de votre IDE et utilisez-le lorsque vous vous sentez paresseux. Ainsi, si vous devez vraiment gérer l'exception vérifiée, vous serez obligé de revenir et de le gérer après avoir constaté le problème au moment de l'exécution. Parce que, croyez-moi (et Anders Hejlsberg), vous ne reviendrez jamais à ce TODO dans votre

catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
266
Rhubarb

La chose à propos des exceptions vérifiées est qu'elles ne sont pas vraiment des exceptions par la compréhension habituelle du concept. Au lieu de cela, ce sont des valeurs de retour alternatives d'API.

L’idée même des exceptions est qu’une erreur générée quelque part dans la chaîne d’appel peut bouillonner et être gérée par un code plus haut, sans que le code intervenant n’en ait à s’inquiéter. Les exceptions cochées, en revanche, exigent que tous les niveaux de code entre le lanceur et le receveur déclarent qu'ils connaissent toutes les formes d'exceptions pouvant les traverser. En pratique, cela n’est guère différent si les exceptions vérifiées étaient simplement des valeurs de retour spéciales que l’appelant devait vérifier. par exemple. [pseudocode]:

public [int or IOException] writeToStream(OutputStream stream) {
    [void or IOException] a= stream.write(mybytes);
    if (a instanceof IOException)
        return a;
    return mybytes.length;
}

Puisque Java ne peut pas utiliser d'autres valeurs de retour, ni de simples nuplets en ligne comme valeurs de retour, les exceptions vérifiées constituent une réponse raisonnable.

Le problème est que beaucoup de code, y compris de grandes parties de la bibliothèque standard, utilisent de manière incorrecte 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 exception RuntimeException? Dans toutes les autres langues, je peux laisser une exception IO arriver, et si je ne fais rien pour la gérer, mon application s’arrêtera et j’aurai une trace de pile pratique à regarder. C’est le meilleure chose qui puisse arriver.

Peut-être que deux méthodes de l'exemple que vous souhaitez capturer toutes les exceptions IOExceptions de l'ensemble du processus d'écriture dans le flux, interrompez le processus et sautez dans le code de rapport d'erreur; in Java vous ne pouvez pas faire cela sans ajouter 'lève une exception IOException' à chaque niveau d'appel, même les niveaux qui ne font pas eux-mêmes d'E/S. Ces méthodes ne devraient pas avoir besoin de connaître le traitement des exceptions; ajouter des exceptions à leurs signatures:

  1. augmente inutilement le couplage;
  2. rend les signatures d'interface très fragiles à changer;
  3. rend le code moins lisible;
  4. est tellement ennuyeux que la réaction habituelle des programmeurs est de vaincre le système en faisant quelque chose d’horrible comme "lève Exception", "catch (Exception e) {}", ou en encapsulant tout dans une RuntimeException (ce qui rend le débogage plus difficile).

Et puis il y a beaucoup d'exceptions de bibliothèques simplement ridicules comme:

try {
    httpconn.setRequestMethod("POST");
} catch (ProtocolException e) {
    throw new CanNeverHappenException("oh dear!");
}

Lorsque vous devez encombrer votre code avec un bricolage ridicule comme celui-ci, il n’est pas étonnant que les exceptions vérifiées reçoivent un tas de haine, même s’il s’agit en réalité d’une mauvaise conception d’API.

Un autre mauvais effet concerne Inversion of Control, 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 ne peut pas parce que 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 exception RuntimeException, qui est encore plus complexe à écrire pour gérer le traitement des exceptions.

Les exceptions vérifiées telles qu’implémentées dans Java et sa bibliothèque standard désignent les valeurs standard, standard et standard. Dans un langage déjà détaillé, ce n’est pas une victoire.

179
bobince

Plutôt que de revenir sur 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 que j'ai écrit ce bloc de code:

try {
  // do stuff
} catch (AnnoyingcheckedException e) {
  throw new RuntimeException(e);
}

99% du temps, je ne peux rien y faire. Enfin, les blocs effectuent le nettoyage nécessaire (ou du moins ils le devraient).

J'ai également perdu le compte du nombre de fois où j'ai vu ceci:

try {
  // do stuff
} catch (AnnoyingCheckedException e) {
  // do nothing
}

Pourquoi? Parce que quelqu'un devait y faire face et était paresseux. Était-ce mal? Sûr. Ça arrive? Absolument. Et si cela était une exception non contrôlée à la place? L'application serait morte (ce qui est préférable pour avaler une exception).

Et puis nous avons un code exaspérant qui utilise des exceptions comme forme de contrôle de flux, comme Java.text.Format . Bzzzt. Faux. Un utilisateur qui insère "abc" dans un champ numérique d'un formulaire ne fait pas exception.

Ok, je suppose que c'était trois raisons.

74
cletus

Je sais que c'est une vieille question, mais j'ai passé un certain temps à lutter contre les exceptions vérifiées et j'ai quelque chose à ajouter. S'il te plaît, pardonne-moi pour la longueur!

Mon principal boeuf avec des exceptions vérifiées est qu'ils ruinent le polymorphisme. Il est impossible de les faire bien jouer avec les interfaces polymorphes.

Prenez le bon vieux Java List ). == Nous avons des implémentations en mémoire communes comme ArrayList et LinkedList. Nous avons également la classe skeletal AbstractList qui facilite la conception de nouveaux types de liste. Pour une liste en lecture seule, nous n'avons besoin que de deux méthodes. : size() et get(int index).

Cet exemple de classe WidgetList lit des objets de taille fixe de type Widget (non affichés) à partir d'un fichier:

class WidgetList extends AbstractList<Widget> {
    private static final int SIZE_OF_WIDGET = 100;
    private final RandomAccessFile file;

    public WidgetList(RandomAccessFile file) {
        this.file = file;
    }

    @Override
    public int size() {
        return (int)(file.length() / SIZE_OF_WIDGET);
    }

    @Override
    public Widget get(int index) {
        file.seek((long)index * SIZE_OF_WIDGET);
        byte[] data = new byte[SIZE_OF_WIDGET];
        file.read(data);
        return new Widget(data);
    }
}

En exposant les widgets à l’aide de l’interface familière List, vous pouvez récupérer des éléments (list.get(123)) ou parcourir une liste (for (Widget w : list) ...) sans avoir besoin de connaître la fonction WidgetList lui-même. On peut transmettre cette liste à toutes les méthodes standard utilisant des listes génériques ou l'envelopper dans un Collections.synchronizedList. Le code qui l'utilise n'a pas besoin de savoir si les "Widgets" sont fabriqués sur-le-champ, proviennent d'un tableau ou sont lus dans un fichier, une base de données, un réseau ou un futur relais de sous-espace. Cela fonctionnera toujours correctement car l'interface List est correctement implémentée.

Sauf que ce n'est pas. La classe ci-dessus n'est pas compilée car les méthodes d'accès aux fichiers peuvent générer une IOException, une exception vérifiée que vous devez "capturer ou spécifier". Vous ne pouvez pas le spécifier comme jeté - le compilateur ne vous le laissera pas car cela violerait le contrat de l'interface List. Et il n'y a aucun moyen utile que WidgetList lui-même puisse gérer l'exception (comme je l'expliquerai plus tard).

Apparemment, la seule chose à faire est de capturer et de rediffuser les exceptions vérifiées comme une exception non vérifiée:

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw new WidgetListException(e);
    }
}

public static class WidgetListException extends RuntimeException {
    public WidgetListException(Throwable cause) {
        super(cause);
    }
}

((Edit: Java 8 a ajouté une classe UncheckedIOException) _ pour ce cas précis: pour capturer et renverser IOExceptions à travers limites de la méthode polymorphe. Cela prouve bien mon point!))

Donc, les exceptions vérifiées ne fonctionnent tout simplement pas dans des cas comme celui-ci. Vous ne pouvez pas les jeter. Idem pour un Map intelligent sauvegardé par une base de données ou une implémentation de Java.util.Random Connectée à une source d'entropie quantique via un port COM. Dès que vous essayez de faire quelque chose de nouveau avec la mise en œuvre d'une interface polymorphe, le concept des exceptions vérifiées échoue. Mais les exceptions cochées sont tellement insidieuses qu'elles ne vous laisseront toujours pas en paix, car vous devez toujours capturer et rediffuser l'une des méthodes de niveau inférieur, encombrant le code et encombrant le suivi de la pile.

Je trouve que l'interface omniprésente Runnable est souvent sauvegardée dans ce coin si elle appelle quelque chose qui jette des exceptions vérifiées. Il ne peut pas lancer l'exception telle qu'elle est, il ne peut donc qu'encombrer le code en interceptant et en retranchant en tant que RuntimeException.

En fait, vous pouvez créez des exceptions vérifiées non déclarées si vous recourez à des hacks. Au moment de l'exécution, la machine virtuelle Java ne se soucie pas des règles d'exception vérifiées, nous devons donc uniquement tromper le compilateur. La façon la plus simple de le faire est d’abuser des génériques. Voici ma méthode (nom de classe indiqué car (avant Java 8), il est requis dans la syntaxe d'appel de la méthode générique)):

class Util {
    /**
     * Throws any {@link Throwable} without needing to declare it in the
     * method's {@code throws} clause.
     * 
     * <p>When calling, it is suggested to prepend this method by the
     * {@code throw} keyword. This tells the compiler about the control flow,
     * about reachable and unreachable code. (For example, you don't need to
     * specify a method return value when throwing an exception.) To support
     * this, this method has a return type of {@link RuntimeException},
     * although it never returns anything.
     * 
     * @param t the {@code Throwable} to throw
     * @return nothing; this method never returns normally
     * @throws Throwable that was provided to the method
     * @throws NullPointerException if {@code t} is {@code null}
     */
    public static RuntimeException sneakyThrow(Throwable t) {
        return Util.<RuntimeException>sneakyThrow1(t);
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> RuntimeException sneakyThrow1(
            Throwable t) throws T {
        throw (T)t;
    }
}

Hourra! En utilisant cela, nous pouvons lancer une exception vérifiée de n'importe quelle profondeur sur 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":

@Override
public int size() {
    try {
        return (int)(file.length() / SIZE_OF_WIDGET);
    } catch (IOException e) {
        throw sneakyThrow(e);
    }
}

Malheureusement, la dernière insulte des exceptions vérifiées est que le compilateur refuse de vous permettre de capturer une exception vérifiée si, à son avis erroné, il ne pourrait pas avoir été jeté. (Les exceptions non vérifiées n'ont pas cette règle.) Pour attraper l'exception levée sournoisement, nous devons faire ceci:

try {
    ...
} catch (Throwable t) { // catch everything
    if (t instanceof IOException) {
        // handle it
        ...
    } else {
        // didn't want to catch this one; let it go
        throw t;
    }
}

C'est un peu gênant, mais du côté positif, cela reste un peu plus simple que le code permettant d'extraire une exception vérifiée encapsulée dans un RuntimeException.

Heureusement, la déclaration throw t; Est légale ici, même si le type de t est vérifié, grâce à une règle ajoutée dans Java 7 concernant le renvoi des exceptions interceptées .


Lorsque les exceptions vérifiées rencontrent un polymorphisme, le cas contraire est également un problème: lorsqu'une méthode est spécifiée comme générant potentiellement une exception vérifiée, mais qu'une implémentation remplacée ne le fait pas. Par exemple, les méthodes abstraites OutputStream de la classe write de == spécifient throws IOException. ByteArrayOutputStream est une sous-classe qui écrit dans un tableau en mémoire au lieu d'une véritable source d'E/S. Ses méthodes write surchargées ne peuvent pas causer IOExceptions. Elles ne possèdent donc pas de clause throws et vous pouvez les appeler sans vous soucier de l'exigence catch-or-specify.

Sauf pas toujours. Supposons que Widget dispose d'une méthode pour l'enregistrer dans un flux:

public void writeTo(OutputStream out) throws IOException;

Déclarer que cette méthode accepte un OutputStream brut 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 non essentielle à la gestion d'une exception qui ne peut pas se produire:

ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
    someWidget.writeTo(out);
} catch (IOException e) {
    // can't happen (although we shouldn't ignore it if it does)
    throw new RuntimeException(e);
}

Comme d'habitude, les exceptions vérifiées font obstacle. Si vos variables sont déclarées en tant que type de base ayant davantage d'exigences en matière d'exceptions ouvertes, vous devez ajouter des gestionnaires pour ces exceptions même si vous savez ils ne se produiront pas dans votre application.

Mais attendez, les exceptions cochées sont en fait donc ennuyeuses, cela elles ne vous laisseront même pas faire l'inverse ! Imaginez que vous attrapiez actuellement un IOException levé par write appelle un OutputStream, mais vous voulez changer le type déclaré de la variable en un ByteArrayOutputStream, le compilateur vous accusera de vouloir attraper une exception vérifiée qu'il dit ne peut pas être levée.

Cette règle cause des problèmes absurdes. Par exemple, l'une des trois méthodes write de OutputStream est pas remplacée par ByteArrayOutputStream. Plus précisément, write(byte[] data) est une méthode pratique qui écrit le tableau complet en appelant write(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 de la méthode de commodité à un argument telle quelle. La méthode héritée fait exactement ce qu'il faut, mais elle inclut une clause non désirée throws. C’était peut-être un oubli dans la conception de ByteArrayOutputStream, mais ils ne peuvent jamais le réparer, car cela briserait la compatibilité de la source avec tout code qui intercepte l’exception - l’exception qui n’a jamais, n’est jamais et jamais. sera jeté!

Cette règle est également agaçante lors de l’édition et du débogage. Par exemple, parfois, je commente un appel de méthode temporairement, et s'il peut avoir levé une exception vérifiée, le compilateur va maintenant se plaindre de l'existence des blocs locaux try et catch . Donc, je dois commenter ceux-là aussi, et maintenant, quand on édite le code à l'intérieur, le IDE va indenter au mauvais niveau parce que les { Et } Sont C'est une petite plainte, mais il semble que la seule chose que les exceptions vérifiées font est de causer des problèmes.


J'ai presque fini. Ma dernière frustration avec les exceptions vérifiées est que sur la plupart des sites d'appels, vous ne pouvez rien y faire d'utile. Idéalement, en cas de problème, un gestionnaire compétent, spécialisé dans l'application, peut informer l'utilisateur du problème et/ou mettre fin à l'opération ou réessayer, le cas échéant. Seul un gestionnaire en haut de la pile peut le faire car c'est le seul à connaître l'objectif général.

Au lieu de cela, nous obtenons l'idiome suivant, qui est répandu comme moyen de fermer le compilateur:

try {
    ...
} catch (SomeStupidExceptionOmgWhoCares e) {
    e.printStackTrace();
}

Dans une interface graphique ou un programme automatisé, le message imprimé ne sera pas vu. Pire, il reste 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 moment, au plus tard l'objet d'exception original aura disparu. Cet idiome n'est pas meilleur que le On Error Resume Next De BASIC ou le error_reporting(0); de PHP.

Appeler une sorte de classe de journalisation n’est pas beaucoup mieux:

try {
    ...
} catch (SomethingWeird e) {
    logger.log(e);
}

C'est tout aussi paresseux que e.printStackTrace(); et continue de fonctionner avec du code dans un état indéterminé. De plus, le choix d'un système de journalisation particulier ou d'un autre gestionnaire dépend de 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 situé plus haut dans la pile d'appels (ou il est défini comme le thread gestionnaire d'exceptions non captées ). Donc, dans la plupart des endroits, il suffit de lever l'exception plus haut dans la pile. Par exemple, throw e;. Les exceptions vérifiées ne font que gêner.

Je suis sûr que les exceptions vérifiées semblaient être une bonne idée lors de la conception du langage, mais dans la pratique, je les ai trouvées gênantes et sans aucun avantage.

46
Boann

Eh bien, il ne s'agit pas d'afficher un stacktrace 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 des exceptions). Si vous choisissez de ne pas avaler ce détail, vous devez continuer à ajouter des déclarations de jets dans l'ensemble de votre application. Cela signifie 1) qu’un nouveau type d’exception affectera un grand nombre de signatures de fonction et 2) vous pouvez manquer une instance spécifique de l’exception que vous voulez réellement intercepter (par exemple, vous ouvrez 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 parce que la signature throws IOException, il est facile de négliger cela).

En fait, je traite de cette situation maintenant dans une application. Nous avons reconditionné presque des exceptions sous la forme 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 le traitement des erreurs aux niveaux supérieurs, en mettant en œuvre une logique de nouvelle tentative, etc. Tout est AppSpecificException, cependant, nous ne pouvons donc pas dire "Si une exception IOException est levée, réessayez" ou "Si ClassNotFound est levé, abandonner complètement". Nous ne disposons pas d'un moyen fiable pour nous rendre à l'exception réelle car les éléments sont reconditionnés à plusieurs reprises au fur et à mesure qu'ils 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 ce que vous voulez et/ou pouvez gérer. Tout le reste bouillonne comme si vous l'aviez repensé 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 tombait dans 3 catégories:

  1. Attrapez et gérez une exception spécifique. Cela consiste à implémenter la logique de nouvelle tentative, par exemple.
  2. Catch and Rethrow autre exceptions. Tout ce qui se passe ici est généralement la journalisation, et c’est généralement un message banal du type "Impossible d’ouvrir $ filename". Ce sont des erreurs que vous ne pouvez rien faire; seuls les niveaux supérieurs en savent assez pour le gérer.
  3. Tout attraper et afficher un message d'erreur. C’est généralement à la racine même d’un répartiteur, ce qui permet de s’assurer que l’erreur peut être communiquée à l’appelant via un mécanisme ne faisant pas exception à la règle (dialogue contextuel, marshaling d’un objet Erreur RPC, etc.).
45

SNR

Premièrement, les exceptions vérifiées diminuent le "rapport signal sur bruit" du code. Anders Hejlsberg parle également de la programmation impérative et déclarative, qui est un concept similaire. Quoi qu'il en soit, considérez les extraits de code suivants:

Mettre à jour l'interface utilisateur à partir de threads non UI en Java:

try {  
    // Run the update code on the Swing thread  
    SwingUtilities.invokeAndWait(() -> {  
        try {
            // Update UI value from the file system data  
            FileUtility f = new FileUtility();  
            uiComponent.setValue(f.readSomething());
        } catch (IOException e) {  
            throw new UncheckedIOException(e);
        }
    });
} catch (InterruptedException ex) {  
    throw new IllegalStateException("Interrupted updating UI", ex);  
} catch (InvocationTargetException ex) {
    throw new IllegalStateException("Invocation target exception updating UI", ex);
}

Mettre à jour l'interface utilisateur à partir d'un thread non UI en C #:

private void UpdateValue()  
{  
   // Ensure the update happens on the UI thread  
   if (InvokeRequired)  
   {  
       Invoke(new MethodInvoker(UpdateValue));  
   }  
   else  
   {  
       // Update UI value from the file system data  
       FileUtility f = new FileUtility();  
       uiComponent.Value = f.ReadSomething();  
   }  
}  

Ce qui me semble beaucoup plus clair. Lorsque vous commencez à faire de plus en plus de travail d'interface utilisateur dans Swing, les exceptions vérifiées commencent à devenir vraiment ennuyeuses et inutiles.

Prison break

Pour mettre en œuvre 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 ne fonctionnent pas. Prenons une liste sauvegardée par une base de données, un système de fichiers ou toute autre implémentation générant une exception vérifiée. La seule implémentation possible consiste à intercepter l'exception vérifiée et à la redéfinir en tant qu'exception non contrôlée:

@Override
public void clear()  
{  
   try  
   {  
       backingImplementation.clear();  
   }  
   catch (CheckedBackingImplException ex)  
   {  
       throw new IllegalStateException("Error clearing underlying list.", ex);  
   }  
}  

Et maintenant, vous devez demander à quoi sert tout ce code? Les exceptions vérifiées ajoutent simplement du bruit, l'exception a été interceptée mais non gérée et la conception par contrat (en termes d'exceptions vérifiées) est tombée en panne.

Conclusion

  • Attraper des exceptions est différent de les gérer.
  • Les exceptions cochées ajoutent du bruit au code.
  • La gestion des exceptions fonctionne bien en C # sans eux.

J'ai blogué à ce sujet précédemment .

24
Luke Quinane

Artima publié une interview avec l'un des architectes de .NET, Anders Hejlsberg, qui couvre de manière approfondie les arguments contre les exceptions vérifiées. Un petit aperçu:

La clause throws, du moins la manière dont elle est mise en œuvre en Java, ne vous oblige pas nécessairement à gérer les exceptions, mais si vous ne les gérez pas, elle vous oblige à reconnaître précisément les exceptions susceptibles de passer. Vous devez soit intercepter les exceptions déclarées, soit les placer dans votre propre clause throws. Pour contourner cette exigence, les gens font des choses ridicules. Par exemple, ils décorent chaque méthode avec "Throws Exception". Cela annule complètement la fonctionnalité et vous obligez le programmeur à écrire plus de gobbledy gunk. Cela n'aide personne.

22
Le Dude

Au départ, je suis d’accord avec vous, car j’ai toujours été favorable aux exceptions vérifiées et j’ai commencé à réfléchir aux raisons pour lesquelles je n’aime pas ne pas avoir d’exceptions vérifiées dans .Net. Mais ensuite, j'ai réalisé que je n'enfais pas autant d'exceptions vérifiées.

Pour répondre à votre question, oui, j'aime bien que mes programmes affichent des traces de pile, de préférence très laides. Je veux que l'application explose en un horrible tas des messages d'erreur les plus laids que vous pourriez souhaiter voir.

Et la raison en est que, si cela se produit, je dois le réparer, et je le dois tout de suite. Je veux savoir tout de suite 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? C'est trop facile d'écrire ce qui suit:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}

Et je sais que vous pouvez dire que c’est une mauvaise pratique et que "la solution" est de faire quelque chose avec l’exception (laissez-moi deviner, enregistrez-le?), Mais dans le monde réel (tm), la plupart des programmeurs ne le font tout simplement pas. il.

Alors oui, je ne veux pas attraper les exceptions si je ne suis pas obligé de le faire, et je veux que mon programme explose de façon spectaculaire lorsque je me trompe. Échouer silencieusement est le pire résultat possible.

20
tsimon

L'article Effective Java Exceptions explique bien quand utiliser sans vérification et quand utiliser les exceptions vérifiées. Voici quelques citations de cet article pour mettre en évidence les points principaux:

Contingence: Condition attendue exigeant une réponse alternative à partir d'une méthode pouvant être exprimée en fonction de la destination de la méthode. L'appelant de la méthode s'attend à ce genre de situation et a une stratégie pour y faire face.

Erreur: Condition non planifiée empêchant une méthode d'atteindre son objectif, qui ne peut pas être décrite sans référence à sa mise en œuvre interne.

(SO n'autorise pas les tables, vous voudrez peut-être lire ce qui suit à partir de la page d'origine ...)

Contingence

  • Est considéré comme: une partie de la conception
  • Devrait se produire: régulièrement mais rarement
  • Qui s'en soucie: Le code en amont qui appelle la méthode
  • Exemples: modes de retour alternatifs
  • Meilleure cartographie: une exception vérifiée

Défaut

  • Est considéré comme: une mauvaise surprise
  • Devrait se produire: jamais
  • Qui s'en soucie: les personnes qui ont besoin de résoudre le problème
  • Exemples: bugs de programmation, dysfonctionnements matériels, erreurs de configuration, fichiers manquants, serveurs indisponibles
  • Meilleure cartographie: une exception non contrôlée
20
Esko Luontola

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, retournons la question et posons la question suivante: quand ou pourquoi les exceptions vérifiées sont-elles attrayantes, c’est-à-dire pourquoi voudriez-vous que le compilateur impose la déclaration des exceptions?

La réponse est évidente: Parfois vous devez intercepter une exception, ce qui n'est possible que si le code appelé offre une classe d'exception spécifique pour l'erreur tu es intéressé par.

Par conséquent, l'argument for exceptions vérifiées est que le compilateur oblige les programmeurs à déclarer les exceptions levées, et heureusement le programmeur documentera également les classes d'exceptions spécifiques et les erreurs qui en résultent. leur.

En réalité cependant, toujours trop souvent un paquet com.acme jette uniquement un AcmeException plutôt que des sous-classes spécifiques. Les appelants doivent ensuite gérer, déclarer ou rediffuser AcmeExceptions, mais ne peuvent toujours pas savoir avec certitude si un AcmeFileNotFoundError s'est produit ou un AcmePermissionDeniedError.

Donc, si vous êtes seulement intéressé par un AcmeFileNotFoundError, la solution consiste à déposer une demande de fonctionnalité auprès des programmeurs ACME et leur dire d'implémenter, de déclarer et de documenter cette sous-classe de AcmeException.

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 tiles. Il ne s'agit toujours que de la qualité de l'API.

En conséquence, les langues sans exception cochée ne sont généralement pas moins bien loties. Les programmeurs pourraient être tentés de lancer des instances non spécifiques d'une classe générale Error plutôt que d'un AcmeException, mais s'ils se soucient de la qualité de leurs API, ils apprendront à introduire un AcmeFileNotFoundError après tout.

Globalement, la spécification et la documentation des exceptions ne diffèrent pas beaucoup de celles des méthodes ordinaires, par exemple. Celles-ci sont également une question de conception d’API, et si un programmeur oublie d’implémenter ou d’exporter une fonctionnalité utile, l’API doit être améliorée pour que vous puissiez l’utiliser utilement.

Si vous suivez ce raisonnement, il devrait être évident que le "souci" de déclarer, intercepter et rediffuser des exceptions qui est si courant dans des langages tels que Java ajoute souvent peu de valeur.

Il est également intéressant de noter que le Java VM n'a pas ) pas ont des exceptions vérifiées - seul le compilateur Java les vérifie et les fichiers de classe contenant des déclarations d'exception modifiées sont compatibles au moment de l'exécution. Java VM = la sécurité n’est pas améliorée par les exceptions vérifiées, mais uniquement par le style de codage.

19
David Lichteblau

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 des exceptions vérifiées assez souvent avec une gestion correcte des erreurs, et d'autres qui ne le font pas.

Jusqu'à présent, j'ai trouvé qu'il était plus facile de travailler avec la base de code avec Checked Exceptions. Quand j’utilise l’API de quelqu'un d’autre, c’est bien que je puisse voir exactement le type de conditions d’erreur auxquelles je peux m'attendre lorsque j’appelle le code et que je le gère correctement, soit en enregistrant, en affichant ou en ignorant (Oui, il existe des cas valables pour exceptions, comme une implémentation ClassLoader). Cela donne au code que j'écris une opportunité de récupération. Je propage toutes les exceptions d'exécution jusqu'à ce qu'elles soient mises en cache et gérées avec un code de traitement d'erreur générique. Lorsque je trouve une exception cochée que je ne souhaite 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 exception RuntimeException et la laisse bouillonner. N'avalez jamais une exception sans une bonne raison (et les bonnes raisons de le faire sont plutôt rares)

Lorsque je travaille avec une base de code qui ne contient pas d'exceptions vérifiées, cela me rend un peu plus difficile de savoir à l'avance à quoi puis-je m'attendre lorsque j'appelle la fonction, ce qui peut casser terriblement certaines choses.

Tout cela est bien sûr une question de préférence et de compétence du développeur. Les deux méthodes de programmation et de gestion des erreurs peuvent être tout aussi efficaces (ou non efficaces), aussi je ne dirais pas qu'il existe The One Way.

Au total, je trouve plus facile de travailler avec Checked Exceptions, spécialement dans les grands projets avec beaucoup de développeurs.

14
Mario Ortegón

Catégories d'exception

Quand je parle d’exceptions, je me réfère toujours à article Vexing d’Eric Lippert article de blog. Il place des exceptions dans ces catégories:

  • Fatal - Ces exceptions sont pas de votre faute: vous ne pouvez pas empêcher cela et vous ne pouvez pas les gérer de manière sensée. Par exemple, OutOfMemoryError ou ThreadAbortException.
  • Boneheaded - Ces exceptions sont de votre faute: vous auriez dû les éviter et elles représentent des bugs dans votre code. Par exemple, ArrayIndexOutOfBoundsException, NullPointerException ou n'importe quel IllegalArgumentException.
  • vexant - Ces exceptions sont pas exceptionnelles, pas de votre faute, vous ne pouvez pas les empêcher, mais vous devrez traitez avec eux. Ils sont souvent le résultat d’une décision de conception malheureuse, telle que le lancement de NumberFormatException de Integer.parseInt au lieu de fournir un Integer.tryParseInt méthode qui renvoie un booléen false en cas d'échec de l'analyse.
  • Exogène - Ces exceptions sont généralement exceptionnelles, pas de votre faute, vous ne pouvez pas (raisonnablement) les empêcher, mais vous devez les manipuler. Par exemple, FileNotFoundException.

Un utilisateur de l'API:

  • ne doit pas gérer fatal ou boneheaded exceptions.
  • devrait gérer vexer exceptions, mais elles ne doivent pas apparaître dans une API idéale.
  • must gérer exogène exceptions.

Exceptions vérifiées

Le fait que l'utilisateur de l'API must 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 choses: le nombre et les types d'arguments attendus par l'appelé, le type de valeur de retour que l'appelant peut attendre et les exceptions que l'appelant doit gérer.

Puisque vexing les exceptions ne doivent pas exister dans une API, seules celles-ci exogènes doivent être exceptions vérifiées pour faire partie de la méthode de contrat. 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 traité. Le traitement d'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 pas ignorer l'exception et a été averti.

Problèmes d'API

Mais toute API ayant vérifié vexing et fatal des exceptions (par exemple, le JCL) alourdira inutilement les utilisateurs de l'API. De telles exceptions have doivent être gérées, mais soit l'exception est si commune qu'elle n'aurait pas dû être une exception, ou rien ne peut être fait lors de sa manipulation. Et this provoque Java détestent les exceptions vérifiées.

En outre, de nombreuses API n’ont pas de hiérarchie de classes d’exception appropriée, ce qui entraîne la représentation de tous les types d’exceptions non exogènes par une seule classe d’exception vérifiée (par exemple, IOException). Et cela entraîne également les Java) à détester les exceptions vérifiées.

Conclusion

Exogenous Les exceptions sont celles qui ne sont pas de votre faute, qui n'auraient pas pu être empêchées et qui doivent être traitées. Celles-ci forment un petit sous-ensemble de toutes les exceptions pouvant être levées. Les API ne doivent avoir que vérifiées exogènes exceptions , et toutes les autres exceptions non vérifiées. Cela créera de meilleures API, mettra moins de pression sur l'utilisateur de l'API et réduira donc le besoin de tout capturer, d'avaler ou de rediffuser les exceptions non contrôlées.

Donc, ne détestez pas Java et ses exceptions vérifiées. Au lieu de cela, détestez les API qui abusent des exceptions vérifiées.

13

Ok ... 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 d'échecs contractuels de cette API. Lorsque vous utilisez un langage fortement typé statiquement, tel que Java, si vous n'utilisez pas d'exceptions vérifiées, vous devez vous appuyer sur la documentation ad-hoc et sur les conventions pour indiquer le risque d'erreur. Cela supprime tous les avantages que le compilateur peut apporter en erreur de manipulation et vous êtes complètement laissé à la bonne volonté des programmeurs.

Donc, on supprime l’exception Checked, comme cela a été fait en C #, comment alors peut-on transmettre la possibilité d’une erreur par programme et par structure? Comment informer le code client que de 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 il en va de même pour les exceptions non contrôlées. Je dis d'attendre quelques années, lorsque les API sont empilées sur plusieurs couches et que vous demanderez le retour d'une sorte de moyen structuré pour acheminer les défaillances.

Prenons le cas où l’exception a été lancée quelque part au bas des couches de l’API et a éclaté parce que personne ne savait que cette erreur pouvait se produire, même s’il s’agissait d’un type d’erreur qui était très plausible lorsque le code d’appel l'a jeté (FileNotFoundException par exemple, par opposition à VogonsTrashingEarthExcept ... Dans ce cas, peu importe si nous le gérons ou non, car il ne reste plus rien à faire pour le gérer).

Beaucoup ont fait valoir que l'impossibilité de 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 le fichier est chargé à un moment donné ... moi, en tant qu'utilisateur de cette API, je ne peux que répondre ... "Qui diable êtes-vous pour décider quand mon programme devrait planter! " 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 hésitation, mais cela signifie-t-il qu’il est souhaitable de traiter les exceptions ? Ne pouvons-nous pas être quelque part au milieu, où l’exception serait refondue et encapsulée chaque fois qu’elle passait à un nouveau niveau d’abstraction afin qu’elle ait réellement une signification?

Enfin, la plupart des arguments que je vois sont les suivants: "Je ne veux pas traiter les exceptions, beaucoup de gens ne veulent pas traiter les exceptions. Les exceptions cochées me forcent à les traiter et je déteste les exceptions cochées" Pour éliminer complètement ce mécanisme et le reléguer au gouffre de goto enfer est juste idiot et manque de jugement et de vision.

Si nous éliminons l'exception cochée, nous pourrions également éliminer le type de retour pour les fonctions et toujours renvoyer une variable "anytype" ... Cela simplifierait tellement la vie maintenant, n'est-ce pas?

6
Newtopian

En effet, les exceptions vérifiées d'un côté augmentent la robustesse et la correction de votre programme (vous êtes obligé de faire des déclarations correctes de vos interfaces - les exceptions qu'une méthode lève sont essentiellement un type de retour spécial). D'autre part, vous rencontrez le problème suivant: étant donné que les exceptions "bouillonnent", vous devez très souvent modifier de nombreuses méthodes (tous les appelants, et les appelants des appelants, etc.) lorsque vous modifiez les exceptions. méthode jette.

Les exceptions vérifiées dans Java ne résolvent pas ce dernier problème; C # et VB.NET rejettent le bébé avec l'eau du bain.

Une approche de Nice qui prend la voie du milieu est décrite dans le présent document OOPSLA 2005 (ou le rapport technique associé .)

En bref, cela vous permet de dire: method g(x) throws like f(x), ce qui signifie que g lève toutes les exceptions f. Voilà, des exceptions vérifiées sans le problème des modifications en cascade.

Bien que ce soit un document académique, je vous encourage à le lire (en partie) car il explique très bien quels sont les avantages et les inconvénients des exceptions vérifiées.

6
Kurt Schelfthout

Anders parle des pièges des exceptions vérifiées et de la raison pour laquelle il les a laissés en dehors de C # dans épisode 97 de la radio Software Engineering.

5
Chuck Conway

Le problème

Le problème le plus grave que je vois avec le mécanisme de gestion des exceptions est que il introduit la duplication de code à grande échelle ! Soyons honnêtes: dans la plupart des projets, dans 95% des cas, tout ce que les développeurs ont à faire à l'exception est de le communiquer d'une manière ou d'une autre à l'utilisateur (et, dans certains cas, à l'équipe de développement, par exemple en envoyant un e-mail). -mail avec la trace de la pile). Donc, généralement, la même ligne/le même bloc de code est utilisé à chaque endroit où l'exception est gérée.

Supposons que nous effectuions une journalisation simple dans chaque bloc catch pour un type d'exception vérifiée:

try{
   methodDeclaringCheckedException();
}catch(CheckedException e){
   logger.error(e);
}

S'il s'agit d'une exception courante, il peut même y avoir plusieurs centaines de blocs try-catch dans une base de code plus grande. Supposons maintenant que nous devions introduire la gestion des exceptions basée sur une boîte de dialogue contextuelle au lieu de la consignation sur la console ou commencer à envoyer un courrier électronique supplémentaire à l'équipe de développement.

Attendez un instant ... allons-nous vraiment éditer 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) à centraliser la gestion des exceptions. Une instance de gestionnaire d'exceptions est injectée par notre framework Dependency Injection à chaque classe devant manipuler des exceptions. Ainsi, le modèle typique de gestion des exceptions ressemble maintenant à ceci:

try{
    methodDeclaringCheckedException();
}catch(CheckedException e){
    exceptionHandler.handleError(e);
}

Maintenant, pour personnaliser notre traitement des exceptions, il 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 de EH et tirer parti des fonctionnalités fournies par notre infrastructure de DI. En modifiant la configuration de notre infrastructure DI, nous pouvons facilement basculer la mise en œuvre EH au niveau mondial ou fournir des implémentations spécifiques de EH aux classes ayant des besoins particuliers en matière de gestion des exceptions (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 finale de l’application (par exemple, développement - consigner l’erreur et arrêter l’application, enregistrer l’erreur avec plus de détails et laisser l’application se poursuivre) sans effort.

Dernière chose

Dernier point, mais non le moindre, il peut sembler que le même type de centralisation peut être obtenu en passant simplement nos exceptions "up" jusqu'à ce qu'elles arrivent à une classe de traitement des exceptions de premier niveau. 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.

5
Piotr Sobczyk

Comme les gens l'ont déjà dit, les exceptions vérifiées n'existent pas dans le bytecode Java. C'est simplement un mécanisme de compilation, ce qui ne diffère pas des autres vérifications de syntaxe. Je vois beaucoup d'exceptions vérifiées comme si le compilateur se plaignait à propos d’une condition redondante: if(true) { a; } b;. Cela est utile, mais j’aurais peut-être fait cela exprès, alors laissez-moi ignorer vos avertissements.

Le fait est que vous ne pourrez pas forcer tous les programmeurs à "faire ce qui est bien" si vous imposez des exceptions vérifiées et que tout le monde subit à présent des dommages collatéraux qui vous haïssent pour la règle que vous avez établie.

Corrigez les mauvais programmes! N'essayez pas de corriger le langage pour ne pas les autoriser! Pour la plupart des gens, "faire quelque chose à propos d'une exception" consiste simplement à en parler à l'utilisateur. Je peux tout aussi bien informer l'utilisateur de l'existence d'une exception non contrôlée, par conséquent, gardez vos classes d'exceptions cochées en dehors de mon API.

5
Martin

Pour tenter de répondre uniquement à la question sans réponse:

Si vous lancez des sous-classes RuntimeException au lieu de sous-classes Exception, comment savoir ce que vous êtes censé intercepter?

La question contient un raisonnement spécieux IMHO. Ce n'est pas parce que l'API vous dit ce qu'elle lance que vous le 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 levant 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 utilisateur XPath entré, alors je voudrais probablement détecter et signaler toutes les exceptions vérifiées et non vérifiées qui sont émises par l'opération.

Si, toutefois, j'écris un moteur de traitement, je traiterai probablement une exception XPathException (cochée) de la même manière qu'un NPE: je la laisserais atteindre le sommet du thread de travail, ignorer le reste du lot, consigner le problème (ou envoyez-le à un service d'assistance pour diagnostic) et laissez un retour à l'utilisateur pour qu'il contacte l'assistance.

5
Dave Elton

Les exceptions vérifiées étaient, dans leur forme originale, une tentative de gestion des imprévus plutôt que des échecs. L'objectif louable était de mettre en évidence des points prévisibles spécifiques (impossible de se connecter, fichier introuvable, etc.) et de s'assurer que les développeurs les gèrent.

Ce qui n’a jamais été inclus dans le concept initial, c’était de forcer la déclaration d’un grand nombre de défaillances systémiques et irrécupérables. Ces échecs n’ont jamais eu raison d’être déclarés comme des exceptions vérifiées.

Les échecs sont généralement possibles dans le code, et les conteneurs EJB, web & Swing/AWT répondent déjà à cela en fournissant un gestionnaire d'exceptions "demande échouée" le plus externe. 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 pas de traitement ou de récupération que les exceptions vérifiées puissent faire, que les exceptions d'exécution ne puissent pas .

Le principal argument contre les exceptions "vérifiées" est que la plupart des exceptions ne peuvent pas être corrigées. Le fait est que nous ne possédons pas le code/sous-système qui a éclaté. Nous ne pouvons pas voir l’implémentation, nous n’en sommes pas responsables et nous ne pouvons pas la réparer.

Si notre application n'est pas une base de données, nous ne devrions pas essayer de la réparer. Cela violerait le principe de l'encapsulation.

Les domaines de JDBC (SQLException) et RMI pour EJB (RemoteException) ont été particulièrement problématiques. Plutôt que d’identifier les imprévus réparables selon le concept original d ’" exception vérifiée ", ces problèmes de fiabilité systémique omniprésents forcés, qui ne sont pas réellement réparables, doivent être largement déclarés.

L’autre défaut grave 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, prise tardive" Les exceptions cochées font peu mais gênent cela.

Nous avons un problème évident en Java d'exiger des milliers de blocs try-catch à ne rien faire, une proportion importante (40% ou plus) étant mal codés. Presque aucun de ceux-ci n'implémente un traitement ou une fiabilité sincère , mais imposer une surcharge de codage importante.

Enfin, les "exceptions vérifiées" sont assez incompatibles avec la programmation fonctionnelle FP).

Leur insistance sur le "traitement immédiat" est en contradiction avec la meilleure pratique de traitement des exceptions "interception tardive" et avec toute structure FP) qui abstrait les boucles/ou le flux de contrôle.

Beaucoup de gens parlent de "manipuler" les exceptions vérifiées, mais parlent à travers leurs chapeaux. Poursuivre après un échec avec des données nulles, incomplètes ou incorrectes pour prétendre le succès ne gère rien. C'est une faute d'ingénierie/fiabilité de la forme la plus basse.

Manquer correctement est la stratégie correcte la plus élémentaire pour gérer une exception. Annoncer la transaction, consigner l'erreur et signaler à l'utilisateur une réponse "en échec" est une bonne pratique - et, plus important encore, éviter que des données commerciales incorrectes ne soient enregistrées dans la base de données.

Les autres stratégies de gestion des exceptions sont les suivantes: "réessayer", "reconnecter" ou "ignorer", au niveau métier, sous-système ou requête. Toutes ces stratégies sont des stratégies de fiabilité générales et fonctionnent bien/mieux avec les exceptions d'exécution.

Enfin, il est de loin préférable d’échouer que de fonctionner avec des données incorrectes. Continuer entraînera soit des erreurs secondaires, éloignées de la cause initiale et plus difficiles à déboguer; ou entraînera éventuellement la validation de données erronées. Les gens se font virer pour cela.

Voir:
- http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/

4
Thomas W

Cet article est le meilleur texte sur la gestion des exceptions en Java que j'ai jamais lu.

Il privilégie les exceptions non vérifiées, mais ce choix est expliqué très précisément et basé sur des 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 contrôlés de ce fil. Surtout cet argument (qui semble être très populaire) est couvert:

Prenons le cas où l’exception a été lancée quelque part au bas des couches de l’API et a éclaté parce que personne ne savait que cette erreur pouvait se produire, même s’il s’agissait d’un type d’erreur qui était très plausible lorsque le code d’appel l'a jeté (FileNotFoundException par exemple, par opposition à VogonsTrashingEarthExcept ... Dans ce cas, peu importe si nous le gérons ou non, car il ne reste plus rien à faire pour le gérer).

L'auteur "réponses":

Il est absolument incorrect de supposer que toutes les exceptions d'exécution ne doivent pas être interceptées et autorisées à se propager jusqu'au "sommet" de l'application. (...) Pour chaque condition exceptionnelle devant être traitée de manière distincte - par les exigences système/métier -, les programmeurs doivent décider où l'attaquer et ce qu'il faut faire une fois que la condition est détectée. Cela doit être fait strictement en fonction des besoins réels de l'application et non en fonction d'une alerte du compilateur. Toutes les autres erreurs doivent pouvoir se propager librement vers le gestionnaire le plus élevé où elles seraient consignées et une action en douceur (peut-être même une fin) sera-t-elle entreprise.

Et la pensée principale ou l'article est:

En ce qui concerne la gestion des erreurs dans les logiciels, la seule hypothèse sûre et correcte qui puisse être faite est qu'une défaillance peut se produire dans absolument chaque sous-routine ou module existant!

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. Cette exception doit être gérée au moins par 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 que peu de gens semblent découvrir ce grand article :-(. Je recommande de tout coeur à tous ceux qui hésitent quelle approche est préférable de prendre le temps de le lire.

4
Piotr Sobczyk

Mon écriture sur c2.com n'a pratiquement pas changé par rapport à sa forme d'origine: CheckedExceptionsAreIncompatibleWithVisitorPattern

En résumé:

Le modèle de visiteur et ses parents sont une classe d'interfaces dans laquelle l'appelant indirect et l'implémentation d'interface connaissent une exception, mais l'interface et l'appelant direct forment une bibliothèque qui ne peut pas le 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 d'exceptions vérifiées dans de tels cas est une grande quantité de code, sinon inutile, qui supprime essentiellement la contrainte d'exception vérifiée du compilateur au moment de l'exécution.

En ce qui concerne le 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 soit des exceptions IO, des exceptions de communication (pour lesquelles les API sont distinguées), soit des erreurs fatales (bogues de programme ou problème grave sur le serveur de support)), cela ne devrait donc pas être trop difficile si nous autorisons une trace de pile pour un problème grave de serveur.

4
Joshua

Ce n’est pas un argument contre le concept pur des exceptions vérifiées, mais la hiérarchie de classes Java utilise pour eux est une foutaise. Nous appelons toujours les choses simplement des "exceptions" - ce qui est correct, parce que la spécification de 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 on imagine? Eh bien non, parce que Exceptions sont des exceptions, de même que les exceptions sont Exceptions, à l'exception de celles qui sont pasExceptions, car les autres exceptions sont en réalité Error s, qui sont l'autre type d'exception, une sorte d'exception extra-exceptionnelle cela ne devrait jamais arriver sauf quand cela arrive, et que vous ne devriez jamais attraper, sauf parfois. Sauf que ce n'est pas tout, vous pouvez également définir d'autres exceptions qui ne sont ni Exceptions ni Errors mais simplement Throwable .

Lesquelles de ces exceptions sont "vérifiées"? Throwables sont des exceptions vérifiées, sauf si elles sont également Errors, qui sont des exceptions non vérifiées, et puis il y a le Exceptions, qui sont également Throwables et sont le type principal de l'exception vérifiée, sauf qu'il existe une exception à cela aussi, à savoir que s'ils sont aussi RuntimeException s, parce que c'est l'autre type d'exception non vérifiée.

À quoi servent RuntimeExceptions? Comme son nom l'indique, il s'agit d'exceptions, comme tous les Exceptions, et elles se produisent à l'exécution, comme toutes les exceptions en réalité, sauf que les RuntimeExceptions sont exceptionnels par rapport aux autres exécutions. Exceptions car ils ne sont pas censés se produire, sauf lorsque vous faites une erreur stupide, bien que RuntimeExceptions ne soient jamais Errors, ils sont donc destinés à des choses exceptionnellement erronées mais qui ne sont pas réellement Errors. Sauf pour RuntimeErrorException , qui est vraiment un RuntimeException pour Errors. Mais toutes les exceptions ne sont-elles pas supposées représenter des situations erronées? Oui, tous. À l'exception de ThreadDeath , exception exceptionnellement peu exceptionnelle, comme le précise la documentation, il s'agit d'une "occurrence normale" et c'est pourquoi ils en ont fait un type de Error.

Quoi qu’il en soit, puisque nous divisons toutes les exceptions au milieu en Errors (pour les exceptions d’exécution exceptionnelles, donc décochées) et en Exceptions (pour les erreurs d’exécution moins exceptionnelles, donc vérifiées sauf lorsque ils ne le sont pas), nous avons maintenant besoin de deux types différents de chacune des exceptions. Nous avons donc besoin de IllegalAccessError et IllegalAccessException , et InstantiationError et InstantiationException , et NoSuchFieldError et NoSuchFieldException , et NoSuchMethodError et NoSuchMethodException , et ZipError et ZipException .

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 jeter sans vérification. Si vous pensez que vous pouvez obtenir un UndeclaredThrowableException , sauf dans les autres cas, où il pourrait apparaître comme UnexpectedException , ou un UnknownException (qui n'a pas de relation avec UnknownError , qui ne concerne que les "exceptions sérieuses"), ou un ExecutionException , ou un InvocationTargetException , ou un ExceptionInInitializerError .

Oh, et il ne faut pas oublier Java) la nouvelle nouvelle 8 UncheckedIOException , qui est une exception RuntimeException conçue pour vous permettre de lancer le concept de vérification des exceptions en dehors de la fenêtre en encapsulant vérifié IOException exceptions causées par des erreurs d'E/S (qui ne causent pas IOError exceptions , bien que cela existe aussi) qui sont exceptionnellement difficiles à manipuler et qu’il est nécessaire de ne pas les vérifier.

Merci Java!

4
Boann

Un problème avec les exceptions vérifiées est que des 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 tendent à être mal utilisées. L'exemple parfait en est la méthode Java.sql.Connection de close() . Il peut lancer un SQLException , même si vous avez déjà indiqué explicitement que vous en avez terminé avec la connexion. Quelle information pourrait fermer () éventuellement transmettre que vous vous souciez?

Habituellement, lorsque je ferme () une connexion *, Cela ressemble à ceci:

try {
    conn.close();
} catch (SQLException ex) {
    // Do nothing
}

Aussi, 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 revenir à Java (nous utilisons à la fois Java et C # où je travaille).

* En tant que commentaire supplémentaire, Connection.close () d'un PooledConnection n'a même pas close une connexion, mais vous devez toujours intercepter l'exception SQLException car il s'agit d'une exception vérifiée .

3
Powerlord

Ici un argument de == contre les exceptions vérifiées (de joelonsoftware.com):

Le raisonnement est que je considère que les exceptions ne valent pas mieux que les "goto", considérées comme nuisibles depuis les années 1960, en ce qu'elles créent un saut brutal d'un point de code à un autre. En fait, ils sont nettement pires que ceux que nous connaissons:

  • Ils sont invisibles dans le code source. En regardant un bloc de code, y compris des fonctions qui peuvent ou non renvoyer des exceptions, il n'y a aucun moyen de voir quelles exceptions peuvent être levées et d'où. Cela signifie que même une inspection minutieuse du code ne révèle pas de bugs potentiels.
  • Ils créent trop de points de sortie possibles pour une fonction. Pour écrire du code correct, vous devez vraiment penser à tous les chemins de code possibles dans votre fonction. Chaque fois que vous appelez une fonction qui peut générer une exception et ne l'attrape pas sur-le-champ, vous créez des opportunités pour des bugs inattendus provoqués par des fonctions qui se sont terminées abruptement, laissant ainsi les données incohérentes ou d'autres chemins de code que vous n'avez pas connus. Penser à.
3
finnw

Le programmeur a besoin de connaître all des exceptions qu'une méthode peut générer pour pouvoir l'utiliser correctement. Ainsi, le frapper à la tête avec quelques-unes des exceptions n’aide pas nécessairement un programmeur négligent à éviter les erreurs.

Le faible coût l'emporte sur le faible coût (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 exige souvent de manière inflexible un travail strict du programmeur. Il existe un calcul coûts-avantages, et la barre doit être haute pour un contrôle menant à une erreur de temps de compilation. Il serait plus utile que IDE assume le rôle de communiquer les exceptions qu'une méthode peut générer (y compris celles qui sont inévitables). Bien que ce ne soit peut-être pas aussi fiable sans déclarations d'exception forcées, les exceptions seraient toujours déclarées dans la documentation, et la fiabilité d'un avertissement IDE) n'est pas cruciale.

3
Aleksandr Dubinsky

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) jeter des exceptions non cochées. Cela signifie que vous pouvez isoler vos dépendances de la bibliothèque (c’est-à-dire que vous n’avez pas besoin de relancer leurs exceptions ou de lancer Exception - généralement une mauvaise pratique). Spring couche DAO en est un excellent exemple.

D'autre part, les exceptions de l'API de base Java de l'API Java) devraient 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 devraient presque toujours être traitées de manière spécifique (c.-à-d. Que votre réaction à un InterruptedException n'est pas le même que votre réaction à un IllegalArgumentException). Le fait que vos exceptions soient vérifiées oblige les développeurs à déterminer si une condition est gérable ou non. (Cela dit, j'ai rarement vu InterruptedException manipulé correctement!)

Une dernière chose - un RuntimeException n'est pas toujours "lorsqu'un développeur a quelque chose de mal". Une exception d'argument non conforme est levée lorsque vous essayez de créer un enum à l'aide de valueOf et qu'il n'y a pas de enum de ce nom. Ce n'est pas nécessairement une erreur du développeur!

2
oxbow_lakes

Les bons prouvent que Checked Exception ne sont pas nécessaires sont:

  1. Beaucoup de frameworks qui fonctionnent pour Java. Comme Spring, qui enveloppe l'exception JDBC des exceptions non contrôlées, en lançant des messages dans le journal
  2. Beaucoup de langages qui sont venus après Java, même au sommet sur la Java - ils ne les utilisent pas
  3. Exceptions cochées, il s'agit d'une prédiction généreuse sur la manière dont le client utiliserait le code qui lève une exception. Mais un développeur qui écrit ce code ne saura jamais à propos du système et de l'entreprise dans laquelle le client du code travaille. Par exemple, les méthodes Interfcace qui forcent à renvoyer une exception vérifiée. Il y a 100 implémentations sur le système, 50 voire 90 implémentations ne lèvent pas cette exception, mais le client doit toujours intercepter cette exception si la référence utilisateur à cette interface est utilisée. Ces 50 ou 90 implémentations ont tendance à gérer ces exceptions en elles-mêmes, en ajoutant une exception au journal (et c'est un bon comportement pour elles). Que devrions-nous faire avec ça? Je ferais mieux d'avoir une logique d'arrière-plan qui ferait tout ce travail - envoyer un message au journal. Et si, en tant que client de code, j’ai le sentiment que j’ai besoin de gérer l’exception, je le ferai. Je peux l'oublier, d'accord - mais si j'utilise TDD, toutes mes étapes sont couvertes et je sais ce que je veux.
  4. Un autre exemple lorsque je travaille avec I/O en Java, cela me force à vérifier toutes les exceptions, si le fichier n'existe pas? Qu'est-ce que je devrais faire avec ça? S'il n'existe pas, le système ne passerait pas à l'étape suivante. Le client de cette méthode n'obtiendrait pas le contenu attendu de ce fichier - il peut gérer une exception d'exécution, sinon je devrais d'abord vérifier Checked Exception, mettre un message à consigner, puis renvoyer une exception de la méthode. Non ... non - je ferais mieux de le faire automatiquement avec RuntimeEception, qui le fait/s'allume automatiquement. Il n'y a aucun sens à le gérer manuellement - je serais heureux d'avoir vu un message d'erreur dans le journal (AOP peut aider avec cela .. quelque chose qui corrige Java). Si, au final, je décide que ce système affiche un message contextuel à l'intention de l'utilisateur final, ce n'est pas un problème.

J'étais content si Java me fournissait un choix ce qu'il faut utiliser pour travailler avec les bibliothèques principales, comme I/O. Like fournit deux copies des mêmes classes - un encapsulé avec RuntimeEception. Ensuite, nous pouvons comparer ce que les gens utiliseraient. Pour le moment, cependant, beaucoup de gens feraient mieux de choisir un framework sur Java ou un langage différent. Comme Scala, JRuby, peu importe. crois juste que Sun avait raison.

2
ses

Une chose importante dont personne n'a parlé est la façon dont cela interfère avec les interfaces et les expressions lambda.

Disons que vous définissez un MyAppException extends Exception. C'est l'exception de premier niveau héritée de toutes les exceptions levées par votre application. Chaque méthode déclare throws MyAppException qui est un peu nuancé, mais gérable. Un gestionnaire d'exceptions enregistre une exception et notifie l'utilisateur d'une manière ou d'une autre.

Tout semble aller jusqu'à ce que vous souhaitiez implémenter une interface qui ne vous appartient pas. Évidemment, il ne déclare pas son intention de lancer MyApException, le compilateur ne vous permet donc pas de lancer l'exception à partir de là.

Cependant, si votre exception s'étend RuntimeException, il n'y aura pas de problème d'interface. Vous pouvez mentionner volontairement l'exception dans JavaDoc si vous le souhaitez. Mais à part cela, il ne fait que bouillonner silencieusement, pour être pris dans votre couche de gestion des exceptions.

2
Vlasec

Nous avons vu des références à l'architecte en chef de C #.

Voici un autre point de vue d'un Java gars sur le moment d'utiliser les exceptions vérifiées. Il reconnaît de nombreux inconvénients mentionnés par d'autres: Exceptions effectives

1
Jacob Toronto

Bien que j'ai lu toute la page, je ne trouve toujours pas un seul argument raisonnable contre les exceptions vérifiées. La plupart des gens parlent plutôt d'une conception d'API médiocre, soit à certaines classes Java), soit à leurs propres classes.

Le seul scénario où cette fonctionnalité peut être gênante est la prototypage. Cela pourrait être résolu en ajoutant un mécanisme au langage (par exemple, une annotation @supresscheckedexceptions). Mais pour la programmation régulière, je pense que les exceptions vérifiées sont une bonne chose.

0
Mister Smith

J'ai beaucoup lu 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, voici ce que je retiens: des exceptions vérifiées dans du code de bas niveau (IO, réseau , OS, etc.) et des exceptions non vérifiées dans les API de haut niveau/au niveau de l’application.

Même s’il n’est pas facile de tracer une ligne de démarcation entre eux, j’ai trouvé qu’il est vraiment ennuyeux/difficile d’intégrer plusieurs API/bibliothèques sous le même toit sans encombrer de nombreuses exceptions vérifiées, mais parfois Il 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 de nombreuses bibliothèques et les intègre sous la même API, une API entièrement basée sur les exceptions non contrôlées. Ce cadre fournit une API de haut niveau qui, à l’origine, était remplie d’exceptions vérifiées et n’avait que plusieurs options non vérifiées. exceptions (exception d'initialisation, ConfigurationException, etc.) et je dois dire n'était pas très amical. La plupart du temps, vous deviez intercepter ou rediffuser des exceptions que vous ne savez pas comment gérer, ou que vous ne vous en souciez même pas (pour ne pas être confondu avec vous, vous devriez ignorer les exceptions), en particulier du côté client où un seul clic pourrait lancer 10 exceptions possibles (vérifiées).

La version actuelle (3ème version) utilise uniquement des exceptions non contrôlées et dispose d'un gestionnaire d'exception global qui est responsable de la gestion de tout élément non capturé. L’API fournit un moyen d’enregistrer les gestionnaires d’exception, qui décideront si une exception est considérée comme une erreur (la plupart du temps, c’est le cas), c’est-à-dire log & notifier quelqu'un, ou peut signifier autre chose - comme cette exception, AbortException, ce qui signifie casser le thread d'exécution en cours et ne pas enregistrer d'erreur car il est souhaitable de ne pas le faire. Bien entendu, pour résoudre tous les threads personnalisés, il faut gérer la méthode run () avec un try {...} catch (all).

public void run () {

try {
     ... do something ...
} catch (Throwable throwable) {
     ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
}

}

Cela n'est pas nécessaire si vous utilisez WorkerService pour planifier des tâches (Runnable, Callable, Worker), qui gèrent 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 une bonne approche. Je verrai après que je publierai le projet si ce que je pense c'est bon pour moi, c'est bon pour les autres aussi ... :)

0
adrian.tarau