Cette question est destinée à s'appliquer à tout langage de programmation orienté objet prenant en charge la gestion des exceptions; J'utilise C # à des fins d'illustration uniquement.
Les exceptions sont généralement destinées à être déclenchées lorsqu'un problème que le code ne peut pas résoudre immédiatement, puis à être interceptées dans une catch
clause située à un emplacement différent (généralement un cadre de pile externe).
Q: Existe-t-il des situations légitimes dans lesquelles des exceptions ne sont pas levées ni interceptées, mais simplement renvoyées par une méthode, puis transmises en tant qu'objets d'erreur?
Cette question m’a été posée car la System.IObserver<T>.OnError
méthode de .NET 4 le suggère: les exceptions sont transmises comme des objets d’erreur.
Regardons un autre scénario, la validation. Supposons que je suis la sagesse conventionnelle et que je distingue donc un type d'objet d'erreur IValidationError
et un type d'exception distinct ValidationException
utilisé pour signaler des erreurs inattendues:
partial interface IValidationError { }
abstract partial class ValidationException : System.Exception
{
public abstract IValidationError[] ValidationErrors { get; }
}
(L' System.Component.DataAnnotations
espace de nom fait quelque chose d'assez similaire.)
Ces types pourraient être utilisés comme suit:
partial interface IFoo { } // an immutable type
partial interface IFooBuilder // mutable counterpart to prepare instances of above type
{
bool IsValid(out IValidationError[] validationErrors); // true if no validation error occurs
IFoo Build(); // throws ValidationException if !IsValid(…)
}
Maintenant, je me demande si je ne peux pas simplifier ce qui précède à ceci:
partial class ValidationError : System.Exception { } // = IValidationError + ValidationException
partial interface IFoo { } // (unchanged)
partial interface IFooBuilder
{
bool IsValid(out ValidationError[] validationErrors);
IFoo Build(); // may throw ValidationError or sth. like AggregateException<ValidationError>
}
Q: Quels sont les avantages et les inconvénients de ces deux approches différentes?
AggregateException
place? Bon point.Réponses:
S'il n'est jamais lancé, il ne s'agit pas d'une exception. C'est un objet
derived
d'une classe Exception, bien qu'il ne suive pas le comportement. L'appeler une exception est purement sémantique à ce stade, mais je ne vois rien de mal à ne pas la lancer. De mon point de vue, ne pas lancer d'exception est exactement la même chose qu'une fonction interne qui l'attrape et l'empêche de se propager.Est-ce valide pour une fonction de retourner des objets d'exception?
Absolument. Voici une courte liste d'exemples où cela peut être approprié:
Est-ce que le jeter n'est pas mauvais?
Lancer une exception, c'est un peu comme: "Va en prison. Ne passe pas Go!" dans le jeu de société Monopoly. Il demande à un compilateur de passer par-dessus tout le code source sans avoir à exécuter ce code source. Cela n'a rien à voir avec des erreurs, rapporter ou arrêter des bogues. J'aime penser à lancer une exception en tant qu'instruction "super return" pour une fonction. Il renvoie l'exécution du code à un niveau beaucoup plus élevé que l'appelant d'origine.
L'important ici est de comprendre que la vraie valeur des exceptions réside dans le
try/catch
modèle, et non dans l'instanciation de l'objet exception. L'objet exception est juste un message d'état.Dans votre question, vous semblez confondre l'utilisation de ces deux choses: un saut vers un gestionnaire de l'exception et l'état d'erreur représenté par l'exception. Le fait que vous ayez pris un état d'erreur et que vous l'ayez enveloppé dans une exception ne signifie pas que vous suivez le
try/catch
modèle ou ses avantages.la source
Exception
classe mais qui ne suit pas le comportement." - Et c'est exactement le problème: est-il légitime d'utiliser unException
objet dérivé dans une situation où il ne sera pas projeté du tout, ni par le producteur de l'objet, ni par aucune méthode d'appel; où il sert uniquement de "message d'état", sans le flux de contrôle "super-retour"? Ou suis-je en train de violer un contrattry
/catch(Exception)
contrat tacite au point que je ne devrais jamais le faire, mais plutôt utiliser un type différent, nonException
typé?No
vous ne rompez aucun contrat. Lepopular opinion
serait que vous ne devriez pas faire ça. La programmation est pleine d'exemples où votre code source ne fait rien de mal, mais tout le monde l'aime pas. Le code source doit toujours être écrit de manière à préciser l’intention. Aidez-vous vraiment un autre programmeur en utilisant une exception de cette façon? Si oui, alors faites-le. Sinon, ne soyez pas surpris si c'est détesté.Renvoyer des exceptions au lieu de les lancer peut avoir un sens sémantique lorsque vous disposez d'une méthode d'assistance pour analyser la situation et renvoyer une exception appropriée qui est ensuite levée par l'appelant (vous pouvez appeler cela une "fabrique d'exceptions"). Lancer une exception dans cette fonction d'analyse d'erreur signifierait que quelque chose n'allait pas au cours de l'analyse, alors que renvoyer une exception signifiait que le type d'erreur avait été analysé avec succès.
Un cas d'utilisation possible pourrait être une fonction qui convertit les codes de réponse HTTP en exceptions:
Notez que le lancement d' une exception signifie que la méthode a été mal utilisée ou qu'il y a eu une erreur interne. Le renvoi d' une exception signifie que le code d'erreur a été identifié avec succès.
la source
return
instructions superflues pour que le compilateur arrête de se plaindre.System.Exit()
. Le code après l'appel de la fonction n'est pas exécuté. Cela ne semble pas être un bon modèle pour une fonction.Conceptuellement, si l'objet exception est le résultat attendu de l'opération, alors oui. Les cas auxquels je peux penser, cependant, impliquent toujours une attrape-lancer à un moment donné:
Variations sur le modèle "Try" (où une méthode encapsule une seconde méthode de levée d'exception, mais capture l'exception et renvoie à la place un booléen indiquant la réussite). Au lieu de renvoyer un booléen, vous pouvez renvoyer n'importe quelle exception levée (null si elle réussit), ce qui permet à l'utilisateur final de disposer de plus d'informations tout en conservant l'indication de réussite ou d'échec sans accroc.
Traitement des erreurs de workflow. Semblable dans la pratique à l’encapsulation de la méthode "essayer un modèle", une étape de flux de travail peut être abstraite dans un modèle de chaîne de commande. Si un lien dans la chaîne lève une exception, il est souvent plus propre d'attraper cette exception dans l'objet abstraite, puis la renvoie à la suite de ladite opération pour que le moteur de flux de travail et le code exécuté en tant qu'étape de flux de travail ne nécessitent pas de nombreuses tentatives. -catch logique de leur propre.
la source
OperationResult
classe abstraite , avec unebool
propriété "Successful", uneGetExceptionIfAny
méthode et uneAssertSuccessful
méthode (qui lèverait l'exception deGetExceptionIfAny
si non-null). Le fait d’GetExceptionIfAny
être une méthode plutôt qu’une propriété permettrait l’utilisation d’objets d’erreur statiques immuables dans les cas où il n’y aurait pas grand-chose à dire.Disons que vous avez chargé une tâche à exécuter dans un pool de threads. Si cette tâche lève une exception, il s'agit d'un thread différent pour ne pas l'attraper. Fil où il a été exécuté vient de mourir.
Considérons maintenant que quelque chose (que ce soit le code de cette tâche ou l'implémentation du pool de threads) intercepte cette exception, le stocke avec la tâche et considère la tâche comme terminée (sans succès). Vous pouvez maintenant demander si la tâche est terminée (et demander si elle a été lancée ou si elle peut être renvoyée dans votre fil de discussion (ou, mieux, une nouvelle exception avec la cause originale comme cause)).
Si vous le faites manuellement, vous remarquerez que vous créez une nouvelle exception, la lançant et la capturant, puis la stockant, dans un fil différent, récupérant le lancer, le rattrapant et le faisant réagir. Il peut donc être judicieux d’éviter de lancer et d’attraper, de simplement l’enregistrer et d’achever la tâche, puis de demander s’il existe une exception et d’y réagir. Mais cela peut conduire à un code plus compliqué si, au même endroit, il peut y avoir de véritables exceptions.
PS: ceci est écrit avec une expérience de Java, où les informations de trace de pile sont créées lors de la création d’une exception (contrairement à C # où elles sont créées lors d’une projection). Ainsi, l'approche d'exception non levée en Java sera plus lente qu'en C # (sauf si elle est pré-créée et réutilisée), mais des informations de trace de pile sont disponibles.
En général, je m'abstiendrais de créer une exception et de ne jamais la lancer (à moins que l'optimisation des performances après le profilage ne l'indique comme un goulot d'étranglement). Au moins en Java, où la création d'exceptions est coûteuse (trace de pile). En C #, c'est une possibilité, mais IMO est surprenant et doit donc être évité.
la source
Cela dépend de votre conception. En règle générale, je ne renverrais pas les exceptions à un appelant, je les jetterais et les rattraperais, sans en rester là. Généralement, le code est écrit pour échouer rapidement et rapidement. Par exemple, considérons le cas de l'ouverture d'un fichier et de son traitement (C # PsuedoCode):
Dans ce cas, nous éviterions les erreurs et arrêterions le traitement du fichier dès qu’il rencontrait un mauvais enregistrement.
Mais disons que l’exigence est que nous souhaitons continuer à traiter le fichier même si une ou plusieurs lignes comportent une erreur. Le code peut commencer à ressembler à ceci:
Donc, dans ce cas, nous renvoyons l'exception à l'appelant, bien que je ne retourne probablement pas d'exception, mais plutôt une sorte d'objet d'erreur car l'exception a été interceptée et déjà traitée. Cela ne fait pas de mal de renvoyer une exception, mais les autres appelants peuvent renvoyer l'exception qui peut ne pas être souhaitable, car l'objet exception a ce comportement. Si vous souhaitez que les appelants aient la possibilité de lancer à nouveau puis de renvoyer une exception, sinon, retirez les informations de l'objet exception et construisez un objet plus léger et plus léger, puis retournez-le. Le rapide échec est généralement un code plus propre.
Pour votre exemple de validation, je n’aurais probablement pas hérité d’une classe d’exception ni d’exceptions car les erreurs de validation pourraient être assez courantes. Il serait moins fastidieux de retourner un objet plutôt que de lancer une exception si 50% de mes utilisateurs ne remplissent pas correctement le formulaire à la première tentative.
la source
Oui. Par exemple, j'ai récemment rencontré une situation dans laquelle j'ai constaté que les exceptions levées dans un service Web ASMX n'incluaient pas d'élément dans le message SOAP résultant. Je devais donc le générer.
Code illustratif:
la source
Dans la plupart des langues (autant que je sache), des exceptions viennent avec quelques goodies ajoutés. Généralement, un instantané de la trace de pile actuelle est stocké dans l'objet. C'est bien pour le débogage, mais cela peut aussi avoir des implications sur la mémoire.
Comme @ThinkingMedia l'a déjà dit, vous utilisez réellement l'exception comme objet d'erreur.
Dans votre exemple de code, il semble que vous le fassiez principalement pour la réutilisation du code et pour éviter la composition. Personnellement, je ne pense pas que ce soit une bonne raison de faire cela.
Une autre raison possible serait de tromper le langage pour vous donner un objet d'erreur avec une trace de pile. Cela donne des informations contextuelles supplémentaires à votre code de traitement des erreurs.
D'autre part, nous pouvons supposer que garder la trace de la pile a un coût en mémoire. Par exemple, si vous commencez à collecter ces objets quelque part, vous risquez de subir un impact désagréable sur la mémoire. Bien entendu, cela dépend en grande partie de la manière dont les traces de pile d'exceptions sont implémentées dans le langage / moteur correspondant.
Alors, est-ce une bonne idée?
Jusqu'à présent, je ne l'ai pas vu utilisé ou recommandé. Mais cela ne veut pas dire grand chose.
La question est: la trace de pile est-elle vraiment utile pour la gestion de vos erreurs? Ou plus généralement, quelles informations souhaitez-vous fournir au développeur ou à l'administrateur?
Le motif typique lancer / essayer / attraper rend nécessaire d'avoir une trace de pile, car sinon, vous n'avez aucune idée de sa provenance. Pour un objet retourné, il provient toujours de la fonction appelée. La trace de la pile peut contenir toutes les informations dont le développeur a besoin, mais une solution moins lourde et plus spécifique suffirait peut-être.
la source
La réponse est oui. Voir http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions et ouvrez la flèche du guide de style C ++ de Google sur les exceptions. Vous pouvez voir ici les arguments contre lesquels ils avaient l'habitude de se prononcer, mais dites que s'ils devaient le refaire, ils auraient probablement décidé de le faire.
Toutefois, il convient de noter que le langage Go n'utilise pas non plus idiomatiquement les exceptions, pour des raisons similaires.
la source