Question

Les exceptions non cochées conviennent si vous souhaitez gérer chaque échec de la même manière, par exemple en le journalisant et en passant à la requête suivante, en affichant un message à l'utilisateur et en gérant l'événement suivant, etc.Si tel est mon cas d'utilisation, tout ce que j'ai à faire est d'intercepter un type d'exception général à un niveau élevé dans mon système et de tout gérer de la même manière.

Mais je veux me remettre de problèmes spécifiques, et je ne suis pas sûr de la meilleure façon de l'aborder avec des exceptions non contrôlées.Voici un exemple concret.

Supposons que j'ai une application Web construite à l'aide de Struts2 et Hibernate.Si une exception survient jusqu'à mon "action", je l'enregistre et présente de jolies excuses à l'utilisateur.Mais l'une des fonctions de mon application Web consiste à créer de nouveaux comptes d'utilisateurs, qui nécessitent un nom d'utilisateur unique.Si un utilisateur choisit un nom qui existe déjà, Hibernate lance un org.hibernate.exception.ConstraintViolationException (une exception non vérifiée) dans les entrailles de mon système.J'aimerais vraiment me remettre de ce problème particulier en demandant à l'utilisateur de choisir un autre nom d'utilisateur, plutôt que de lui donner le même message "nous avons enregistré votre problème mais pour l'instant vous êtes arrosé".

Voici quelques points à considérer :

  1. Il y a beaucoup de gens qui créent des comptes simultanément.Je ne veux pas verrouiller toute la table utilisateur entre un "SELECT" pour voir si le nom existe et un "INSERT" si ce n'est pas le cas.Dans le cas des bases de données relationnelles, il peut y avoir quelques astuces pour contourner ce problème, mais ce qui m'intéresse vraiment, c'est le cas général où la pré-vérification d'une exception ne fonctionnera pas en raison d'une condition de concurrence fondamentale.La même chose pourrait s'appliquer à la recherche d'un fichier sur le système de fichiers, etc.
  2. Étant donné la propension de mon CTO à la gestion au volant induite par la lecture des colonnes technologiques dans "Inc.", j'ai besoin d'une couche d'indirection autour du mécanisme de persistance afin de pouvoir abandonner Hibernate et utiliser Kodo, ou autre, sans rien changer sauf le plus bas. couche de code de persistance.En fait, il existe plusieurs couches d’abstraction dans mon système.Comment puis-je empêcher leur fuite malgré des exceptions non vérifiées ?
  3. L'une des faiblesses déclarées des exceptions vérifiées est de devoir les "gérer" à chaque appel sur la pile, soit en déclarant qu'une méthode appelante les lance, soit en les interceptant et en les gérant.Les gérer signifie souvent les envelopper dans une autre exception vérifiée d’un type approprié au niveau d’abstraction.Ainsi, par exemple, dans le pays des exceptions vérifiées, une implémentation de mon UserRegistry basée sur le système de fichiers pourrait intercepter IOException, alors qu'une implémentation de base de données attraperait SQLException, mais les deux lanceraient un UserNotFoundException qui cache l'implémentation sous-jacente.Comment puis-je profiter des exceptions non contrôlées, en m'épargnant du fardeau de cet emballage à chaque couche, sans divulguer les détails de l'implémentation ?
Était-ce utile?

La solution

OMI, l'emballage des exceptions (vérifiées ou non) présente plusieurs avantages qui en valent le coût :

1) Cela vous encourage à réfléchir aux modes de défaillance du code que vous écrivez.Fondamentalement, vous devez considérer les exceptions que le code que vous appelez peut générer, et à votre tour, vous considérerez les exceptions que vous lancerez pour le code qui appelle le vôtre.

2) Cela vous donne la possibilité d'ajouter des informations de débogage supplémentaires dans la chaîne d'exceptions.Par exemple, si vous disposez d'une méthode qui lève une exception sur un nom d'utilisateur en double, vous pouvez envelopper cette exception avec une autre qui inclut des informations supplémentaires sur les circonstances de l'échec (par exemple, l'adresse IP de la requête qui a fourni le nom d'utilisateur en double) qui n'était pas disponible pour le code de niveau inférieur.La trace des exceptions des cookies peut vous aider à déboguer un problème complexe (c'est certainement le cas pour moi).

3) Il vous permet de devenir indépendant de l'implémentation du code de niveau inférieur.Si vous encapsulez des exceptions et devez remplacer Hibernate par un autre ORM, il vous suffit de modifier votre code de gestion Hibernate.Toutes les autres couches de code continueront à utiliser avec succès les exceptions encapsulées et les interpréteront de la même manière, même si les circonstances sous-jacentes ont changé.Notez que cela s'applique même si Hibernate change d'une manière ou d'une autre (ex :ils changent d'exceptions dans une nouvelle version) ;il ne s'agit pas uniquement d'un remplacement technologique en gros.

4) Cela vous encourage à utiliser différentes classes d'exceptions pour représenter différentes situations.Par exemple, vous pouvez avoir une DuplicateUsernameException lorsque l'utilisateur tente de réutiliser un nom d'utilisateur, et une DatabaseFailureException lorsque vous ne pouvez pas vérifier les noms d'utilisateur en double en raison d'une connexion à la base de données interrompue.Ceci, à son tour, vous permet de répondre à votre question (« comment puis-je récupérer ? ») de manière flexible et puissante.Si vous obtenez une DuplicateUsernameException, vous pouvez décider de suggérer un nom d'utilisateur différent à l'utilisateur.Si vous obtenez une DatabaseFailureException, vous pouvez la laisser bouillonner jusqu'au point où elle affiche une page "en panne pour maintenance" à l'utilisateur et vous envoie un e-mail de notification.Une fois que vous disposez d’exceptions personnalisées, vous disposez de réponses personnalisables – et c’est une bonne chose.

Autres conseils

J'aime reconditionner les exceptions entre les "niveaux" de mon application, ainsi par exemple une exception spécifique à la base de données est reconditionnée à l'intérieur d'une autre exception qui est significative dans le contexte de mon application (bien sûr, je laisse l'exception d'origine en tant que membre donc Je n'encombre pas la trace de la pile).

Cela dit, je pense qu'un nom d'utilisateur non unique n'est pas une situation suffisamment "exceptionnelle" pour justifier un lancement.J'utiliserais plutôt un argument de retour booléen.Sans connaître grand-chose de votre architecture, il m'est difficile de dire quoi que ce soit de plus spécifique ou d'applicable.

Voir Modèles de génération, de manipulation et de gestion des erreurs

À partir du modèle Split Domain et Erreurs techniques

Une erreur technique ne doit jamais provoquer une erreur de domaine (jamais le Twain ne doit se rencontrer).Lorsqu'une erreur technique doit entraîner l'échec du traitement commercial, elle doit être enveloppée en tant que SystemError.

Les erreurs de domaine doivent toujours commencer à partir d'un problème de domaine et être gérée par code de domaine.

Les erreurs de domaine doivent passer "de manière transparente" à travers des limites techniques.Il se peut que ces erreurs soient sérialisées et reconstituées pour que cela se produise.Les procurations et les façades devraient prendre la responsabilité de le faire.

Les erreurs techniques doivent être gérées dans des points particuliers dans l'application, tels que les limites (voir le journal à la limite de distribution).

La quantité d'informations de contexte remontées avec l'erreur dépendra de l'utilité de celle-ci pour le diagnostic et la manipulation ultérieurs (déterminer une stratégie alternative).Vous devez vous demander si la trace de pile d'une machine distante est entièrement utile au traitement d'une erreur de domaine (bien que l'emplacement du code de l'erreur et des valeurs variables à ce moment puisse être utile)

Donc, enveloppez l'exception de mise en veille prolongée à la limite pour mettre en veille prolongée avec une exception de domaine non vérifiée telle qu'une "UniqueUsernameException", et laissez-la remonter jusqu'à son gestionnaire.Assurez-vous de javadoc l'exception levée même s'il ne s'agit pas d'une exception vérifiée !

Puisque vous utilisez actuellement la mise en veille prolongée, la chose la plus simple à faire est simplement de vérifier cette exception et de l'envelopper dans une exception personnalisée ou dans un objet de résultat personnalisé que vous avez peut-être configuré dans votre framework.Si vous souhaitez abandonner la mise en veille prolongée plus tard, assurez-vous simplement de placer cette exception à un seul endroit, le premier endroit où vous interceptez l'exception de la mise en veille prolongée, c'est le code que vous devrez probablement modifier de toute façon lorsque vous effectuerez un changement, donc si le catch est au même endroit, alors les frais généraux supplémentaires sont presque nuls.

aide?

Je suis d'accord avec Nick.L'exception que vous avez décrite n'est pas vraiment une "exception inattendue", vous devez donc concevoir votre code en conséquence en tenant compte des exceptions possibles.

Je recommanderais également de jeter un œil à la documentation de Microsoft Enterprise Library Bloc de gestion des exceptions il a un bon aperçu des modèles de gestion des erreurs.

Vous pouvez intercepter les exceptions non vérifiées sans avoir besoin de les envelopper.Par exemple, ce qui suit est Java valide.

try {
    throw new IllegalArgumentException();
} catch (Exception e) {
    System.out.println("boom");
}

Ainsi, dans votre action/contrôleur, vous pouvez avoir un bloc try-catch autour de la logique où l'appel Hibernate est effectué.En fonction de l'exception, vous pouvez afficher des messages d'erreur spécifiques.

Mais je suppose que dans votre cadre d'aujourd'hui, cela pourrait être Hibernate et demain SleepLongerDuringWinter.Dans ce cas, vous devez prétendre avoir votre propre petit framework ORM qui entoure le framework tiers.Cela vous permettra d'envelopper toutes les exceptions spécifiques au framework dans des exceptions plus significatives et/ou vérifiées que vous savez mieux comprendre.

  1. La question n'est pas vraiment liée au check vs.débat incontrôlé, la même chose s’applique aux deux types d’exceptions.

  2. Entre le point où ConstraintViolationException est levée et le point où nous voulons gérer la violation en affichant un joli message d'erreur, il y a un grand nombre d'appels de méthode sur la pile qui devraient être abandonnés immédiatement et ne devraient pas se soucier du problème.Cela fait du mécanisme d'exception le bon choix plutôt que de repenser le code des exceptions aux valeurs de retour.

  3. En fait, utiliser une exception non vérifiée au lieu d’une exception vérifiée est une solution naturelle, puisque nous voulons vraiment que toutes les méthodes intermédiaires de la pile d’appels ignorer l'exception et je ne m'en occupe pas .

  4. Si nous voulons gérer la "violation de nom unique" uniquement en affichant un joli message d'erreur (page d'erreur) à l'utilisateur, il n'est pas vraiment nécessaire d'avoir une exception DuplicateUsernameException spécifique.Cela maintiendra le nombre de classes d’exception à un faible niveau.Au lieu de cela, nous pouvons créer une MessageException qui peut être réutilisée dans de nombreux scénarios similaires.

    Dès que possible, nous attrapons la ConstraintViolationException et la convertissons en MessageException avec un joli message.Il est important de le convertir rapidement, lorsque nous pouvons être sûrs que c'est bien la "contrainte de nom d'utilisateur unique" qui a été violée et non une autre contrainte.

    Quelque part à proximité du gestionnaire de niveau supérieur, gérez simplement MessageException d'une manière différente.Au lieu de "nous avons enregistré votre problème mais pour l'instant vous êtes arrosé", affichez simplement le message contenu dans MessageException, aucune trace de pile.

    MessageException peut prendre des paramètres de constructeur supplémentaires, tels qu'une explication détaillée du problème, l'action suivante disponible (annuler, aller sur une autre page), une icône (erreur, avertissement)...

Le code peut ressembler à ceci

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

Dans un endroit totalement différent, il y a un gestionnaire d'exceptions supérieur

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}

@Jan Coché ou non est une question centrale ici.Je remets en question votre supposition (#3) selon laquelle l'exception devrait être ignorée dans les images intermédiaires.Si je fais cela, je me retrouverai avec une dépendance spécifique à l'implémentation dans mon code de haut niveau.Si je remplace Hibernate, les blocs catch dans toute mon application devront être modifiés.Pourtant, en même temps, si j'attrape l'exception à un niveau inférieur, je ne tire pas beaucoup d'avantages de l'utilisation d'une exception non contrôlée.

De plus, le scénario ici est que je souhaite détecter une erreur logique spécifique et modifier le flux de l'application en demandant à nouveau à l'utilisateur un identifiant différent.Changer simplement le message affiché n'est pas suffisant, et la possibilité de mapper différents messages en fonction du type d'exception est déjà intégrée aux servlets.

@erikson

Juste pour ajouter de la nourriture à vos pensées :

Coché versus non coché est également débattu ici

L'utilisation d'exceptions non vérifiées est conforme au fait qu'elles sont utilisées par l'OMI pour les exceptions. provoqué par l'appelant de la fonction (et l'appelant peut être plusieurs couches au-dessus de cette fonction, d'où la nécessité pour les autres trames d'ignorer l'exception)

Concernant votre problème spécifique, vous devez intercepter l'exception non vérifiée à un niveau élevé et l'encapsuler, comme l'a dit @Kanook dans votre propre exception, sans afficher la pile d'appels (comme mentionné par @Jan Soltis)

Ceci étant dit, si la technologie sous-jacente change, cela aura bel et bien un impact sur ces catch() déjà présents dans votre code, et cela ne répond pas à votre dernier scénario.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top