"Sire, je suis venu avec une défense de dragon." Il n'a plus peur de nous! Il est déclenché par le battement des ailes d'un dragon et allume une sirène forte pour que tout le monde puisse entendre le dragon s'approcher.
"Cette défense fait-elle autre chose?"
"Non, pourquoi?" Nous serons prévenus!
"Oui ... Mangé par les sirènes hurlantes ... Et pourtant ... nous rappelle-t-on quand nous avons prévu des pannes? ...
Description du problème
Cette méthode ne prétend pas au concept de gestion des erreurs dans les projets complexes et complexes. Il s'agit plutôt d'un exemple de ce qui peut être fait avec un minimum de moyens.
Une bonne norme consiste à supposer qu'aucun assert () ne doit être déclenché pendant l'exécution du programme. Et si au moins un assert () a fonctionné lors du test de l'application, vous devez envoyer cette erreur au développeur. Mais que faire si l'application n'est pas entièrement testée? Et assert () fonctionnera pour le client? Envoyer une erreur au développeur? Abandonner un programme? En réalité, ce sera la version finale de l'application et l'assert () standard sera simplement désactivé. La question se pose également avec la contradiction interne du système: il devrait y avoir beaucoup d'assert () pour faciliter la détection des erreurs, mais il devrait y avoir moins d'assert () pour moins interrompre l'utilisateur et son travail avec l'application. Je ne voudrais surtout pas «tomber» si le nombre de personnes qui utilisent l'application dépend de la stabilité du travail et si assert () était essentiellement insignifiant (nécessitant une correction, mais permettant, par exemple, de continuer à travailler assez bien).
De telles considérations conduisent à la nécessité d'affiner assert () c / c ++. Et définissez vos propres macros qui étendent les fonctionnalités de assert () standard - mais en ajoutant un minimum de gestion des erreurs. Que ces macros soient.
VERIFY_EXIT (Condition);
VERIFY_RETURN (Condition, ReturnValue);
VERIFY_THROW (condition, exception);
VERIFY_DO (Condition) {/ * bloc échec * /};(Ces macros peuvent être appelées différemment. Par exemple, VERIFY_OR_EXIT (), VERIFY_OR_RETURN (), VERIFY_OR_THROW (), VERIFY_OR_DO (). Ou vice versa dans une version plus courte.)
Ces macros, tout d'abord, ont une implémentation pour la version de débogage de la compilation et la version finale. Cela leur permet d'avoir un comportement dans la version finale du programme. C'est-à-dire effectuer des actions non seulement pendant les tests, mais aussi avec l'utilisateur.
Description de la macro
(La description des macros est approximative, leur autre conception est également possible.)
1)
VERIFY_EXIT (Condition);Vérifie la
condition Condition et si elle est fausse, elle appelle la norme assert () (version de débogage) et quitte également la fonction actuelle (versions de débogage et de publication).
2)
VERIFY_RETURN (Condition, ReturnValue);Il vérifie la
condition Condition et s'il est faux, il appelle la norme assert () (version de débogage), et quitte également la fonction actuelle en retournant
ReturnValue (versions de débogage et de publication).
3)
VERIFY_THROW (condition, exception);Vérifie la
condition Condition et si elle est fausse, elle appelle ensuite la norme assert () (version de débogage) et lève également une
exception (versions de débogage et de publication).
4)
VERIFY_DO (Condition) {/ * bloc de blocage * /};Vérifie la
condition Condition et si elle est fausse, elle appelle la norme assert () (version de débogage), et effectue également le
bloc d'échec ou l'opération immédiatement après la macro (versions de débogage et de publication).
Pour toutes les macros, il est important:
- Dans tous les cas, la condition doit être vraie pour «passer» la macro et fausse pour activer le chemin de la gestion minimale des erreurs.
- Chacune des macros implémente une méthode minimale de gestion des erreurs. Ceci est nécessaire pour implémenter le comportement en cas d'erreurs non détectées lors des tests, mais survenues chez l'utilisateur. Selon l'implémentation, vous pouvez informer le développeur d'une erreur survenue sur le client, mais chaque implémentation fournit également un moyen minimal de récupérer d'une erreur.
Modèles d'utilisation des macros
Bien sûr, la chose la plus intéressante est que les surhommes entropiques (héros de la réduction des erreurs dans les programmes) sont l'utilisation de ces macros.
1) Conditions préalables et postérieures.
Le premier cas d'utilisation est les conditions pré et post. Permettez-moi de vous rappeler que les conditions préalables vérifient l'état du programme (arguments d'entrée, état de l'objet, variables utilisées) pour vérifier la conformité aux exigences nécessaires du fragment de code exécuté. Les conditions de publication (elles sont moins courantes dans les programmes) visent à vérifier que nous avons atteint le résultat souhaité et que l'état des objets reste valide pour le fragment de code actuel.
L'utilisation des macros proposées est simple - nous attribuons chaque vérification dans une macro distincte. Nous sélectionnons les macros en fonction de la gestion des erreurs dont nous avons besoin. (VERIFY_EXIT () - traitement d'erreur avec la sortie de cette fonction, VERIFY_RETURN () - traitement d'erreur avec le retour d'une valeur, VERRIFY_THROW () - traitement d'erreur avec une exception, etc.)
Vous pouvez également ajouter ou utiliser la macro VERIFY (), qui n'effectuera aucune gestion d'erreur. Cela peut être utile, par exemple dans les conditions de publication à la fin d'une fonction.
Ces macros sont complètement autonomes si vous utilisez les principes du code pur et allouez suffisamment de fonctions pour implémenter des actions atomiques. Chaque fonction peut vérifier l'état d'un objet, les arguments d'entrée, etc. pour mener à bien son action atomique.
2) Sémantique d'une transaction.
En outre, ces macros peuvent être utilisées pour implémenter du code avec une sémantique de transaction. Une telle sémantique s'entend comme: 1) une préparation progressive à une opération avec vérification des résultats de chacune des étapes de préparation; 2) l'exécution de l'action uniquement si toutes les étapes de préparation ont réussi; 3) refus de remplir, si certaines conditions ne sont pas remplies au stade de la préparation (avec un éventuel retour en arrière de l'exécution).
3) Concevoir le code en tenant compte d'une éventuelle extension.
Cela est particulièrement vrai pour les bibliothèques et le code général, qui peuvent initialement être développés dans le contexte d'une seule condition d'exécution, et peuvent ensuite être utilisés avec d'autres conditions (commencer à être utilisés différemment). Dans ce cas, ces macros peuvent décrire les "limites" de la fonctionnalité du code. Déterminez ce qui a été initialement perçu comme une erreur et ce qui a réussi. (Cette approche est proche des conditions de post-publication classiques.) Bien sûr, j'écris les «bordures» entre guillemets, car ces limites peuvent être révisées, mais il est important de déterminer (ou plutôt de transmettre aux futurs développeurs) la connaissance des limites acceptables de la conception de code.
Implémentation de macro
Je suppose que la plupart des développeurs de niveau intermédiaire n'auront pas de problèmes pour implémenter ces macros. Mais si vous avez besoin d'informations, je vais consacrer quelques points importants.
Les macros doivent être représentables comme une seule instruction. Que peut-on faire avec do {} tandis que les (fausses) constructions ou similaires. Par exemple, comme ceci:
#define VERFY_EXIT(cond) \ do{bool _= (bool)(cond); assert(_); if(!_) {return;}} while(false) \
Ensuite, vous pouvez écrire le code suivant:
if(a > 0) VERIFY_EXIT(a%2==0);
Bien sûr, ce n'est qu'une des possibilités de mise en œuvre. Vous pouvez implémenter des macros de différentes manières.
PS Bataille réussie avec l'entropie, surhommes!