web-dev-qa-db-fra.com

Est-ce une bonne pratique d'attraper une exception vérifiée et de lever une RuntimeException?

J'ai lu du code d'un collègue et j'ai constaté qu'il attrape souvent diverses exceptions, puis lève toujours une "RuntimeException" à la place. J'ai toujours pensé que c'était une très mauvaise pratique. Ai-je tort?

73
RoflcoptrException

Je ne connais pas suffisamment le contexte pour savoir si votre collègue fait quelque chose de mal ou non, alors je vais discuter de cela dans un sens général.

Je ne pense pas que ce soit toujours une mauvaise pratique de transformer les exceptions vérifiées en une sorte d'exception d'exécution. Les exceptions vérifiées sont souvent mal utilisées et abusées par les développeurs.

Il est très facile d'utiliser les exceptions vérifiées lorsqu'elles ne sont pas destinées à être utilisées (conditions irrécupérables, ou même contrôler le flux). Surtout si une exception vérifiée est utilisée pour des conditions à partir desquelles l'appelant ne peut pas récupérer, je pense qu'il est justifié de transformer cette exception en une exception d'exécution avec un message/état utile. Malheureusement, dans de nombreux cas, lorsque l'on est confronté à une condition irrécupérable, ils ont tendance à avoir un bloc de capture vide, ce qui est l'une des pires choses que vous puissiez faire. Le débogage d'un tel problème est l'un des plus gros problèmes qu'un développeur peut rencontrer.

Donc, si vous pensez que vous avez affaire à une condition récupérable, elle doit être gérée en conséquence et l'exception ne doit pas être transformée en exception d'exécution. Si une exception vérifiée est utilisée pour des conditions irrécupérables, la transformer en exception d'exécution est justifiée.

56
c_maker

Il peut être BON . Veuillez lire cet article sur onjava.com :

La plupart du temps, le code client ne peut rien faire contre les SQLExceptions. N'hésitez pas à les convertir en exceptions non contrôlées. Considérez le morceau de code suivant:

public void dataAccessCode(){
  try{
      ..some code that throws SQLException
  }catch(SQLException ex){
      ex.printStacktrace();
  }
} 

Ce bloc catch supprime simplement l'exception et ne fait rien. La justification est que mon client ne peut rien faire contre une exception SQLE. Que diriez-vous de le traiter de la manière suivante?

public void dataAccessCode(){
   try{
       ..some code that throws SQLException
   }catch(SQLException ex){
       throw new RuntimeException(ex);
   }
} 

Cela convertit SQLException en RuntimeException. Si SQLException se produit, la clause catch lève une nouvelle RuntimeException. Le thread d'exécution est suspendu et l'exception est signalée. Cependant, je ne corrompe pas ma couche d'objet métier avec une gestion des exceptions inutile, d'autant plus qu'elle ne peut rien faire pour une exception SQLException. Si ma capture a besoin de la cause de l'exception racine, je peux utiliser la méthode getCause () disponible dans toutes les classes d'exception à partir de JDK1.4.

Lancer des exceptions vérifiées et ne pas pouvoir s'en remettre n'aide pas.

Certaines personnes pensent même que les exceptions vérifiées ne devraient pas être utilisées du tout. Voir http://www.ibm.com/developerworks/Java/library/j-jtp05254/index.html

Récemment, plusieurs experts réputés, dont Bruce Eckel et Rod Johnson, ont déclaré publiquement que même s'ils étaient initialement entièrement d'accord avec la position orthodoxe sur les exceptions vérifiées, ils ont conclu que l'utilisation exclusive des exceptions vérifiées n'est pas une aussi bonne idée qu'elle apparu au début, et que les exceptions vérifiées sont devenues une source importante de problèmes pour de nombreux grands projets. Eckel adopte une position plus extrême, suggérant que toutes les exceptions devraient être décochées; L'opinion de Johnson est plus conservatrice, mais suggère toujours que la préférence orthodoxe pour les exceptions vérifiées est excessive. (Il convient de noter que les architectes de C #, qui avaient presque certainement beaucoup d'expérience dans l'utilisation de la technologie Java, ont choisi d'omettre les exceptions vérifiées de la conception du langage, faisant de toutes les exceptions des exceptions non contrôlées. , laissez de la place pour une implémentation des exceptions vérifiées ultérieurement.)

Également à partir du même lien:

La décision d'utiliser des exceptions non contrôlées est compliquée, et il est clair qu'il n'y a pas de réponse évidente. Le conseil de Sun est de les utiliser pour rien, l'approche C # (avec laquelle Eckel et d'autres sont d'accord) est de les utiliser pour tout. D'autres disent: "il y a un juste milieu".

42
mrmuggles

Non, vous ne vous trompez pas. Sa pratique est extrêmement erronée. Vous devez lever une exception qui capture le problème qui l'a provoqué. RunTimeException est large et dépasse la portée. Il doit s'agir d'une exception NullPointerException, ArgumentException, etc. Tout ce qui décrit avec précision ce qui s'est mal passé. Cela offre la possibilité de différencier les problèmes que vous devez gérer et de laisser le programme survivre par rapport aux erreurs qui devraient être un scénario "Ne pas passer". Ce qu'il fait n'est que légèrement mieux que "On Error Resume Next" sauf s'il manque quelque chose dans les informations fournies dans la question.

13
Rig

Ça dépend.

Cette pratique peut être encore plus sage. Il existe de nombreuses situations (par exemple dans le développement Web), où si une exception se produit, vous ne pouvez rien faire (car vous ne pouvez pas par exemple réparer une base de données incohérente à partir de votre code :-), seul le développeur peut le faire). Dans ces situations, il est judicieux d'envelopper l'exception levée dans une exception d'exécution et de la renvoyer. Ensuite, vous pouvez intercepter toutes ces exceptions dans une couche de gestion des exceptions, enregistrer l'erreur et afficher à l'utilisateur un code d'erreur localisé + un message Nice.

D'un autre côté, si l'exception n'est pas à l'exécution (est vérifiée), le développeur de l'API indique que cette exception est résolvable et doit être réparée. Si c'est possible, alors vous devriez certainement le faire.

L'autre solution pourrait être de renvoyer cette exception vérifiée dans la couche appelante, mais si vous n'avez pas pu la résoudre, là où l'exception s'est produite, vous ne pourrez probablement pas la résoudre ici également ...

8
malejpavouk

J'aimerais avoir des commentaires à ce sujet, mais je trouve qu'il y a des moments où ce n'est pas nécessairement une mauvaise pratique. (Ou terriblement mauvais). Mais peut-être que je me trompe.

Souvent, une API que vous utilisez lèvera une exception que vous ne pouvez pas imaginer réellement jetée dans votre cas d'utilisation spécifique. Dans ce cas, il semble parfaitement correct de lancer une RuntimeException avec l'exception interceptée comme cause. Si cette exception est levée, elle sera probablement la cause d'une erreur de programmation et n'est pas dans les limites pour une spécification correcte.

En supposant que la RuntimeException n'est pas interceptée et ignorée par la suite, elle n'est pas proche d'un OnErrorResumeNext.

Le OnErrorResumeNext se produirait lorsque quelqu'un intercepte une exception et l'ignore simplement ou l'imprime simplement. C'est une très mauvaise pratique dans presque tous les cas.

5
user606723

TL; DR

Local

  • Les exceptions d'exécution doivent être levées lorsque l'erreur est irrécupérable: lorsque l'erreur est dans le code et ne dépend pas de l'état externe (ainsi, la récupération serait de corriger le code).
  • Les exceptions vérifiées doivent être levées lorsque le code est correct, mais l'état externe n'est pas comme prévu: pas de connectivité réseau, fichier introuvable ou corrompu, etc.

Conclusion

Nous pouvons renvoyer une exception vérifiée en tant qu'exception d'exécution si le code de propagation ou d'interface suppose que l'implémentation sous-jacente dépend de l'état externe, alors que ce n'est clairement pas le cas.


Cette section traite du sujet du moment où l'une des exceptions doit être levée. Vous pouvez passer à la barre horizontale suivante si vous souhaitez simplement lire une explication plus détaillée de la conclusion.

Quand est-il approprié de lever une exception d'exécution? Vous lancez une exception d'exécution lorsqu'il est clair que le code est incorrect et que la récupération est appropriée en modifiant le code.

Par exemple, il convient de lever une exception d'exécution pour les éléments suivants:

float nan = 1/0;

Cela générera une division par zéro exception d'exécution. Cela est approprié car le code est défectueux.

Ou par exemple, voici une partie du constructeur de HashMap:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                loadFactor);
    // more irrelevant code...
}

Afin de fixer la capacité initiale ou le facteur de charge, il convient que vous modifiez le code pour vous assurer que les valeurs correctes sont transmises. Cela ne dépend pas de la présence d'un serveur éloigné, de l'état actuel du disque, un fichier ou un autre programme. Ce constructeur appelé avec des arguments invalides dépend de l'exactitude du code appelant, que ce soit un mauvais calcul qui a conduit à des paramètres invalides ou un flux inapproprié qui a manqué une erreur.

Quand est-il approprié de lever une exception vérifiée? Vous lancez une exception vérifiée lorsque le problème est récupérable sans changer le code. Ou, pour le dire autrement, vous lancez une exception vérifiée lorsque l'erreur est liée à l'état alors que le code est correct.

Maintenant, le mot "récupérer" peut être difficile ici. Cela pourrait signifier que vous trouverez un autre moyen d'atteindre l'objectif: par exemple, si le serveur ne répond pas, vous devez essayer le serveur suivant. Si ce type de récupération est possible pour votre cas, c'est parfait, mais ce n'est pas la seule chose que la récupération signifie - la récupération pourrait simplement afficher une boîte de dialogue d'erreur à l'utilisateur qui explique ce qui s'est passé, ou si c'est une application serveur, alors cela pourrait être envoyer un e-mail à l'administrateur, ou même simplement consigner l'erreur de manière appropriée et concise.

Prenons l'exemple qui a été mentionné dans la réponse de mrmuggles:

public void dataAccessCode(){
   try{
       ..some code that throws SQLException
   }catch(SQLException ex){
       throw new RuntimeException(ex);
   }
}

Ce n'est pas la bonne façon de gérer l'exception vérifiée. La simple incapacité à gérer l'exception dans la portée de cette méthode ne signifie pas que l'application doit être bloquée. Au lieu de cela, il convient de le propager à une portée plus élevée comme ceci:

public Data dataAccessCode() throws SQLException {
    // some code that communicates with the database
}

Ce qui permet la possibilité de récupération par l'appelant:

public void loadDataAndShowUi() {
    try {
        Data data = dataAccessCode();
        showUiForData(data);
    } catch(SQLException e) {
        // Recover by showing an error alert dialog
        showCantLoadDataErrorDialog();
    }
}

Les exceptions vérifiées sont un outil d'analyse statique, elles indiquent clairement au programmeur ce qui pourrait mal se passer dans un certain appel sans qu'il soit nécessaire d'apprendre la mise en œuvre ou de passer par un processus d'essai et d'erreur. Cela permet de garantir facilement qu'aucune partie du flux d'erreur ne sera ignorée. Renvoyer une exception vérifiée en tant qu'exception d'exécution fonctionne par rapport à cette fonctionnalité d'analyse statique économe en travail.

Il convient également de mentionner que la couche appelante a un meilleur contexte du schéma plus grand des choses, comme cela a été démontré ci-dessus. Il pourrait y avoir de nombreuses raisons pour lesquelles dataAccessCode serait appelé, la raison spécifique de l'appel n'est visible que par l'appelant - il est donc en mesure de prendre une meilleure décision lors de la récupération correcte en cas d'échec.

Maintenant que cette distinction est claire, nous pouvons déduire quand il est possible de renvoyer une exception vérifiée en tant qu'exception d'exécution.


Compte tenu de ce qui précède, quand est-il approprié de renvoyer une exception vérifiée en tant que RuntimeException? Lorsque le code que vous utilisez suppose une dépendance à l'égard de l'état externe, mais vous pouvez clairement affirmer qu'il ne dépend pas de l'état externe.

Considérer ce qui suit:

StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
    doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
    throw new IllegalStateException(e);
}

Dans cet exemple, le code se propage IOException car l'API de Reader est conçue pour accéder à l'état externe, mais nous savons que l'implémentation StringReader n'accède pas à l'état externe. À cette portée, où nous pouvons certainement affirmer que les parties impliquées dans l'appel n'accèdent pas à IO ou à tout autre état externe, nous pouvons sans risque renvoyer l'exception en tant qu'exception d'exécution sans étonner les collègues qui ne sont pas au courant de notre implémentation (et supposent peut-être que le code d'accès aux E/S lancera un IOException).


La raison pour laquelle strictement les exceptions externes dépendantes de l'état sont cochées est qu'elles ne sont pas déterministes (contrairement aux exceptions dépendantes de la logique, qui seront prévisibles à chaque fois pour une version du code). Par exemple, si vous essayez de diviser par 0, vous produirez toujours une exception. Si vous ne divisez pas par 0, vous ne produirez jamais d'exception et vous n'aurez pas à gérer ce cas d'exception, car cela ne se produira jamais. Dans le cas de l'accès à un fichier, cependant, réussir une fois ne signifie pas que vous réussirez la prochaine fois - l'utilisateur peut avoir changé les autorisations, un autre processus peut l'avoir supprimé ou modifié. Vous devez donc toujours gérer ce cas exceptionnel, ou vous avez probablement un bug.

5
Ben Barkay

C'est une pratique courante dans de nombreux cadres. Par exemple. Hibernate fait exactement cela. L'idée est que les API ne doivent pas être intrusives pour le côté client et Exception sont intrusives car vous devez explicitement écrire du code pour les gérer à cet endroit où vous appelez l'API. Mais cet endroit pourrait ne pas être le bon endroit pour gérer l'exception en premier lieu.
.

2
user10326

Pour les applications autonomes. Lorsque vous savez que votre application ne peut pas gérer l'exception que vous pourriez, au lieu de lancer l'exception RuntimeException vérifiée, lancez Error, laissez l'application se bloquer, attendez les rapports de bogues et corrigez votre application. (Voir la réponse de mrmuggles pour une discussion plus approfondie des avantages et des inconvénients des vérifiés par rapport aux non vérifiés.)

2

Dans une question booléenne, il est difficile de répondre différemment après deux réponses controversées, mais je voudrais vous donner une perspective qui, même mentionnée à quelques endroits, n'a pas été suffisamment soulignée pour l'importance qu'elle a.

Avec les années, j'ai constaté que toujours quelqu'un est confus au sujet d'un problème trivial, il ne comprend pas certains des principes fondamentaux.

Superposition. Une application logicielle (au moins censée être) un tas de couches superposées. Une attente importante pour une bonne stratification est que les couches inférieures fournissent des fonctionnalités pour potentiellement plusieurs composants de la couche supérieure.

Supposons que votre application dispose des couches suivantes de bas en haut NET, TCP, HTTP, REST, MODÈLE DE DONNÉES, BUSINESS.

Si votre couche métier souhaite exécuter un appel de repos ... attendez une seconde. Pourquoi ai-je dit ça? Pourquoi n'ai-je pas dit requête HTTP ou TCP ou envoi de packages réseau? Parce que ceux-ci ne sont pas pertinents pour ma couche métier. Je ne vais pas les gérer, je ne vais pas regarder dans les détails Je suis parfaitement d'accord si elles sont au fond des exceptions que je reçois en tant que cause et je ne veux pas savoir qu'elles existent même.

De plus, c'est mauvais si je connais les détails, car si demain je voudrais changer les protocoles de transport soulignant les détails spécifiques au protocole TCP signifie que mon REST abstraction n'a pas fait un bon travail pour s'abstraire de l'implémentation spécifique.

Lors de la transition d'une exception d'un calque à un autre, il est important de revoir chaque aspect de celle-ci et le sens que cela aura pour l'abstraction fournie par le calque actuel. Il peut s'agir de remplacer l'exception par d'autres, il peut combiner plusieurs exceptions. Il peut également les faire passer de coché à non coché ou vice versa.

Bien sûr, est-ce que les endroits que vous avez mentionnés ont un sens, c'est une autre histoire, mais en général - oui, cela pourrait être une bonne chose à faire.

1
gsf

Toute la chose "exception vérifiée" est une mauvaise idée.

La programmation structurée ne permet de transmettre des informations entre les fonctions (ou, dans le langage Java, méthodes) que lorsqu'elles sont "proches". Plus précisément, les informations ne peuvent se déplacer entre les fonctions que de deux manières:

  1. De l'appelant à l'appelé, en passant l'argument.

  2. De l'appelé à son appelant, en tant que valeurs de retour.

C'est fondamentalement une bonne chose. C'est ce qui vous permet de raisonner localement sur votre code: si vous avez besoin de comprendre ou de modifier une partie de votre programme, il vous suffit de regarder cette partie et d'autres "proches".

Cependant, dans certaines circonstances, il est nécessaire d'envoyer des informations à une fonction "distante", sans que quiconque au milieu "sache". C'est précisément lorsque des exceptions doivent être utilisées. Une exception est un message secret envoyé par un relanceur (quelle que soit la partie de votre code pouvant contenir une instruction throw) vers un gestionnaire = (quelle que soit la partie de votre code pouvant contenir un bloc catch compatible avec l'exception qui était thrown).

Les exceptions vérifiées détruisent le secret du mécanisme et, avec lui, la raison même de son existence. Si une fonction peut se permettre de laisser son appelant "connaître" une information, il suffit d'envoyer cette information directement en tant que partie de la valeur de retour.

1
pyon

Cela peut dépendre au cas par cas. Dans certains scénarios, il est judicieux de faire ce que fait votre ami, par exemple lorsque vous exposez une API pour certains clients et que vous souhaitez que le client soit le moins au courant des détails de l'implémentation, où vous savez que certaines exceptions d'implémentation peuvent être spécifiques à détails de la mise en œuvre et non exposé au client.

En gardant les exceptions vérifiées à l'écart, vous pouvez exposer des API qui permettraient au client d'écrire du code plus propre car le client lui-même pourrait pré-valider les conditions exceptionnelles.

Par exemple, Integer.parseInt (String) prend une chaîne et retourne l'équivalent entier de celle-ci et lève NumberFormatException au cas où la chaîne n'est pas numérique. Imaginons maintenant qu'une soumission de formulaire avec un champ age soit convertie par cette méthode mais le client aurait déjà assuré la validation de sa part, donc inutile de forcer la vérification d'exception.

0
user94369

Il y a vraiment quelques questions ici

  1. Devriez-vous transformer les exceptions vérifiées en exceptions non contrôlées?

La règle générale est que les exceptions que l'appelant doit intercepter et récupérer doivent être vérifiées. D'autres exceptions (celles où le seul résultat raisonnable est d'interrompre toute l'opération ou où vous les considérez suffisamment improbables pour que le souci de les traiter spécifiquement n'en vaille pas la peine) ne devraient pas être vérifiées.

Parfois, votre jugement quant à savoir si une exception mérite d'être interceptée et récupérée est différent de celui de l'API avec laquelle vous travaillez. Parfois, le contexte compte, une exception qui mérite d'être gérée dans une situation peut ne pas valoir la peine d'être gérée dans une autre. Parfois, votre main est forcée par les interfaces existantes. Donc, oui, il existe des raisons légitimes de transformer une exception vérifiée en exception non vérifiée (ou en un autre type d'exception vérifiée)

  1. Si vous souhaitez transformer une exception non vérifiée en exception cochée, comment devez-vous procéder?.

Tout d'abord et surtout, assurez-vous d'utiliser la fonction de chaînage d'exceptions. De cette façon, les informations de l'exception d'origine ne sont pas perdues et peuvent être utilisées pour le débogage.

Deuxièmement, vous devez décider du type d'exception à utiliser. L'utilisation d'une simple exception d'exécution rend plus difficile pour l'appelant de déterminer ce qui s'est mal passé, mais si l'appelant essaie de déterminer ce qui s'est mal passé, cela peut être une indication que vous n'auriez pas dû modifier l'exception pour la désactiver.

0
Peter Green