Je me bats avec une question très simple:
Je travaille maintenant sur une application serveur, et j'ai besoin d'inventer une hiérarchie pour les exceptions (certaines exceptions existent déjà, mais un cadre général est nécessaire). Comment puis-je même commencer à faire cela?
Je pense suivre cette stratégie:
1) Qu'est-ce qui ne va pas?
- Quelque chose est demandé, ce qui n'est pas autorisé.
- Quelque chose est demandé, il est autorisé, mais cela ne fonctionne pas, en raison de paramètres incorrects.
- Quelque chose est demandé, c'est permis, mais ça ne marche pas, à cause d'erreurs internes.
2) Qui lance la demande?
- L'application client
- Une autre application serveur
3) Remise des messages: comme nous traitons avec une application serveur, il s'agit de recevoir et d'envoyer des messages. Et si l'envoi d'un message se passe mal?
En tant que tel, nous pouvons obtenir les types d'exceptions suivants:
- ServerNotAllowedException
- ClientNotAllowedException
- ServerParameterException
- ClientParameterException
- InternalException (dans le cas où le serveur ne sait pas d'où vient la demande)
- ServerInternalException
- ClientInternalException
- MessageHandlingException
Il s'agit d'une approche très générale pour définir la hiérarchie des exceptions, mais je crains de manquer de cas évidents. Avez-vous des idées sur les domaines que je ne couvre pas, connaissez-vous des inconvénients de cette méthode ou existe-t-il une approche plus générale de ce type de question (dans ce dernier cas, où puis-je la trouver)?
Merci d'avance
la source
catch
blocs que j'utilise, je n'ai pas beaucoup plus recours à l'exception que le message d'erreur qu'il contient. Je n'ai vraiment rien de différent que je puisse faire pour une exception impliquée dans l'échec de la lecture d'un fichier comme un échec d'allocation de mémoire pendant le processus de lecture, j'ai donc tendance à attraperstd::exception
et à signaler le message d'erreur qu'il contient, peut-être à décorer avec"Failed to open file: %s", ex.what()
un tampon de pile avant de l'imprimer.catch
blocs différents dans un seul site de récupération, mais souvent c'est juste pour ignorer le message à l'intérieur de l'exception et imprimer un message plus localisé ...Réponses:
Remarques générales
(un peu biaisé)
Je n'opterais généralement pas pour une hiérarchie d'exceptions détaillée.
Chose la plus importante: une exception indique à votre appelant que votre méthode n'a pas pu terminer son travail. Et votre interlocuteur doit en être averti, afin qu'il ne continue pas simplement. Cela fonctionne avec n'importe quelle exception, quelle que soit la classe d'exception que vous choisissez.
Le deuxième aspect est la journalisation. Vous souhaitez trouver des entrées de journal significatives en cas de problème. Cela n'a pas non plus besoin de classes d'exception différentes, seulement des messages texte bien conçus (je suppose que vous n'avez pas besoin d'un automate pour lire vos journaux d'erreurs ...).
Le troisième aspect est la réaction de votre interlocuteur. Que peut faire votre correspondant lorsqu'il reçoit une exception? Ici, il peut être judicieux d'avoir différentes classes d'exceptions, de sorte que l'appelant peut décider de réessayer le même appel, d'utiliser une solution différente (par exemple, utiliser une source de secours à la place) ou d'abandonner.
Et vous souhaitez peut-être utiliser vos exceptions comme base pour informer l'utilisateur final du problème. Cela signifie créer un message convivial en plus du texte admin pour le fichier journal, mais n'a pas besoin de classes d'exception différentes (bien que cela puisse rendre la génération de texte plus facile ...).
Un aspect important pour la journalisation (et pour les messages d'erreur utilisateur) est la possibilité de modifier l'exception avec des informations de contexte en la rattrapant sur une couche, en ajoutant des informations de contexte, par exemple des paramètres de méthode, et en la relançant.
Votre hiérarchie
Qui lance la demande? Je ne pense pas que vous aurez besoin des informations qui ont lancé la demande. Je ne peux même pas imaginer comment vous savez au fond d'une pile d'appels.
Gestion des messages : ce n'est pas un aspect différent, mais juste des cas supplémentaires pour "Qu'est-ce qui ne va pas?".
Dans un commentaire, vous parlez d'un indicateur " pas de journalisation " lors de la création d'une exception. Je ne pense pas qu'à l'endroit où vous créez et lâchez une exception, vous pouvez prendre une décision fiable de consigner ou non cette exception.
La seule situation que je peux imaginer est qu'une couche supérieure utilise votre API d'une manière qui produira parfois des exceptions, et cette couche sait alors qu'elle ne doit déranger aucun administrateur avec l'exception, donc elle avale silencieusement l'exception. Mais c'est une odeur de code: une exception attendue est une contradiction en soi, un indice pour changer l'API. Et c'est la couche supérieure qui devrait décider, pas le code générateur d'exceptions.
la source
La principale chose à garder à l'esprit lors de la conception d'un modèle de réponse aux erreurs est de s'assurer qu'il est utile aux appelants. Cela s'applique que vous utilisiez des exceptions ou des codes d'erreur définis, mais nous nous limiterons à illustrer avec des exceptions.
Si votre langage ou votre infrastructure fournit déjà des classes d'exceptions générales, utilisez-les là où elles sont appropriées et où elles sont raisonnablement prévisibles . Ne pas définir vos propres
ArgumentNullException
ou desArgumentOutOfRange
classes exception. Les appelants ne s'attendront pas à les attraper.Définissez une
MyClientServerAppException
classe de base pour englober les erreurs uniques dans le contexte de votre application. Ne lancez jamais une instance de la classe de base. Une réponse d'erreur ambiguë est LA PIRE CHOSE JAMAIS . S'il y a une "erreur interne", expliquez ce qu'est cette erreur.Pour la plupart, la hiérarchie sous la classe de base doit être large, pas profonde. Il vous suffit de l'approfondir dans les situations où il est utile à l'appelant. Par exemple, s'il y a 5 raisons pour lesquelles un message peut échouer du client au serveur, vous pouvez définir une
ServerMessageFault
exception, puis définir une classe d'exception pour chacune de ces 5 erreurs en dessous. De cette façon, l'appelant peut simplement attraper la superclasse s'il en a besoin ou s'il le souhaite. Essayez de limiter cela à des cas spécifiques et raisonnables.N'essayez pas de définir toutes vos classes d'exceptions avant qu'elles ne soient réellement utilisées. Vous finirez par refaire l'essentiel. Lorsque vous rencontrez un cas d'erreur lors de l'écriture de code, décidez de la meilleure façon de décrire cette erreur. Idéalement, il devrait être exprimé dans le contexte de ce que l'appelant essaie de faire.
En ce qui concerne le point précédent, n'oubliez pas que ce n'est pas parce que vous utilisez des exceptions pour répondre aux erreurs que vous devez utiliser uniquement des exceptions pour les états d'erreur. Gardez à l'esprit que lever des exceptions est généralement coûteux et que le coût des performances peut varier d'une langue à l'autre. Avec certaines langues, le coût est plus élevé en fonction de la profondeur de la pile d'appels, donc si une erreur est profonde dans une pile d'appels, vérifiez si vous ne pouvez pas utiliser de types primitifs simples (codes d'erreur entiers ou drapeaux booléens) pour pousser l'erreur sauvegarde la pile, afin qu'elle puisse être levée plus près de l'appel de l'appelant.
Si vous incluez la journalisation dans le cadre de la réponse d'erreur, il devrait être facile pour les appelants d'ajouter des informations de contexte à un objet d'exception. Commencez à partir de l'endroit où les informations sont utilisées dans le code de journalisation. Décidez combien d'informations sont nécessaires pour que le journal soit utile (sans être un mur de texte géant). Ensuite, travaillez en arrière pour vous assurer que les classes d'exception peuvent être facilement fournies avec ces informations.
Enfin, à moins que votre application ne puisse absolument gérer avec élégance une erreur de mémoire insuffisante, n'essayez pas de traiter ces erreurs ou d'autres exceptions d'exécution catastrophiques. Laissez simplement le système d'exploitation s'en occuper, car en réalité, c'est tout ce que vous pouvez faire.
la source
Eh bien, je vous recommande avant tout de créer une
Exception
classe de base pour toutes les exceptions vérifiées qui peuvent être levées par votre application. Si votre application a été appeléeDuckType
, créez uneDuckTypeException
classe de base.Cela vous permet d'intercepter toute exception de votre
DuckTypeException
classe de base pour la gestion. À partir de là, vos exceptions devraient se ramifier avec des noms descriptifs qui peuvent mieux mettre en évidence le type de problème. Par exemple, "DatabaseConnectionException".Permettez-moi d'être clair, ces exceptions doivent toutes être vérifiées pour les situations qui pourraient arriver que vous voudriez probablement gérer avec élégance dans votre programme. En d'autres termes, vous ne pouvez pas vous connecter à la base de données, donc un
DatabaseConnectionException
message est jeté que vous pouvez attraper pour attendre et réessayer après un certain temps.Vous ne verriez pas d' exception vérifiée pour un problème très inattendu tel qu'une requête SQL non valide ou une exception de pointeur nul, et je vous encourage à laisser ces exceptions transcender la plupart des clauses catch (ou interceptées et renvoyées si nécessaire) jusqu'à ce que vous arriviez au principal contrôleur lui-même, qui peut alors intercepter
RuntimeException
uniquement à des fins de journalisation.Ma préférence personnelle est de ne pas renvoyer une
RuntimeException
exception non vérifiée , car, par la nature d'une exception non vérifiée, vous ne vous y attendriez pas et, par conséquent, la renvoyer sous une autre exception cache des informations. Cependant, si c'est votre préférence, vous pouvez toujours attraper unRuntimeException
et lancer unDuckTypeInternalException
qui contrairement àDuckTypeException
dériveRuntimeException
et n'est donc pas coché.Si vous préférez, vous pouvez sous-catégoriser vos exceptions à des fins d'organisation, par exemple
DatabaseException
pour tout ce qui concerne la base de données, mais j'encouragerais toujours ces sous-exceptions à dériver de votre exception de baseDuckTypeException
et à être abstraites et donc dérivées avec des noms descriptifs explicites.En règle générale, vos captures d'essai devraient être de plus en plus génériques lorsque vous montez dans la pile d'appels pour gérer les exceptions, et encore une fois, dans votre contrôleur principal, vous ne géreriez pas un
DatabaseConnectionException
mais plutôt le simpleDuckTypeException
dont dérivent toutes vos exceptions vérifiées.la source
Essayez de simplifier cela.
La première chose qui vous aidera à penser dans une autre stratégie est la suivante: intercepter de nombreuses exceptions est très similaire à l'utilisation d' exceptions vérifiées de Java (désolé, je ne suis pas développeur C ++). Ce n'est pas bon pour de nombreuses raisons, donc j'essaie toujours de ne pas les utiliser, et votre stratégie d'exception de la hiérarchie m'en rappelle beaucoup.
Donc, je vous recommande une autre stratégie flexible: utilisez des exceptions non vérifiées et des erreurs de code.
Par exemple, consultez ce code Java:
Et votre exception unique :
Cette stratégie que j'ai trouvée sur ce lien et vous pouvez trouver l'implémentation Java ici , où vous pouvez voir plus de détails à ce sujet, car le code ci-dessus est simplifié.
Comme vous devez séparer les différentes exceptions entre les applications "client" et "autre serveur", vous pouvez avoir plusieurs classes de code d'erreur implémentant l'interface ErrorCode.
la source
Les exceptions sont les gotos sans restriction et doivent être utilisées avec prudence. La meilleure stratégie pour eux est de les restreindre. La fonction appelante doit gérer toutes les exceptions levées par les fonctions qu'elle appelle ou le programme meurt. Seule la fonction appelante a le contexte correct pour gérer l'exception. Avoir des fonctions plus haut dans l'arborescence d'appels les gère sont des gotos sans restriction.
Les exceptions ne sont pas des erreurs. Ils se produisent lorsque les circonstances empêchent le programme de terminer une branche du code et indiquent une autre branche à suivre.
Les exceptions doivent être dans le contexte de la fonction appelée. Par exemple: une fonction qui résout des équations quadratiques . Il doit gérer deux exceptions: division_by_zero et square_root_of_negative_number. Mais ces exceptions n'ont pas de sens pour les fonctions appelant ce solveur d'équations quadratiques. Ils se produisent en raison de la méthode utilisée pour résoudre les équations et les relancer simplement expose les composants internes et rompt l'encapsulation. Au lieu de cela, division_by_zero doit être renommé not_quadratic et square_root_of_negative_number et no_real_roots.
Il n'est pas nécessaire d'avoir une hiérarchie d'exceptions. Une énumération des exceptions (qui les identifie) levées par une fonction est suffisante car la fonction appelante doit les gérer. Leur permettre de traiter l'arborescence des appels est un goto hors contexte (sans restriction).
la source