Nous utilisons des exceptions pour permettre au consommateur du code de gérer un comportement inattendu de manière utile. Habituellement, des exceptions sont construites autour du scénario "ce qui s'est passé" - comme FileNotFound
(nous n'avons pas pu trouver le fichier que vous avez spécifié) ou ZeroDivisionError
(nous n'avons pas pu effectuer l' 1/0
opération).
Et s'il est possible de spécifier le comportement attendu du consommateur?
Par exemple, imaginez que nous avons une fetch
ressource, qui exécute la requête HTTP et renvoie les données récupérées. Et au lieu d'erreurs comme ServiceTemporaryUnavailable
ou RateLimitExceeded
nous soulèverions simplement un RetryableError
suggérant au consommateur qu'il devrait simplement réessayer la demande et ne pas se soucier d'un échec spécifique. Donc, nous suggérons essentiellement une action à l'appelant - le "quoi faire".
Nous ne le faisons pas souvent parce que nous ne connaissons pas toutes les utilisations des consommateurs. Mais imaginez que c'est un composant spécifique que nous connaissons la meilleure ligne de conduite pour un appelant - alors devrions-nous alors utiliser l'approche «que faire»?
la source
if( ! function() ) handle_something();
, mais pouvoir gérer l'erreur quelque part où vous connaissez réellement le contexte d'appel - c'est-à-dire dire à un client d'appeler un administrateur sys si votre serveur a échoué ou de se recharger automatiquement si la connexion a chuté, mais vous alerter dans cas, l'appelant est un autre microservice. Laissez les blocs de capture gérer la capture.Réponses:
Cela échoue presque toujours pour au moins l'un de vos appelants, pour lequel ce comportement est extrêmement irritant. Ne présumez pas que vous connaissez le mieux. Dites à vos utilisateurs ce qui se passe, pas ce que vous supposez qu'ils devraient faire à ce sujet. Dans de nombreux cas, il est déjà clair ce que devrait être un plan d'action sensé (et, si ce n'est pas le cas, faites une suggestion dans votre manuel d'utilisation).
Par exemple, même les exceptions données dans votre question démontrent votre hypothèse cassée: a
ServiceTemporaryUnavailable
équivaut à "réessayer plus tard" etRateLimitExceeded
équivaut à "woah there chill out, peut-être ajuster vos paramètres de minuterie et réessayer dans quelques minutes". Mais l'utilisateur peut aussi vouloir déclencher une sorte d'alarmeServiceTemporaryUnavailable
(ce qui indique un problème de serveur), et pas pourRateLimitExceeded
(ce qui n'est pas le cas).Donnez-leur le choix .
la source
RetryableError
.Compte tenu de cette idée:
... une chose que je suggérerais est que vous pourriez mélanger les préoccupations de signaler une erreur avec des plans d'action pour y répondre d'une manière qui pourrait dégrader la généralité de votre code ou nécessiter beaucoup de "points de traduction" pour les exceptions .
Par exemple, si je modélise une transaction impliquant le chargement d'un fichier, elle peut échouer pour plusieurs raisons. Peut-être que le chargement du fichier implique le chargement d'un plugin qui n'existe pas sur la machine de l'utilisateur. Le fichier est peut-être simplement corrompu et nous avons rencontré une erreur lors de son analyse.
Peu importe ce qui se passe, disons que la procédure consiste à signaler ce qui est arrivé à l'utilisateur et à lui demander ce qu'il veut faire ("réessayer, charger un autre fichier, annuler").
Lanceur contre receveur
Cette ligne de conduite s'applique quel que soit le type d'erreur que nous avons rencontré dans ce cas. Il n'est pas intégré à l'idée générale d'une erreur d'analyse, il n'est pas intégré à l'idée générale de l'échec du chargement d'un plugin. Il est intégré à l'idée de rencontrer de telles erreurs lors du contexte précis de chargement d'un fichier (combinaison de chargement d'un fichier et d'échec). Donc, généralement, je le vois, grossièrement parlant, comme la
catcher's
responsabilité de déterminer le plan d'action en réponse à une exception levée (ex: inviter l'utilisateur avec des options), pas lethrower's
.Autrement dit, les sites auxquels les
throw
exceptions manquent généralement ce type d'informations contextuelles, surtout si les fonctions qui lancent sont généralement applicables. Même dans un contexte totalement dégénéré quand ils ont ces informations, vous vous retrouvez coincé en termes de comportement de récupération en l'intégrant dans lethrow
site. Les sites quicatch
sont généralement ceux qui ont le plus d'informations disponibles pour déterminer une ligne de conduite, et vous donnent un endroit central pour modifier si cette ligne de conduite doit jamais changer pour cette transaction donnée.Lorsque vous commencez à essayer de lever des exceptions ne signalant plus ce qui ne va pas mais essayant de déterminer quoi faire, cela pourrait dégrader la généralité et la flexibilité de votre code. Une erreur d'analyse ne doit pas toujours conduire à ce type d'invite, elle varie en fonction du contexte dans lequel une telle exception est levée (la transaction dans laquelle elle a été levée).
Le lanceur aveugle
De manière générale, une grande partie de la conception de la gestion des exceptions tourne souvent autour de l'idée d'un lanceur aveugle. Il ne sait pas comment l'exception va être interceptée, ni où. Il en va de même pour les anciennes formes de récupération d'erreurs utilisant la propagation d'erreur manuelle. Les sites qui rencontrent des erreurs n'incluent pas de ligne de conduite utilisateur, ils incorporent uniquement les informations minimales pour signaler le type d'erreur rencontré.
Responsabilités inversées et généralisation du receveur
En y réfléchissant plus attentivement, j'essayais d'imaginer le type de base de code où cela pourrait devenir une tentation. Mon imagination (peut-être erronée) est que votre équipe joue toujours le rôle de "consommateur" ici et implémente également la plupart du code d'appel. Peut-être que vous avez beaucoup de transactions disparates (beaucoup de
try
blocs) qui peuvent toutes se heurter aux mêmes ensembles d'erreurs, et toutes devraient, du point de vue de la conception, conduire à une action uniforme de récupération.En tenant compte des conseils judicieux de la
Lightness Races in Orbit's
bonne réponse (qui, je pense, vient vraiment d'un état d'esprit avancé orienté bibliothèque), vous pourriez toujours être tenté de lever des exceptions "que faire", seulement plus près du site de récupération de transaction.Il pourrait être possible de trouver ici un site intermédiaire et commun de traitement des transactions qui centralise réellement les préoccupations "que faire" mais toujours dans le contexte de la capture.
Cela ne s'appliquerait que si vous pouvez concevoir une sorte de fonction générale que toutes ces transactions externes utilisent (ex: une fonction qui entre une autre fonction à appeler ou une classe de base de transaction abstraite avec un comportement remplaçable modélisant ce site de transaction intermédiaire qui effectue la capture sophistiquée ).
Pourtant, celui-ci pourrait être responsable de la centralisation du plan d'action de l'utilisateur en réponse à une variété d'erreurs possibles, et toujours dans le contexte de la capture plutôt que du lancer. Exemple simple (pseudocode Python-ish, et je ne suis pas du tout un développeur Python expérimenté, donc il pourrait y avoir une façon plus idiomatique de procéder):
[Avec un peu de chance avec un meilleur nom que
general_catcher
]. Dans cet exemple, vous pouvez transmettre une fonction contenant la tâche à exécuter tout en bénéficiant d'un comportement de capture généralisé / unifié pour tous les types d'exceptions qui vous intéressent, et continuer à étendre ou modifier la partie «que faire» tout vous aimez depuis cet emplacement central et toujours dans uncatch
contexte où cela est généralement encouragé. Mieux encore, nous pouvons empêcher les sites de lancer de se préoccuper de "quoi faire" (en préservant la notion de "lanceur aveugle").Si vous ne trouvez aucune de ces suggestions ici utile et qu'il y a une forte tentation de lever des exceptions "quoi faire" de toute façon, sachez surtout que cela est très anti-idiomatique à tout le moins, ainsi que potentiellement décourageant un état d'esprit généralisé.
la source
Je pense que la plupart du temps, il serait préférable de passer des arguments à la fonction pour lui dire comment gérer ces situations.
Par exemple, considérons une fonction:
Je peux passer RetryPolicy.noRetries () ou RetryPolicy.retries (3) ou autre chose. En cas d'échec d'une nouvelle tentative, il consultera la politique pour décider s'il doit ou non réessayer.
la source
new RetryPolicy().onRateLimitExceeded(STOP).onServiceTemporaryUnavailable(RETRY, 3)
ou quelque chose, car ilRateLimitExceeded
pourrait être nécessaire de gérer différemmentServiceTemporaryUnavailable
. Après avoir écrit cela, ma pensée est: mieux vaut lever une exception, car cela donne un contrôle plus flexible.