java: méthode standard pour envelopper les exceptions vérifiées

J’ai une question assez détaillée à propos de la bonne façon d’emballer une exception vérifiée et de la manière dont Guava le fait. (Toutes mes excuses pour la longueur mais je veux que mon processus de reflection soit arrêté)


L’interface Runnable standard ressemble à ceci:

public interface Runnable { public void run(); } 

run() ne peut pas lancer une exception vérifiée.

Donc, si je veux avoir un Runnable qui est utilisé pour envelopper les tâches qui lancent des exceptions vérifiées, et j’ai l’intention d’avoir la chose qui appelle Runnable.run() gérer ces exceptions, plutôt que dans Runnable.run() lui-même, je dois encapsuler l’exception dans une exception non contrôlée.

Donc pendant un moment j’utilisais:

 Runnable r = new Runnable { @Override public void run() { try { doNastyStuff(); } catch (NastyException e) { throw new RuntimeException(e); } } }; 

et puis je peux gérer RuntimeException dans un niveau supérieur. Sauf que je me suis dit que ce que je veux vraiment, c’est gérer une exception encapsulée séparément, car je sais que sa sémantique consiste à envelopper une exception vérifiée. J’ai donc écrit cette classe d’aide:

 /** * Wrapped exception: the purpose of this is just to wrap another exception, * and indicate that it is a wrapped exception */ public class WrappedException extends RuntimeException { /** * @param t any throwable */ public WrappedException(Throwable t) { super(t); } } 

et alors je peux faire ceci:

 /* place that produces the exception */ ... catch (NastyException e) { throw new WrappedException(e); } ... /* upper level code that calls Runnable.run() */ try { ... SomeOtherNastyCode(); r.run(); ... } catch (SomeOtherNastyException e) { logError(e); } catch (WrappedException e) { logError(e.getCause()); } 

et cela semble bien fonctionner.

Mais maintenant, je pense, eh bien, si je veux utiliser ceci dans une bibliothèque aussi bien qu’une application qui utilise la bibliothèque, maintenant ils dépendent tous les deux de WrappedException, donc cela devrait vraiment être dans une bibliothèque de base que je peux inclure partout.

Ce qui me fait penser, peut-être que Guava a une classe standard WrappedException quelque part, puisque j’inclue maintenant Guava comme dépendance par défaut. Donc je peux juste faire

 throw new WrappedException(e); 

ou

 throw Exceptions.wrap(e); 

ou

 Exceptions.rethrow(e); 

Je me suis contenté de regarder autour de Guava et de trouver Throwables qui a Throwables.propagate() qui semble similaire, mais il enveloppe simplement les exceptions vérifiées dans une RuntimeException , plutôt qu’une sous-classe spéciale de RuntimeException.

Quelle approche est la meilleure? Ne devrais-je pas utiliser une exception WrappedException spéciale par rapport à une exception RuntimeException? Mon code de niveau supérieur veut connaître la plus grande exception qui ajoute une valeur informative.

Si j’ai une exception RuntimeException qui englobe une exception NastyException qui encapsule une exception NullPointerException, le RuntimeException ne ajoute pas de valeur informative, et je m’en fiche, donc l’erreur que je voudrais enregistrer serait l’exception NastyException.

Si j’ai une exception IllegalArgumentException qui encapsule une exception NastyException, l’exception IllegalArgumentException ajoute généralement une valeur informative.

Donc, dans mon code principal qui effectue la journalisation des erreurs, je devrais faire quelque chose comme ceci:

 catch (RuntimeException re) { logError(getTheOutermostUsefulException(re)); } /** * heuristics to tease out whether an exception * is wrapped just for the heck of it, or whether * it has informational value */ Throwable getTheOutermostUsefulException(RuntimeException re) { // subclasses of RuntimeException should be used as is if (re.getClass() != RuntimeException) return re; // if a runtime exception has a message, it's probably useful else if (re.getMessage() != null) return re; // if a runtime exception has no cause, it's certainly // going to be more useful than null else if (re.getCause() == null) return re; else return re.getCause(); } 

La philosophie me semble bonne, mais la mise en œuvre est mauvaise. Existe-t-il un meilleur moyen de gérer les exceptions encapsulées?


questions connexes:

  • Comment utiliser Throwables.propagateIfInstanceOf () à partir de Google Guava?
  • Envelopper une exception cochée dans une exception non contrôlée en Java?

Le spring est la seule bibliothèque que je connaisse avec quelque chose de relativement similaire. Ils ont des exceptions nestedes: NestedRuntimeException et NestedCheckedException . Ces exceptions ont des méthodes utiles telles que getMostSpecificCause() ou contains(Class exType) . Leur méthode getMessage() retourne le message de la cause (il est ajouté si l’exception d’habillage a déjà un message).

Il est utilisé dans la hiérarchie des exceptions d’access aux données de Spring. L’idée est que chaque fournisseur de firebase database expose différentes exceptions dans leurs pilotes JDBC. Spring les intercepte et les traduit en plus génériques DataAccessExceptions . Un autre avantage est que les exceptions vérifiées sont automatiquement traduites en exceptions d’exécution.

Ceci étant dit, le code n’est pas très compliqué et je suis sûr que vous pourriez faire quelque chose de similaire dans votre base de code. Pas besoin d’append une dépendance Spring juste pour ça.

Si j’étais vous, je n’essaierais pas de “décompresser” les exceptions trop tôt, à moins que vous ne puissiez vraiment les gérer immédiatement. Je les laisserais bouillir (enveloppés ou non) avec un gestionnaire d’exception global qui examinerait leur chaîne de causalité, trouverait la première exception “significative” et extrairait un message d’erreur intelligent. Si je ne pouvais pas le faire, j’imprimais simplement “erreur technique” et ajoutais toute la trace de la stack dans le détail du message d’erreur ou dans une sorte de journal, à des fins de rapport de bogue. Vous devriez ensuite corriger le bogue et / ou lancer une exception plus significative.

Throwables.getCausalChain () de Guava pourrait également être intéressant pour simplifier la gestion des exceptions:

 Iterables.filter(Throwables.getCausalChain(e), IOException.class)); 

Modifier:

J’ai réfléchi un peu plus à votre problème, et je pense que vous ne devriez pas vous inquiéter de placer vos exceptions avec un type spécifique “WrapperException”. Vous devriez les envelopper en utilisant ce qui est le plus logique: soit une simple RuntimeException ( RuntimeException () de Guava pourrait y être utile), une RuntimeException avec un message d’erreur supplémentaire, ou un type d’exception plus significatif si nécessaire.

Le mécanisme de chaîne causale de Java vous permettra d’atteindre la cause fondamentale de toute façon. Vous ne devriez pas avoir à vous soucier de l’encapsulation des exceptions partout dans votre code. Ecrivez un gestionnaire d’exceptions global pour le gérer et utilisez des bulles d’exception standard partout ailleurs.

En général, il est préférable d’éviter d’encapsuler les exceptions vérifiées avec une exception non vérifiée (voir ici et ici ). Quelques façons de le contourner:

  1. Utiliser Callable au lieu de Runnable
  2. Utilisez le cadre des Executors comme suggéré par Bohemian
  3. Sous-classe Runnable (ou l’interface / classe que vous utilisez) et ajoutez un moyen de vérifier les exceptions lors de l’exécution après le fait, peut-être une méthode public Exception getRunException() ou similaire.

Si vous devez encapsuler une exception cochée, je pense que la meilleure façon de procéder consiste à définir une sous-classe de RuntimeException . Mais j’essaierais de l’éviter si possible.