53 votes

Théorie sur la gestion des erreurs ?

La plupart des conseils concernant la gestion des erreurs se résument à une poignée de trucs et astuces (cf. ce poste par exemple). Ces conseils sont utiles mais je pense qu'ils ne répondent pas à toutes les questions. J'ai le sentiment que je devrais concevoir mon application en fonction d'une certaine philosophie, d'une école de pensée qui fournit une base solide sur laquelle s'appuyer. Existe-t-il une telle théorie sur le thème de la gestion des erreurs ?

Voici quelques questions pratiques :

  • Comment décider si une erreur doit être traitée localement ou propagée à un code de niveau supérieur ?
  • Comment choisir entre consigner une erreur ou l'afficher sous forme de message d'erreur à l'utilisateur ?
  • La journalisation doit-elle être effectuée uniquement dans le code de l'application ? Ou est-il possible de faire de la journalisation à partir du code de la bibliothèque ?
  • En cas d'exception, où devez-vous généralement les attraper ? Dans le code de bas niveau ou de niveau supérieur ?
  • Devriez-vous vous efforcer d'appliquer une stratégie unifiée de traitement des erreurs à toutes les couches du code, ou essayer de développer un système capable de s'adapter à diverses stratégies de traitement des erreurs (afin de pouvoir traiter les erreurs provenant de bibliothèques tierces).
  • Est-il utile de créer une liste de codes d'erreur ? Ou est-ce démodé de nos jours ?

Dans de nombreux cas, le bon sens suffit pour développer une stratégie suffisante pour faire face aux conditions d'erreur. Toutefois, j'aimerais savoir s'il existe une approche plus formelle/"savante" ?

PS : il s'agit d'une question générale, mais les réponses spécifiques au C++ sont également les bienvenues (le C++ est mon principal langage de programmation au travail).

14voto

ROAR Points 598

La journalisation est-elle quelque chose qui ne doit dans le code de l'application ? Ou est-il d'effectuer une certaine journalisation à partir du du code.

Je voulais juste faire un commentaire à ce sujet. Mon point de vue est de ne jamais logger directement dans le code de la bibliothèque, mais de fournir des crochets ou des callbacks pour implémenter ceci dans le code de l'application, de sorte que l'application puisse décider de ce qu'il faut faire avec la sortie du log (si quelque chose du tout).

11voto

marcmagransdeabril Points 815

Il y a quelques années, je me posais exactement la même question :)

Après avoir cherché et lu plusieurs choses, je pense que la référence la plus intéressante que j'ai trouvée était Modèles pour la génération, le traitement et la gestion des erreurs d'Andy Longshaw et Eoin Woods. Il s'agit d'une tentative courte et systématique de couvrir les idiomes de base que vous mentionnez et quelques autres.

La réponse à ces questions est assez controversée, mais les auteurs ci-dessus ont eu le courage de s'exposer lors d'une conférence, puis de coucher leurs idées sur le papier.

10voto

Warren Dew Points 3257

Introduction

Pour comprendre ce qui doit être fait pour la gestion des erreurs, je pense qu'il faut clairement comprendre les types d'erreurs que l'on rencontre, et les contextes dans lesquels on les rencontre.

Pour moi, il a été extrêmement utile de considérer les deux principaux types d'erreurs comme :

  1. Erreurs qui ne devraient jamais se produire et qui sont généralement dues à un bogue dans le code.

  2. Les erreurs qui sont attendues et ne peuvent être évitées dans le cadre d'un fonctionnement normal, comme une connexion à une base de données qui tombe en panne à cause d'un problème de base de données sur lequel l'application n'a aucun contrôle.

La manière dont une erreur doit être traitée dépend fortement du type d'erreur dont il s'agit.

Les différents contextes qui influencent la façon dont les erreurs doivent être traitées sont les suivants :

  • Code d'application

  • Code de la bibliothèque

La gestion des erreurs dans le code de la bibliothèque diffère quelque peu de celle du code de l'application.

Une philosophie pour le traitement des deux principaux types d'erreurs est discutée ci-dessous. Les considérations spéciales pour le code de la bibliothèque sont également abordées. Enfin, les questions pratiques spécifiques de l'article original sont abordées dans le contexte de la philosophie présentée.

Types d'erreurs

Erreurs de programmation - bugs - et autres erreurs qui ne devraient jamais se produire

De nombreuses erreurs sont le résultat de fautes de programmation. Ces erreurs ne peuvent généralement pas être corrigées, car l'erreur de programmation spécifique ne peut être anticipée. Cela signifie que nous ne pouvons pas savoir à l'avance dans quel état l'erreur laisse l'application, nous ne pouvons donc pas récupérer de cet état et ne devrions pas essayer.

En fin de compte, la solution à ce type d'erreur consiste à corriger l'erreur de programmation. Pour ce faire, l'erreur doit être signalée aussi rapidement que possible. Idéalement, le programme devrait se terminer immédiatement après avoir identifié une telle erreur et fourni les informations pertinentes. Une sortie rapide et évidente réduit le temps nécessaire à l'achèvement du cycle de débogage et de retestage, ce qui permet de corriger un plus grand nombre de bogues dans le même laps de temps, et d'obtenir une application plus robuste avec moins de bogues au moment du déploiement.

L'autre objectif majeur dans le traitement de ce type d'erreur doit être de fournir suffisamment d'informations pour faciliter l'identification du bogue. En Java, par exemple, le lancement d'une RuntimeException fournit souvent suffisamment d'informations dans la trace de la pile pour identifier le bogue immédiatement ; dans un code propre, les corrections immédiates peuvent souvent être identifiées juste en examinant la trace de la pile. Dans d'autres langages, il est possible d'enregistrer la pile d'appels ou de conserver les informations nécessaires. Il est essentiel de ne pas supprimer des informations dans l'intérêt de la brièveté ; ne vous inquiétez pas de l'espace que vous prenez dans le journal lorsque ce type d'erreur se produit. Plus vous fournirez d'informations, plus les bogues pourront être corrigés rapidement et moins il restera de bogues pour polluer les journaux lorsque l'application sera mise en production.

Applications serveur

Dans certaines applications serveur, il est important que le serveur soit suffisamment tolérant aux pannes pour continuer à fonctionner même en cas d'erreurs de programmation occasionnelles. Dans ce cas, la meilleure approche consiste à établir une séparation très nette entre le code serveur qui doit continuer à fonctionner et le code de traitement des tâches qui peut être autorisé à échouer. Par exemple, les tâches peuvent être reléguées dans des threads ou des sous-processus, comme c'est le cas dans de nombreux serveurs Web.

Dans une telle architecture de serveur, le thread ou le sous-processus qui traite la tâche peut alors être traité comme une application qui peut échouer. Toutes les considérations ci-dessus s'appliquent à une telle tâche : l'erreur doit être mise en évidence aussi rapidement que possible par une sortie propre de la tâche, et suffisamment d'informations doivent être enregistrées pour permettre de trouver et de corriger facilement le bogue. Lorsqu'une telle tâche se termine en Java, par exemple, la trace complète de la pile de toute RuntimeException ayant causé la sortie doit normalement être enregistrée.

La plus grande partie possible du code doit être exécutée dans les threads ou les processus qui gèrent la tâche, plutôt que dans le thread ou le processus principal du serveur. En effet, tout bogue dans le thread ou le processus principal du serveur entraînera toujours l'arrêt de l'ensemble du serveur. Il est préférable de pousser le code - avec les bogues qu'il contient - dans le code de gestion des tâches, où il ne provoquera pas de plantage du serveur lorsque le bogue se manifestera.

Les erreurs qui sont attendues et qui ne peuvent être évitées dans le cadre d'un fonctionnement normal

Les erreurs qui sont attendues et ne peuvent être évitées dans le cadre d'un fonctionnement normal, comme une exception provenant d'une base de données ou d'un autre service distinct de l'application, nécessitent un traitement très différent. Dans ces cas, l'objectif n'est pas de corriger le code, mais plutôt de faire en sorte que le code gère l'erreur lorsque cela a un sens, et d'informer les utilisateurs ou les opérateurs qui peuvent résoudre le problème autrement.

Dans ces cas, par exemple, l'application peut souhaiter jeter tous les résultats accumulés jusqu'à présent et réessayer l'opération. Dans l'accès aux bases de données, l'utilisation de transactions peut contribuer à garantir que les données accumulées sont éliminées. Dans d'autres cas, il peut être utile d'écrire son code en tenant compte de ces tentatives. Le concept d'idempotence peut également être utile ici.

Lorsque les tentatives automatisées ne résolvent pas suffisamment le problème, il convient d'informer les êtres humains. L'utilisateur doit être informé de l'échec de l'opération ; souvent, on peut lui donner la possibilité de réessayer. L'utilisateur peut alors juger si une nouvelle tentative est souhaitable, et peut également apporter des modifications à l'entrée qui pourraient améliorer les choses lors d'une nouvelle tentative.

Pour ce type d'erreur, on peut utiliser la journalisation et peut-être des avis par courriel pour informer les opérateurs du système. Contrairement à la consignation des erreurs de programmation, la consignation des erreurs attendues dans le cadre d'un fonctionnement normal doit être plus succincte, car l'erreur peut se produire plusieurs fois et apparaître plusieurs fois dans les journaux ; les opérateurs analyseront souvent le schéma de nombreuses erreurs, plutôt que de se concentrer sur une erreur individuelle.

Bibliothèques et applications

La discussion ci-dessus sur les types d'erreurs est directement applicable au code d'application. L'autre contexte majeur pour la gestion des erreurs est le code de bibliothèque. Le code de bibliothèque a toujours les deux mêmes types d'erreurs de base, mais il ne peut ou ne doit pas communiquer directement avec l'utilisateur, et il a moins de connaissances sur le contexte de l'application, y compris si une sortie immédiate est acceptable, que le code de l'application.

Par conséquent, il existe des différences dans la manière dont les bibliothèques doivent gérer la journalisation, la manière dont elles doivent gérer les erreurs qui peuvent être attendues dans le cadre d'un fonctionnement normal, et la manière dont elles doivent gérer les erreurs de programmation et autres erreurs qui ne devraient jamais se produire.

En ce qui concerne la journalisation, la bibliothèque doit si possible prendre en charge la journalisation dans le format souhaité par le code d'application client. Une approche valable est de ne pas faire de journalisation du tout, et de permettre au code d'application de faire toute la journalisation sur la base des informations d'erreur fournies au code d'application par la bibliothèque. Une autre approche consiste à utiliser une interface de journalisation configurable, permettant à l'application cliente de fournir l'implémentation de la journalisation, par exemple lors du premier chargement de la bibliothèque. En Java, par exemple, la bibliothèque peut utiliser l'interface de journalisation logback, et permettre à l'application de se préoccuper de l'implémentation de la journalisation à configurer pour que logback l'utilise.

Pour les bogues et autres erreurs qui ne devraient jamais se produire, les bibliothèques ne peuvent toujours pas simplement quitter l'application, car cela pourrait ne pas être acceptable pour l'application. Les bibliothèques doivent plutôt quitter l'appel de la bibliothèque, en fournissant à l'appelant suffisamment d'informations pour l'aider à diagnostiquer le problème. Ces informations peuvent être fournies sous la forme d'une exception avec une trace de la pile, ou la bibliothèque peut consigner les informations si l'approche de consignation configurable est utilisée. L'application peut alors traiter cette erreur comme n'importe quelle autre erreur de ce type, généralement en quittant ou, dans un serveur, en autorisant le processus de tâche ou le thread à quitter, avec la même journalisation ou le même rapport d'erreur que pour les erreurs de programmation dans le code de l'application.

Les erreurs qui sont attendues dans le cadre d'un fonctionnement normal doivent également être signalées au code client. Dans ce cas, comme pour ce type d'erreur rencontrée dans le code client, les informations associées à l'erreur peuvent être plus succinctes. En règle générale, les bibliothèques devraient moins gérer localement ce type d'erreur, en s'appuyant davantage sur le code client pour décider, par exemple, s'il faut réessayer et combien de fois. Le code client peut ensuite transmettre la décision de réessayer à l'utilisateur si nécessaire.

Questions pratiques

Maintenant que nous avons la philosophie, appliquons-la aux questions pratiques que vous mentionnez.

  • Comment décider si une erreur doit être traitée localement ou propagée à un code de niveau supérieur ?

S'il s'agit d'une erreur attendue dans le cadre d'un fonctionnement normal, réessayez ou consultez éventuellement l'utilisateur localement. Sinon, il faut la propager au code de niveau supérieur.

  • Comment choisir entre consigner une erreur ou l'afficher sous forme de message d'erreur à l'utilisateur ?

S'il s'agit d'une erreur attendue dans le cadre d'un fonctionnement normal et que l'intervention de l'utilisateur serait utile pour déterminer l'action à entreprendre, demandez-lui son avis et enregistrez un message succinct ; s'il semble s'agir d'une erreur de programmation, informez brièvement l'utilisateur et enregistrez des informations plus détaillées.

  • La journalisation doit-elle être effectuée uniquement dans le code de l'application ? Ou est-il possible de faire de la journalisation à partir du code de la bibliothèque ?

La journalisation du code de la bibliothèque doit être sous le contrôle du code client. Tout au plus, la bibliothèque devrait enregistrer vers une interface pour laquelle le client fournit l'implémentation.

  • En cas d'exceptions, où faut-il généralement les attraper ? Dans le code de bas niveau ou de niveau supérieur ?

Les exceptions qui sont attendues dans le cadre d'une opération normale peuvent être capturées localement et l'opération est relancée ou traitée d'une autre manière. Dans tous les autres cas, les exceptions doivent être autorisées à se propager.

  • Devriez-vous vous efforcer d'appliquer une stratégie unifiée de traitement des erreurs à toutes les couches du code, ou essayer de développer un système capable de s'adapter à diverses stratégies de traitement des erreurs (afin de pouvoir traiter les erreurs provenant de bibliothèques tierces).

Les types d'erreurs dans les bibliothèques tierces sont les mêmes types d'erreurs que celles qui se produisent dans le code de l'application. Les erreurs doivent être traitées principalement en fonction du type d'erreur qu'elles représentent, avec des ajustements pertinents pour le code de la bibliothèque.

  • Est-il utile de créer une liste de codes d'erreur ? Ou est-ce démodé de nos jours ?

Le code d'application doit fournir une description complète de l'erreur dans le cas d'erreurs de programmation, et une description succincte dans le cas d'erreurs pouvant survenir dans le cadre d'un fonctionnement normal ; dans les deux cas, une description est normalement plus appropriée qu'un code d'erreur. Les bibliothèques peuvent fournir un code d'erreur pour indiquer si une erreur est une erreur de programmation ou une autre erreur interne, ou si l'erreur peut se produire dans le cadre d'un fonctionnement normal, ce dernier type pouvant être subdivisé plus finement ; toutefois, une hiérarchie d'exceptions peut être plus utile qu'un code d'erreur dans les langages où cela est possible. Notez toutefois que les applications exécutées à partir de la ligne de commande peuvent faire office de bibliothèques pour les scripts shell.

7voto

Matthieu M. Points 101624

Avertissement : Je ne connais aucune théorie sur la gestion des erreurs, j'ai cependant pensé de manière répétitive à ce sujet en explorant divers langages et paradigmes de programmation, ainsi qu'en jouant avec des conceptions de langages de programmation (et en les discutant). Ce qui suit, donc, est un résumé de mon expérience jusqu'à présent ; avec des arguments objectifs.

Note : cela devrait couvrir toutes les questions, mais je n'ai même pas essayé de les aborder dans l'ordre, préférant une présentation structurée. À la fin de chaque section, je présente une réponse succincte aux questions auxquelles elle a répondu, pour plus de clarté.


Introduction

En guise de prémisse, je voudrais noter que, quel que soit le sujet de la discussion, certains paramètres doivent être gardés à l'esprit lors de la conception d'une bibliothèque (ou d'un code réutilisable).

L'auteur ne peut espérer deviner comment cette bibliothèque sera utilisée et doit donc éviter les stratégies qui rendent l'intégration plus difficile qu'elle ne le devrait. Le défaut le plus flagrant serait de s'appuyer sur un état partagé au niveau mondial ; un état partagé au niveau du thread peut également être un cauchemar pour les interactions avec les coroutines/green-threads. L'utilisation de ces coroutines et threads met également en évidence le fait que la synchronisation doit être laissée à l'utilisateur. Dans un code monofilaire, elle sera inexistante (meilleures performances), tandis que dans les coroutines et les green-threads, l'utilisateur est le mieux placé pour implémenter (ou utiliser des implémentations existantes) des mécanismes de synchronisation dédiés.

Ceci étant dit, lorsque la bibliothèque est à usage interne uniquement, les variables globales ou thread-local pourrait ne sont pas pratiques ; si elles sont utilisées, elles doivent être clairement documentées comme une limitation technique.


Enregistrement

Il existe plusieurs façons d'enregistrer des messages :

  • avec des informations supplémentaires telles que l'horodatage, l'ID du processus, l'ID du fil, le nom/IP du serveur, ...
  • via des appels synchrones ou avec un mécanisme asynchrone (et un mécanisme de gestion des débordements)
  • dans des fichiers, des bases de données, des bases de données distribuées, des serveurs de journaux dédiés, ...

En tant qu'auteur d'une bibliothèque, les journaux doivent être intégrés à l'infrastructure du client (ou désactivés). La meilleure façon d'y parvenir est de permettre au client de fournir des hooks afin de gérer lui-même les logs, ma recommandation est la suivante :

  • pour fournir 2 hooks : un pour décider si l'on doit loguer ou non, et un pour loguer réellement (le message étant formaté et le dernier hook appelé seulement lorsque le client a décidé de loguer)
  • pour fournir, en plus du message : une gravité (aka niveau), le nom du fichier, de la ligne et de la fonction si c'est un logiciel libre ou sinon l'adresse de l'utilisateur. module logique (si plusieurs)
  • pour, par défaut, écrire dans stdout y stderr (en fonction de la gravité), jusqu'à ce que le client dise explicitement de ne pas enregistrer

Je tiens à souligner que, conformément aux directives énoncées dans l'introduction, la synchronisation est laissée au client.

En ce qui concerne l'enregistrement des erreurs : n'enregistrez pas (en tant qu'erreurs) ce que vous signalez déjà par ailleurs via votre API ; vous pouvez toutefois continuer à enregistrer les détails à un niveau de gravité moindre. Le client peut décider de signaler ou non l'erreur lorsqu'il la traite, et par exemple choisir de ne pas la signaler s'il ne s'agissait que d'un appel spéculatif.

Remarque : certaines informations ne doivent pas figurer dans les journaux et il est préférable de masquer d'autres éléments. Par exemple, les mots de passe ne doivent pas être enregistrés, et les numéros de carte de crédit, de passeport ou de sécurité sociale doivent être masqués (au moins partiellement). Dans une bibliothèque conçue pour ces informations sensibles, cela peut être fait pendant la journalisation ; sinon, l'application doit s'en charger.

La journalisation doit-elle être effectuée uniquement dans le code de l'application ? Ou est-il possible de faire de la journalisation à partir du code de la bibliothèque ?

Le code d'application doit décider de la politique à suivre. Le fait qu'une bibliothèque se connecte ou non dépend de sa nécessité.


Poursuivre après une erreur ?

Avant de parler du signalement des erreurs, la première question à se poser est de savoir si l'erreur doit être signalée (pour être traitée) ou si les choses vont tellement mal que l'abandon du processus en cours est clairement la meilleure politique.

Il s'agit certainement d'un sujet délicat. En général, je conseillerais de concevoir le système de manière à ce que la poursuite du processus soit une option, avec une purge/réinitialisation si nécessaire. Si cela ne peut être réalisé dans certains cas, alors ces cas devraient provoquer l'interruption du processus.

Note : sur certains systèmes, il est possible d'obtenir un vidage de la mémoire du processus. Si une application manipule des données sensibles (mot de passe, cartes de crédit, passeports, ...), il est préférable de la désactiver en production (mais elle peut être utilisée pendant le développement).

Note : il peut être intéressant d'avoir un interrupteur de débogage qui transforme une partie des appels de signalement d'erreur en avortements avec un vidage de la mémoire pour aider au débogage pendant le développement.


Signaler une erreur

L'apparition d'une erreur signifie que le contrat d'une fonction/interface n'a pas pu être rempli. Cela a plusieurs conséquences :

  • le client doit être averti, c'est pourquoi l'erreur doit être signalée
  • aucune donnée partiellement correcte ne doit s'échapper dans la nature

Ce dernier point sera traité plus loin ; pour l'instant, concentrons-nous sur le signalement de l'erreur. Le client ne devrait jamais être en mesure de accidentellement ignorer ce rapport. C'est pourquoi l'utilisation de codes d'erreur est une telle abomination (dans les langues où les valeurs de retour peuvent être ignorées) :

ErrorStatus_t doit(Input const* input, Output* output);

Je connais deux systèmes qui nécessitent une action explicite de la part du client :

  • exceptions
  • les types de résultats ( optional<T> , either<T, U> , ...)

Le premier est bien connu, le second est très utilisé dans les langages fonctionnels et a été introduit dans C++11 sous le nom de std::future<T> bien que d'autres implémentations existent.

Je conseille de préférer la seconde solution, lorsque c'est possible, car elle est plus facile à comprendre, mais de revenir aux exceptions lorsqu'aucun résultat n'est attendu. Contraste :

Option<Value&> find(Key const&);

void updateName(Client::Id id, Client::Name name);

Dans le cas d'opérations "en écriture seule" telles que updateName le client n'a pas besoin d'un résultat. Il podría être introduit, mais il serait facile d'oublier le contrôle.

Le retour aux exceptions se produit également lorsqu'un type de résultat est peu pratique ou insuffisant pour transmettre les détails :

Option<Value&> compute(RepositoryInterface&, Details...);

Dans un tel cas de callback défini de manière externe, il existe une liste presque infinie de défaillances potentielles. L'implémentation pourrait utiliser le réseau, une base de données, le système de fichiers, ... dans ce cas, et afin de rapporter les erreurs avec précision :

  • la fonction de rappel définie en externe doit être censée signaler les erreurs par le biais d'exceptions lorsque l'interface est insuffisante (ou peu pratique) pour transmettre tous les détails de l'erreur.
  • les fonctions basées sur ce abstrait Le callback doit être transparent pour ces exceptions (les laisser passer, sans les modifier).

L'objectif est de laisser cette exception remonter jusqu'à la couche où l'implémentation de l'interface a été décidée (au moins), car ce n'est qu'à ce niveau qu'il est possible d'interpréter correctement l'exception levée.

Remarque : le callback défini en externe n'est pas obligé d'utiliser des exceptions, nous devons simplement nous attendre à ce qu'il en utilise.


Utilisation d'une erreur

Afin d'utiliser un rapport d'erreur, le client doit disposer d'informations suffisantes pour prendre une décision. Les informations structurées, comme les codes d'erreur ou les types d'exception, doivent être privilégiées (pour les actions automatiques) et des informations supplémentaires (message, pile, ...) peuvent être fournies de manière non structurée (pour que les humains puissent enquêter).

Il serait préférable qu'une fonction documente clairement tous les modes d'échec possibles : quand ils se produisent et comment ils sont signalés. Cependant, surtout en cas d'exécution d'un code arbitraire, le client doit être prêt à faire face à des codes/exceptions inconnus.

Une exception notable est, bien sûr, les types de résultats : boost::variant<Output, Error0, Error1, ...> fournit une liste exhaustive, vérifiée par le compilateur, des modes de défaillance connus... bien qu'une fonction retournant ce type puisse toujours être rejetée, bien sûr.

Comment choisir entre consigner une erreur ou l'afficher sous forme de message d'erreur à l'utilisateur ?

L'utilisateur doit toujours être averti lorsque sa commande n'a pu être honorée, mais un message convivial (compréhensible) doit être affiché. Si possible, des conseils ou des solutions de contournement doivent également être présentés. Les détails sont pour les équipes de recherche.


Récupérer une erreur ?

Enfin, et ce n'est certainement pas le moins important, vient la partie vraiment effrayante des erreurs : la récupération.

C'est une chose pour laquelle les bases de données (les vraies) sont si bien faites : la sémantique des transactions. Si quelque chose d'inattendu se produit, la transaction est interrompue comme si rien ne s'était passé.

Dans le monde réel, les choses ne sont pas simples. L'exemple simple de l'annulation d'un e-mail envoyé me vient à l'esprit : trop tard. Des protocoles peuvent exister, en fonction de votre domaine d'application, mais cela n'entre pas dans le cadre de cette discussion. La première étape, cependant, est la capacité de récupérer un courrier électronique sain. en mémoire et cela est loin d'être simple dans la plupart des langages (et les STM ne peuvent pas tout faire aujourd'hui).

Tout d'abord, une illustration du défi :

void update(Client& client, Client::Name name, Client::Address address) {
    client.update(std::move(name));
    client.update(std::move(address)); // Throws
}

Maintenant, après que la mise à jour de l'adresse ait échoué, je me retrouve avec une adresse à moitié mise à jour. client . Qu'est-ce que je peux faire ?

  • essayer d'annuler toutes les mises à jour qui ont eu lieu est presque impossible (l'annulation peut échouer).
  • la copie de l'état avant l'exécution de chaque mise à jour est un gouffre de performance (en supposant que l'on puisse même le rechanger de manière sûre).

Dans tous les cas, la comptabilité requise est telle que des erreurs se glissent.

Et le pire de tout : il n'y a aucune hypothèse sûre qui puisse être faite quant à l'étendue de la corruption (sauf que client est maintenant bâclée). Ou du moins, aucune hypothèse qui résistera au temps (et aux changements de code).

Comme souvent, le seul moyen de gagner est de ne pas jouer.


Une solution possible : Transactions

Dans la mesure du possible, l'idée maîtresse est de définir macro qui soit échouera, soit produira le résultat attendu. Ce sont nos transactions . Et leur forme est invariante :

Either<Output, Error> doit(Input const&);

// or

Output doit(Input const&); // throw in case of error

Une transaction ne modifie pas d'état externe, donc si elle ne produit pas de résultat :

  • le monde extérieur n'a pas changé (rien à rétablir)
  • il n'y a pas de résultat partiel à observer

Toute fonction qui n'est pas une transaction doit être considérée comme ayant corrompu tout ce qu'elle a touché, et donc la seule façon de s'en sortir. sain d'esprit La meilleure façon de traiter une erreur provenant de fonctions non transactionnelles est de la laisser s'accumuler jusqu'à ce qu'une couche transactionnelle soit atteinte. Toute tentative de traiter l'erreur avant est, en fin de compte, vouée à l'échec.

Comment décider si une erreur doit être traitée localement ou propagée à un code de niveau supérieur ?

En cas d'exceptions, où faut-il généralement les attraper ? Dans le code de bas niveau ou de niveau supérieur ?

Traitez-les à chaque fois que c'est sûr de le faire et il y a de l'intérêt à le faire. En particulier, il est normal d'attraper une erreur, de vérifier si elle peut être traitée localement, puis de la traiter ou de la laisser passer.


Devriez-vous vous efforcer d'appliquer une stratégie unifiée de traitement des erreurs à toutes les couches du code, ou essayer de développer un système capable de s'adapter à diverses stratégies de traitement des erreurs (afin de pouvoir traiter les erreurs provenant de bibliothèques tierces).

Je n'ai pas abordé cette question précédemment, mais je crois qu'il est clair que l'approche que j'ai mise en évidence est déjà double puisqu'elle comprend à la fois des types de résultats et des exceptions. En tant que tel, le traitement des bibliothèques tierces devrait être un jeu d'enfant, bien que je conseille de les envelopper quand même pour d'autres raisons (le code tiers est mieux isolé au-delà d'une interface orientée métier chargée de l'adaptation de l'impédance).

5voto

MarkR Points 37178

Mon point de vue sur la journalisation (ou d'autres actions) à partir du code de la bibliothèque est JAMAIS.

Une bibliothèque ne devrait pas imposer une politique à son utilisateur, et l'utilisateur peut avoir INTENSIFIÉ qu'une erreur se produise. Le programme a peut-être délibérément sollicité une erreur particulière, dans l'espoir qu'elle se produise, pour tester une condition. L'enregistrement de cette erreur serait trompeur.

La journalisation (ou toute autre chose) impose une politique à l'appelant, ce qui est mauvais. De plus, si une condition d'erreur inoffensive (qui serait ignorée ou réessayée de manière inoffensive par l'appelant, par exemple) devait se produire avec une fréquence élevée, le volume des journaux pourrait masquer toute erreur légitime ou causer des problèmes de robustesse (remplissage des disques, utilisation excessive d'E/S, etc.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X