Ok je sais que jetter est pas une bonne idée:
try {
// Some code
} catch(Throwable e) { // Not cool!
// handle the exception
}
Mais récemment, je lisais un code à source ouverte et j’ai vu cet élément de code intéressant (du moins pour moi):
try {
// Some Code
} catch (Throwable ex){
response = handleException(ex, resource);
}
private handleException(Throwable t, String resource) {
if (t instanceof SQLEXception) {
// Some code
} else if (t instanceof IllegalArgumentException) {
//some code
} //so on and so forth
}
Cela ne semble pas être si mauvais? Quel est le problème avec cette approche?
Il y a diverses raisons pour lesquelles vous ne devriez pas attraper un Throwable. Tout d’abord, Throwable
inclut Error
s - et normalement, une application ne peut pas faire grand chose si l’une d’elles apparaît. Throwable réduit également vos chances de découvrir ce qui est arrivé. Tout ce que vous obtenez est "quelque chose de grave est arrivé" - ce qui pourrait être une catastrophe ou juste une nuisance.
L'autre approche est meilleure, mais bien sûr, je ne voudrais toujours pas attraper Throwable, mais essayer d'attraper des exceptions plus spécifiques, si possible. Sinon, vous attrapez tout et essayez ensuite de déterminer le type de problème qui s'est produit. Votre exemple pourrait être écrit comme ...
try {
...
} catch (SQLEXception ex){
response = ... ;
} catch (IllegalArgumentException ex){
response = ...;
}
... ce qui réduirait le nombre de blocs if ( ... instanceof ... )
(nécessaires uniquement parce que l'auteur a d'abord décidé de tout prendre dans le même seau). En réalité, il s’agit de throws Throwable
, alors vous n’avez évidemment pas beaucoup de choix.
Vous avez raison de dire qu'attraper Throwable
n'est pas une bonne idée. Cependant, le code que vous présentez dans votre question n'attrape pas Throwable
de manière perverse, mais parlons-en plus tard. Pour le moment, le code que vous présentez dans votre question présente plusieurs avantages:
1. Lisibilité
Si vous examinez attentivement le code, vous remarquerez que même si le bloc catch intercepte une variable Throwable
, la méthode handleException
vérifie le type d'exception levée et effectue éventuellement différentes actions en fonction de ce type.
Le code présenté dans votre question est synonyme de:
try {
doSomething();
} catch (SQLEXception ex){
response = handleException(resource);
} catch(IllegalArgumentException ex) {
response = handleException(resource);
} catch(Throwable ex) {
response = handleException(resource);
}
Même si vous devez capturer plus de 10 exceptions seulement, ce code peut facilement prendre beaucoup de lignes de code et la construction multi-catch ne va pas rendre le code plus propre. Le code que vous présentez dans votre question consiste simplement à déléguer la catch
à une autre méthode afin de rendre la méthode réelle qui effectue le travail plus lisible.
2. Réutilisation
Le code de la méthode handleRequest peut facilement être modifié et placé dans une classe d'utilitaire, puis utilisé dans l'ensemble de votre application pour gérer à la fois Exception
s et Error
s. Vous pouvez même extraire la méthode en deux méthodes private
; L'une qui gère Exception
et l'autre qui gère Error
et qui ont la méthode handleException
qui prend un Throwable
déléguer davantage les appels à ces méthodes.
3. Maintien
Si vous décidez de modifier la manière dont vous enregistrez une SQLException
s dans votre application, vous devez effectuer cette modification à un seul endroit plutôt que de consulter chaque méthode de chaque classe qui lance une SQLException
.
Donc, attraper Throwable
est-il une mauvaise idée?
Le code que vous présentez dans votre question n’est pas vraiment identique à attraper Throwable
seul. Le code suivant est un gros no-no:
try {
doSomething();
} catch(Throwable e) {
//log, rethrow or take some action
}
Vous devriez attraper Throwable
ou Exception
aussi loin que possible dans la chaîne catch
.
Dernier point mais non le moindre, rappelez-vous que le code que vous présentez dans votre question est le code du framework et que le framework peut encore récupérer certaines erreurs. Voir Quand attraper Java.lang.Error pour une meilleure explication.
Attraper Throwable
s de la paresse est une mauvaise idée.
C'était particulièrement tentant avant que try-multi-catch
ne soit introduit.
try {
...
} catch (SomeException e) {
//do something
} catch (OtherException e) {
//do the same thing
} ...
Répéter les blocs de capture est fastidieux et prolixe, aussi certaines personnes ont-elles décidé d'attraper simplement Exception
ou Throwable
et de s'en passer. C'est ce qu'il faut éviter car:
Throwable
dans le bloc collé. (Et nous avons tous vu le code qui fait ça ... :)) Mais attraper Throwable
s quand il est absolument nécessaire va bien.
Quand est-ce nécessaire? Très rarement. Dans le code de type framework, il existe différents scénarios (le chargement dynamique d'une classe externe est le plus évident). Dans une application autonome, un exemple typique consiste à afficher/consigner un type de message d'erreur avant de quitter. (Gardant à l'esprit que la tentative peut échouer, vous ne voulez donc rien y mettre de critique.)
En règle générale, si vous ne pouvez rien faire à propos d'une exception/erreur, vous ne devriez pas l'attraper du tout.
Il existe exactement deux utilisations valables pour utiliser un réseau énorme:
Si vous voulez tout gérer de manière uniforme, comme une capture de niveau supérieur pour la journalisation/la création de rapports, éventuellement suivie d'une sortie immédiate.
Pour réduire la duplication, en exportant toute la manipulation dans sa propre méthode.
Catch l'ancêtre commun le plus dérivé est d'éviter le travail extra et augmenter la clarté.
DRY est un principe de conception important.
Dans les deux cas, à moins que vous n'espériez que cette exception et que vous l'ayez traitée complètement, revenez en arrière.
Vous avez posté un lien vers Jongo, qui illustre une utilisation possible de cette technique: réutiliser le code de traitement des erreurs.
Supposons que vous disposiez d'un grand bloc de code de traitement des erreurs qui se répète naturellement à divers endroits dans votre code. Par exemple, Jongo génère des réponses standard pour certaines classes d'erreur standard. Il peut être judicieux d'extraire ce code de gestion des erreurs dans une méthode afin de pouvoir le réutiliser à partir de tous les emplacements nécessaires.
Cependant, cela ne veut pas dire qu'il n'y a rien de mal avec le code de Jongo.
Attraper Throwable
(plutôt que d’utiliser plusieurs correspondances) est toujours suspect, car vous attraperez probablement Error
s que vous n'êtes pas vraiment en mesure de gérer (êtes-vous bien sûr avez-vous voulu attraper ThreadDeath
?) Dans cette situation, si vous devez absolument attraper Throwable
, il vaudrait mieux "attraper et relâcher" (c'est-à-dire, tout ce que vous ne vouliez pas attraper). Jongo ne fait pas ça.
Juste pour fournir un équilibre - il y a un endroit où je vais toujours catch (Throwable)
:
public static void main(String args[]) {
try {
new Test().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
Au moins, quelque chose montre quelque part que quelque chose s'est mal passé.
Tout d’abord, attraper Throwable rend votre application plutôt transparente. Vous devez être aussi explicite que possible sur la capture des exceptions pour permettre une bonne traçabilité dans des cas exceptionnels.
Jetons un coup d'oeil à la méthode handleException (...) et voyons quelques-uns des problèmes qui se produisent avec cette approche:
De mon point de vue, les blocs d'arrêt sont exactement conçus pour les fonctionnalités que nous essayons de couvrir dans handleExceptions (...), alors utilisez-les.
Java 7 résout un peu l'ennui qui consiste à intercepter plusieurs exceptions similaires avec une gestion similaire. Vous ne devriez certainement pas faire ce que la personne a fait ici. Attrapez simplement les exceptions appropriées selon vos besoins, cela peut paraître moche mais alors c'est à quoi throws
est destiné, passez-le à la méthode qui devrait l'attraper et vous ne devriez pas gaspiller trop d'espace de code.
Vous pouvez toujours intercepter différents types d'exceptions et effectuer certaines opérations en fonction du type d'exception que vous avez obtenu.
Voici un exemple
try{
//do something that could throw an exception
}catch (ConnectException e) {
//do something related to connection
} catch (InvalidAttributeValueException e) {
// do anything related to invalid attribute exception
} catch (NullPointerException e) {
// do something if a null if obtained
}
catch (Exception e) {
// any other exception that is not handled can be catch here, handle it here
}
finally{
//perform the final operatin like closing the connections etc.
}