Attraper une exception de base pour lever une exception plus spécifique?

Sep 12 2020

J'ai une interface Accessoret Repository. Accessorrésume où stocker les documents JSON (système de fichiers local, base de données NoSQL, etc.). Repositoryrésume la représentation de mes objets de domaine et effectue mon mapping objet-relationnel. Accessorest injecté dans un Repository.

J'ai un UserRepositoryqui est injecté avec tout Accessor. Par exemple, je pourrais injecter un FileSystemAccessordans ceci UserRepository, et cela tirerait des documents JSON de mon système de fichiers, mappés sur des Userobjets.

Accessorfournit une méthode get_for_unique_valuequi recherche un document JSON par une valeur unique pour une clé spécifique, étant donné le nom d'une "collection" contenant tous ces documents JSON. S'il existe plusieurs documents avec la même valeur, un NonUniqueValueErrorest renvoyé.

UserRepositoryfournit une méthode get_user_by_emailqui trouve un utilisateur par son e-mail. Plusieurs utilisateurs ne sont pas censés exister avec le même e-mail, je veux donc lever une exception pour l'indiquer. Le sous-jacent injecté Accessorfournit get_for_unique_value, donc l' get_user_by_emailutilise en interne. Tout contrôleur de niveau supérieur ne serait pas en mesure de dériver le contexte du NonUniqueValueErrorbouillonnement, donc je veux attraper le sous-jacent NonUniqueValueErroret lancer un à la NonUniqueUserErrorplace.

NonUniqueUserError peut être déclaré de trois manières:

  1. NonUniqueUserErrorprend le jeté NonUniqueValueErroret s'étend Exception.
  2. NonUniqueUserErrorprend le jeté NonUniqueValueErroret s'étend NonUniqueValueError.
  3. NonUniqueUserErrors'étend NonUniqueValueError, sans prendre le pris NonUniqueValueError.

(1) préserve la trace de pile sous-jacente du jet NonUniqueValueError, mais n'est pas intercepté par une NonUniqueValueErrorclause plus large plus haut dans la pile d'appels. (3) serait intercepté par une telle clause, mais perd la trace de pile. (2) semble combiner le meilleur des deux mondes.

Est-ce que j'aborde ce problème correctement en attrapant un NonUniqueValueErroret en le lançant à la NonUniqueUserErrorplace? Dans l'affirmative, y a-t-il une raison pour laquelle je ne devrais pas choisir (2)? J'ai lu Est-ce vraiment si mauvais d'attraper une exception générale? , mais je veux confirmer que je traite toujours correctement l' NonUniqueValueErrorexception ici (car elle est plus générale que NonUniqueUserError).

La capture d'exception de base pour préserver l'intégrité des données montre une situation où la même exception exacte est interceptée et levée, mais j'essaie d'ajouter du contexte en lançant une nouvelle exception plus spécifique.

Réponses

7 DocBrown Sep 12 2020 at 15:47

Tout d'abord, permettez-moi de répondre à vos questions immédiates:

  • Ajouter plus de contexte à une exception autrement non spécifique en l'attrapant et en la renvoyant est une technique standard, il n'y a rien de mal à cela (voir ici ou ici .)

  • Lors de la relance, la préservation de la trace de pile sous-jacente est certainement une bonne idée, en particulier dans le cas où la trace de pile de l'exception non spécifique peut pointer vers un code qui est sous votre propre contrôle. Sinon, la trace de la pile peut être utile pour le fournisseur du framework / bibliothèque qui a levé l'exception.

  • Faire NonUniqueUserErrorune spécialisation des NonUniqueValueErrorsons me semble raisonnable, mais c'est plus une question de goût.

Cependant, je pense que l'éléphant dans la salle est la question de savoir si c'est une bonne idée - ou non - de fournir une méthode get_user_by_emailqui ne peut renvoyer qu'un seul utilisateur, alors que le système permet en fait de stocker plusieurs utilisateurs avec la même adresse e-mail . Même si cela "ne devrait normalement pas être le cas", cela semble être possible dans le système décrit. Lorsqu'il se produit, il ne s'agit pas d'un échec du programme dans le code de requête, mais d'une incohérence des données. Cela signifie que le débogage du code de requête avec une trace de pile ne mène probablement à rien.

Pour permettre à un appelant de gérer la situation «exceptionnelle» où plusieurs de ces utilisateurs existent de manière plus flexible, une méthode get_users_by_email(qui retourne un tableau ou une séquence de plusieurs utilisateurs) me paraît l'option la plus judicieuse. Dans le cas où cette méthode retourne plus d'un enregistrement utilisateur, les appelants peuvent toujours décider s'ils préfèrent

  1. pour gérer ce cas en affichant ou en enregistrant les différents utilisateurs multiples, ou

  2. pour sélectionner l'un des résultats par des moyens supplémentaires, comme d'autres attributs que l'adresse e-mail, ou peut-être une sélection interactive, ou

  3. une action différente comme arrêter le programme, ne rien faire ou tout ce qui est approprié.

Le simple fait de lancer une exception empêcherait l'appelant d'effectuer des actions telles que # 1 et # 2. Donc, au lieu de réfléchir beaucoup à la conception des classes d'exceptions susmentionnées, il vaut mieux vérifier si une conception d'API sans aucune exception spécifique de ce type pourrait mieux répondre aux exigences données.