web-dev-qa-db-fra.com

Attraper une exception imbriquée dans une autre exception

Je veux intercepter une exception, qui est imbriquée dans une autre exception. Je le fais actuellement de cette façon:

} catch (RemoteAccessException e) {
    if (e != null && e.getCause() != null && e.getCause().getCause() != null) {
        MyException etrp = (MyException) e.getCause().getCause();
        ...
    } else {
        throw new IllegalStateException("Error at calling service 'service'");
    }
}

Existe-t-il un moyen de rendre cela plus efficace et plus élégant?

26
user321068

Il n'y a pas de moyen plus élégant de "capturer" sélectivement les exceptions imbriquées. Je suppose que si vous faisiez ce genre d'exception imbriquée attrapant beaucoup, vous pourriez éventuellement refactoriser le code en une méthode utilitaire commune. Mais il ne sera toujours ni élégant ni efficace.

La solution élégante est de supprimer l'imbrication d'exception. Soit ne chaînez pas les exceptions en premier lieu, soit (sélectivement) déballez et relancez les exceptions imbriquées plus haut dans la pile.

Les exceptions ont tendance à être imbriquées pour 3 raisons:

  1. Vous avez décidé que les détails de l'exception d'origine ne sont probablement pas utiles pour la récupération d'erreur de l'application ... mais vous souhaitez les conserver à des fins de diagnostic.

  2. Vous implémentez des méthodes API qui n'autorisent pas d'exception vérifiée spécifique mais votre code inévitablement lève cette exception. Une solution de contournement courante consiste à "passer en contrebande" l'exception vérifiée à l'intérieur d'une exception non vérifiée.

  3. Vous êtes paresseux et transformez un divers ensemble d'exceptions non liées en une seule exception pour éviter d'avoir beaucoup d'exceptions vérifiées dans la signature de votre méthode1.

Dans le premier cas, si vous devez maintenant discriminer les exceptions encapsulées, vos hypothèses initiales étaient incorrectes. La meilleure solution consiste à modifier les signatures de méthode afin de vous débarrasser de l'imbrication.

Dans le second cas, vous devez probablement déballer les exceptions dès que le contrôle a réussi la méthode API problématique.

Dans le troisième cas, vous devriez repenser votre stratégie de gestion des exceptions; c'est-à-dire le faire correctement2.


1 - En effet, l'une des raisons semi-légitimes de faire cela a disparu en raison de l'introduction de la syntaxe de capture multi-exceptions dans Java 7.

2 - Ne changez pas vos méthodes API en throws Exception. Cela ne fait qu'empirer les choses. Vous devez maintenant "gérer" ou propager Exception chaque fois que vous appelez les méthodes. C'est un cancer ...

26
Stephen C

La méthode ExceptionUtils # getRootCause () peut être très pratique dans de telles situations.

19
user321068

Vous devriez ajouter quelques vérifications pour voir si e.getCause().getCause() est vraiment un MyException. Sinon, ce code lancera un ClassCastException. J'écrirais probablement ceci comme:

} catch(RemoteAccessException e) {
    if(e.getCause() != null && e.getCause().getCause() instanceof MyException) {
        MyException ex = (MyException)e.getCause().getCause();
        // Do further useful stuff
    } else {
        throw new IllegalStateException("...");
    }
}
18
Marc

Je viens de résoudre un problème comme celui-ci en écrivant une méthode utilitaire simple, qui vérifiera toute la chaîne causée par.

  /**
   * Recursive method to determine whether an Exception passed is, or has a cause, that is a
   * subclass or implementation of the Throwable provided.
   *
   * @param caught          The Throwable to check
   * @param isOfOrCausedBy  The Throwable Class to look for
   * @return  true if 'caught' is of type 'isOfOrCausedBy' or has a cause that this applies to.
   */
  private boolean isCausedBy(Throwable caught, Class<? extends Throwable> isOfOrCausedBy) {
    if (caught == null) return false;
    else if (isOfOrCausedBy.isAssignableFrom(caught.getClass())) return true;
    else return isCausedBy(caught.getCause(), isOfOrCausedBy);
  }

Lorsque vous l'utilisez, vous créez simplement une liste des if de l'exception la plus spécifique à la moins spécifique, avec une clause else de secours:

try {
  // Code to be executed
} catch (Exception e) {
  if (isCausedBy(e, MyException.class)) {
    // Handle MyException.class
  } else if (isCausedBy(e, AnotherException.class)) {
    // Handle AnotherException.class
  } else {
    throw new IllegalStateException("Error at calling service 'service'");
  }
}
3

Je ne vois aucune raison pour laquelle vous voulez que la gestion des exceptions soit efficace et élégante, je me contente d’être efficace. On les appelle des exceptions pour une raison.

Ce code sera un cauchemar de maintenance. Vous ne pouvez pas repenser la pile d'appels pour lever l'exception qui vous intéresse? S'il est important, les signatures de méthode doivent l'afficher et non le masquer enveloppé dans 2 autres exceptions.

Le premier (e! = Null) n'est pas nécessaire.

Et vous pouvez mieux changer le 3e en e.getCause (). GetCause () instanceof MyException)

2
Peter Tillemans

Vous pouvez faire comme ci-dessous:

catch (RemoteAccessException e) {
    int index = ExceptionUtils.indexOfThrowable(e, MyExcetption.class)
    if (index != -1) {
         //handleMyException
    } else {
    }
}
1
Shrikant Lohiya

Je doute, mais vous pouvez vérifier avec instanceof si l'exception est du type correct.

Edit: Il devrait y avoir une raison pour laquelle l'exception imbriquée est encapsulée, vous devez donc vous demander quel est le but de l'attraper.

0
Petar Minchev