Vous êtes sur la page 1sur 20

Programmation par contrat, application en C++

par Julien Blanc

Date de publication : 14/12/2009

Ce tutoriel vise prsenter de manire relativement concise les objectifs de la conception et de la programmation par contrat, ainsi que les techniques de mise en oeuvre dans le langage C++. Le lecteur est suppos connatre les bases de la programmation, de l'approche oriente objet et de la gnricit. Ce tutoriel s'adresse donc des dveloppeurs de niveau moyen expriment. Ragissez cet article :

Programmation par contrat, application en C++ par Julien Blanc

1 - Prsentation gnrale, historique, contexte et objectifs 2 - Approche fonctionnelle, dfinition du contrat 2.1 - Prcondition 2.2 - Postcondition 2.2.1 - Postconditions et exceptions 2.3 - Responsabilit du contrat 2.4 - Rupture du contrat 2.5 - Neutralit du contrat 3 - Approche objet 3.1 - Invariants de classe 3.1.1 - Invariants et exceptions 3.2 - Hritage du contrat 4 - La gnricit et le contrat 5 - Application au C++ 5.1 - Les contrats existants en C++ 5.1.1 - Le typage, vu du point de vue contrat 5.1.2 - Le cas de const 5.2 - Dfinir un contrat, utilisation du prprocesseur 5.2.1 - Comment grer une violation de contrat ? 5.2.2 - Pourquoi dsactiver la vrification du contrat ? 5.2.3 - Macros utiles 5.3 - Dfinir un contrat hrit grce l'interface non virtuelle 5.4 - Documenter avec Doxygen 6 - Pour aller un peu plus loin 6.1 - Notion de covariance, et respect du contrat 6.2 - Rfrences 7 - Remerciements

-2Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

1 - Prsentation gnrale, historique, contexte et objectifs


La programmation par contrat est une technique de conception (en anglais, on utilise le terme plus heureux de design by contract) invente par Bertrand Meyer, qui l'a intgre dans le langage Eiffel. L'objectif de cette mthode est d'arriver crire du code plus robuste et plus facilement rutilisable. Partant du principe que la premire qualit d'un code est la correction (le code fait ce pour quoi il a t conu), les objectifs de la programmation par contrat sont : Rduire le temps consacr au dbogage du code, en dtectant les erreurs au plus tt dans le cycle de dveloppement. Faciliter la dtection des erreurs. Augmenter la rutilisabilit du code, en s'assurant qu'un code correct dans un environnement le sera aussi dans un nouvel environnement.

-3Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

2 - Approche fonctionnelle, dfinition du contrat


Un contrat, tel qu'on l'entend en programmation par contrat, est avant tout une relation de confiance entre deux parties. Dans l'approche fonctionnelle, les deux parties sont l'appelant et l'appel. Ces deux parties s'engagent, chacune, respecter leur part du contrat. La terminologie la plus couramment employe utilise une analogie avec le monde rel. On parle ainsi de fournisseur (l'appel fournit un service) et de client (l'appelant fait appel au service).

2.1 - Prcondition
Les prconditions, ce sont les conditions ncessaires et suffisantes la bonne excution de la fonction. Par exemple :
double racine_carree(x)

a une prcondition assez vidente, qui est x >= 0. En programmation classique, cette prcondition n'est jamais exprime. Elle est implicite. Aussi, le programmeur doit deviner, partir de ce que fait la fonction, quelles sont ses prconditions. L'exprience montre que le programmeur a souvent tendance croire que la fonction qu'il appelle en fait plus que ce qu'elle ne fait rellement. Aussi, en programmation par contrat, sont incluses, ds la conception, les conditions ncessaires et suffisantes l'appel de la fonction. charge de l'appelant de s'assurer qu'il les respecte. La fonction sera donc dclare de la sorte (en pseudo-code) :
double racine_carree(x) require x >= 0;

La notation utilise ici n'est l que pour illustrer, et gagner en clart. Ce n'est donc pas du code C++ valide, inutile d'essayer de le compiler. Une prsentation de ce qui est possible en C++ sera donne plus loin. La diffrence peut paratre mineure, mais elle est fondamentale. En exprimant ces prconditions, on garantit l'appelant que, s'il les respecte, on aura un comportement dfini. Qu'est-ce qu'une bonne prcondition, comment dois-je dfinir mes prconditions ? Une bonne prcondition rpond aux critres suivants : Elle est vrifiable par l'appelant : nous verrons pourquoi plus loin. Elle porte gnralement sur les paramtres de la fonction. Je n'ai pas dfini de comportement, si elle n'est pas respecte. Ds qu'on respecte mes prconditions, j'ai un comportement dfini.

Une fonction peut bien entendu avoir plusieurs prconditions. Comme une prcondition est une expression boolenne, ces prconditions peuvent se combiner au moyen des oprateurs logiques OU et ET. Sans autre prcision, le dfaut est l'oprateur ET. La fonction strlen, par exemple, pourrait s'exprimer comme ceci :
int strlen(char * s)

-4Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

require s != NULL require "s termine par un \0"

On remarque que la deuxime prcondition n'est pas rellement exprimable dans le langage. Ce n'est pas un problme, le contrat s'adressant avant tout au dveloppeur qui, lui, est tout fait capable de le comprendre.

2.2 - Postcondition
La postcondition, c'est une proprit qui est vrifie lorsque la fonction est termine. L encore, en programmation classique, elle n'est jamais exprime. En programmation par contrat, elle sera exprime dans la dclaration de la fonction, par exemple :
int carre(x) ensure return >= 0

Ce que cette postcondition dit, c'est que la valeur de retour de carre est positive. Ce qui tait implicite, que le dveloppeur devait deviner, est dsormais exprim, noir sur blanc, et garanti. Cela fait dsormais partie du contrat Comment dfinir une bonne postcondition ? Les postconditions ont un gros problme par rapport aux prconditions. On ne sait pas exactement comment s'arrter, car l'ensemble des choses que l'on peut garantir est virtuellement infini. Ainsi, on peut trs bien crire :
int carre(x) ensure return >=0 ensure "la rponse la question sur la vie, l'univers et le reste est 42"

Aussi, pour qu'une postcondition soit utile, il faut qu'elle rponde au moins l'une des caractristiques suivantes : Elle porte sur la valeur de retour de la fonction. Elle porte sur l'un des paramtres de la fonction (pass par rfrence, par exemple). Elle apporte une information utile.

Le dernier point est le plus sujet discussion. En reprenant nos deux exemples, et seulement en lisant les contrats, on sait qu'on a le droit d'crire :
double x = racine_carree(carre(-3.4));

En effet, la postcondition de carre vrifie la prcondition de racine_carree , aussi, grce mes contrats, j'ai la garantie que mon code va fonctionner. On peut donc dire que ma postcondition m'est utile. De la mme manire, la mthode push_back de la classe std::vector se dclare de la sorte :
std::vector::push_back(T) ensure size() == pre.size() + 1

Ou, autrement dit, on garantit qu'aprs push_back, la taille de mon vecteur a augment de 1, et donc, qu'on peut appeler pop_back() sans vrifier que la taille est suprieure zro.

2.2.1 - Postconditions et exceptions

-5Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

Dfinir des postconditions impose de les garantir. C'est quelque chose qui n'est parfois pas possible. Par exemple, une mthode open sur un objet fichier a comme postcondition que le fichier est ouvert. Toutefois, cela serait prsupposer que l'opration russit toujours, or, elle peut chouer, pour de multiples raisons. Doit-on renoncer au contrat pour autant ? La rponse est bien videmment non. Les langages modernes disposent d'un mcanisme d'exception, qui peut tre utilis pour notifier l'appelant que l'appel n'a pas t en mesure de respecter son contrat. On ne vrifiera donc pas les postconditions en cas d'exception. Cela donne quelque part un guide sur quand utiliser des exceptions. Si une fonction n'est pas en mesure de garantir ses postconditions, alors elle doit lever une exception.

2.3 - Responsabilit du contrat


Maintenant que nous savons dfinir un contrat, reste dterminer qui choit quoi. De manire assez logique, le respect des postconditions ne peut tre que le fait de l'appel, l'appelant n'ayant pas la main dessus. De manire moins vidente, c'est l'appelant qui est responsable des prconditions. En effet, les prconditions sont les conditions ncessaires et suffisantes pour appeler la fonction. De fait, elles doivent tre respectes avant mme de rentrer dans le corps de l'appel. C'est donc l'appelant qu'elles choient. Le contrat est donc bien un engagement bipartite, entre l'appelant et l'appel. L'appelant s'engage sur les prconditions, et l'appel s'engage sur les postconditions. Si les prconditions ne sont pas respectes, il faut corriger le code de l'appelant. Si les postconditions ne sont pas respectes, il faut corriger le code de l'appel.

2.4 - Rupture du contrat


Rapidement se pose la question de la rupture du contrat. Que se passe-t-il si l'une des deux parties ne respecte pas ses engagements ? cela, la programmation par contrat n'impose rien. Ds lors que les deux parties n'ont pas fait leur part du contrat, le programme est faux, et il est inutile d'essayer de se "rattraper aux branches" (sauf, bien sr, si des vies humaines sont en jeu). Il faut avant tout corriger le programme. Ceci s'oppose compltement la programmation dfensive, qui elle, vise rejeter tout paramtre invalide, et renvoyer l'appelant dans les cordes au moyen d'une exception (dont, souvent, il ne saura pas quoi faire). Le comportement lors de la rupture de contrat est un choix, gnralement fait la compilation. Il faut donc le considrer comme un comportement indfini.

2.5 - Neutralit du contrat


Maintenant que vous savez qu'un contrat non vrifi est un comportement indfini, et qu'un programme correct respecte toujours ses contrats, se pose la question de savoir s'il faut tester la validit le contrat. La rponse n'est pas si vidente qu'elle en a l'air. Tester le contrat est utile, car cela permet de changer un comportement indfini en un comportement dfini (par exemple, lever une exception), qui permettra de diagnostiquer rapidement l'erreur. Mais tester le contrat a un cot, qu'il n'est pas ncessaire de payer si le programme est correct. Il est trs important de bien faire la diffrence entre contrat et validation de donnes utilisateur. Un contrat ne sert pas vrifier que les donnes saisies sont correctes, il sert exprimer le fait qu'une fonction ncessite des donnes correctes en entre. La

-6Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

validation de donnes saisies est une tape normale du traitement logiciel, et en aucun cas la programmation par contrat ne vise supprimer cette tape. En revanche, bien utilise, elle permettra d'identifier plus facilement un manquement dans cette tape. De fait, ce qui est souvent fait est de vrifier le respect du contrat lors de la phase de debug, et de ne plus le faire lors de la phase de release. Ce qu'il est indispensable de vrifier, c'est que le comportement du programme est le mme, que cette vrification soit active ou pas. On parle de neutralit du contrat, car celui-ci est totalement neutre du point de vue de l'excution du programme. ce titre, les prconditions et postconditions ne peuvent avoir d'effet de bord.

-7Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

3 - Approche objet
Dans le monde objet, le contrat s'applique aussi. Sur les fonctions membres, il s'applique de la mme manire que sur les fonctions classiques, mais il faut tenir compte du paramtre supplmentaire qu'est le "this" sur lequel est appele la routine. Les prconditions vont donc pouvoir s'appliquer l'tat de l'objet. Par exemple, supposons une classe FileStream, une prcondition pour appeler la mthode "Read" est que le FileStream soit dans l'tat "Ouvert". De mme pour les postconditions. Attention lors de la dfinition de prconditions sur des variables membres, il ne faut pas oublier que les prconditions doivent tre vrifiables par le client. Aussi, il ne faut pas imposer de prconditions sur un tat non expos de l'objet. Pour les postconditions, ce problme n'existe pas, mais on peut s'interroger sur l'intrt d'une telle postcondition pour le client. La notion d'"tat expos" est assez subjective. En effet, une postcondition de "open()" tant que le fichier est ouvert, cet tat est expos mme si la classe ne comporte pas de mthode "is_open()". Personnellement, je prfre m'en tenir tat expos = vrifiable par code, mais ce n'est en aucun cas faux d'tre plus permissif sur cette dfinition, l'essentiel tant que l'appelant soit en mesure de respecter sa part du contrat.

3.1 - Invariants de classe


L'objet ayant un tat, on peut dfinir des proprits sur cet tat, qui sont toujours vrifies. Une sorte de pr/postcondition universelle, respecte par l'ensemble des routines publiques (y compris protges) de l'objet. Ces proprits sont appeles invariants de classe. Les invariants sont valides en entre et en sortie de chaque routine, toutefois, il est tout fait possible de rompre ces invariants, le temps d'un traitement. Ce n'est pas un problme du moment que, une fois sorti de la routine, l'invariant est de nouveau respect. Un corollaire intressant de cela est qu'en multithread, si une routine doit violer un invariant, alors elle ne peut le faire qu'au sein d'une section protge, et tous les autres appels de fonction doivent attendre que cet invariant soit nouveau respect pour, soit commencer, soit terminer. Pour prendre l'exemple de la classe std::vector, un invariant de cette classe est que la taille du vecteur est toujours suprieure ou gale zro. L'invariant n'est pas ncessairement une proprit visible depuis l'extrieur, en effet, c'est une garantie, le client n'a pas d'influence dessus. Toutefois, comme pour les postconditions, l'invariant doit donner une information utile au client.

3.1.1 - Invariants et exceptions


On l'a vu, en cas d'exception, les postconditions ne sont pas respectes. La question est plus dlicate rgler pour les invariants. En effet, la mthode at() sur std::vector est susceptible de nous renvoyer une exception, mais l'objet reste utilisable, et ses invariants sont respects. contrario, une socket dont l'invariant est "Ouvert" qui se retrouverait soudainement ferme aprs un send() du fait d'une erreur rseau n'est plus utilisable, et ses invariants sont rompus.

-8Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

La meilleure approche me semble donc de laisser le choix, et d'informer le client. On aura donc des exceptions qui cassent les invariants de l'objet, d'autres non. Ceci peut tre indiqu au client en typant les exceptions qui cassent les invariants, en les faisant par exemple driver d'une mme classe breaking_exception. Ainsi, si une opration casse l'invariant, l'utilisateur en est inform, et l'erreur de programmation (violation d'invariant) se produira si l'utilisateur rutilise l'objet (appelle une routine de l'objet). Il faudra toutefois prendre en compte que la violation de l'invariant est due non un bug du code de l'objet, mais l'utilisation d'un objet "cass". Bien que l'invariant devrait tre respect en entre du destructeur, dans l'implmentation, nous ne le vrifierons pas. En effet, le destructeur est tout de mme appel pour des objets "casss", et nous ne voulons certainement pas que ce soit considr comme une erreur de programmation (mme si l'objet est cass, on sait le plus souvent dans quelle mesure et comment le librer proprement). Une autre approche consiste dire que les objets doivent toujours respecter leurs invariants, et que s'ils ne sont pas en mesure de le faire, c'est que la conception est mauvaise. Cette approche a l'avantage que toute violation d'invariant est due un bug dans la classe elle-mme, ce qui facilite le diagnostic. Cet avantage compense la perte de flexibilit dans la dfinition des invariants.

3.2 - Hritage du contrat


Avant d'aller plus loin, il faut dfinir de quel hritage on parle. La programmation par contrat s'intresse avant tout l'hritage polymorphique. Pour faire un programme correct et rutilisable, il est essentiel d'avoir un certain nombre de garanties. Dans le cas de l'hritage polymorphique, la garantie que l'on veut est que si on cre une classe B qui drive de A, alors, tout le code dj crit, qui utilise des A, est valide pour un B. Cette proprit est aussi connue sous le nom de Principe de Substitution de Liskov (LSP en anglais). Pour se convaincre de l'utilit du LSP, on peut raisonner par l'absurde. Si le LSP n'est pas respect, alors, des fonctions que j'ai crites pour A, ne fonctionneront pas pour B. C'est--dire que je vais devoir rcrire du code spcifique pour grer ces B. De plus, je vais devoir connatre le type dynamique pour diffrencier les cas o j'ai B des cas o j'ai A. J'ai donc perdu tout le bnfice de mon hritage polymorphique. Afin d'avoir cette garantie, il existe un outil formidable qui est le contrat. Si B respecte le mme contrat que A, alors, B est substituable A. Mais on perd beaucoup en flexibilit. Heureusement, c'est un peu plus souple : Les prconditions peuvent en effet tre tendues. Le minimum est que la routine de B fonctionne pour l'ensemble des valeurs admises par celle de A, mais rien ne l'empche de faire plus. Les postconditions peuvent tre renforces. L aussi, le minimum est de garantir la mme chose que ce que faisait A, mais il est toujours possible de garantir plus. Les invariants doivent tre respects, mais il est possible d'en dfinir de nouveaux, condition de s'assurer que les routines dfinies dans A, susceptibles d'tre appeles, ne les violent pas. En gnral, on dfinira de nouveaux invariants sur des membres n'existant pas dans A. Les plus perspicaces d'entre vous se diront qu'il y a un problme cette approche, savoir que l'hritage polymorphique impose une restriction supplmentaire, "this est un B", plus restrictive que "this est un A". Nous verrons plus loin pourquoi ce n'est pas un problme.

-9Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

4 - La gnricit et le contrat
Lorsqu'on fait de la programmation gnrique, on part toujours d'un certain nombre d'hypothses sur le paramtre gnrique (T). Mme une classe d'ordre aussi gnral que std::vector a besoin d'une proprit sur T pour fonctionner, savoir, "T est copiable". Bien malheureusement, cette proprit n'est exprime nulle-part, ou alors, au fin fond d'une documentation qui ne sera pas lue. Le problme, tel qu'exprim, peut se rsumer "comment garantir que le code, que j'ai crit pour T, soit valable pour tout T pour lequel je l'utilise ?". a ne vous rappelle rien ? C'est un problme de substitution, on va donc utiliser nouveau cet outil formidable qu'est le contrat, cette fois ci sur T. Le contrat sur T peut tre exprim de plusieurs manires : Une pratique courante en programmation objet "classique" est d'imposer une classe de base pour T. Le contrat est exprim dans la classe de base. Si l'on dispose d'un mcanisme de typage structurel, alors il est thoriquement possible de dfinir le contrat en tant que structure (comprendre, structure et contrat) que doit respecter le type. Cela est rapprocher de ce qui a t fait pour les concepts, qui ont malheureusement t repousss l'aprs C++0x. En effet, cela est trs loin d'tre trivial, et pose de nombreux problmes encore non-rsolus. On notera que dans le deuxime cas, on va avoir des proprits vrifiables par le compilateur (par exemple, T est copiable), et d'autres non (par exemple, T.resize(int newsize) qui requiert que newsize soit >= 0.

- 10 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

5 - Application au C++ 5.1 - Les contrats existants en C++


C++, comme la plupart des langages de programmation, n'inclut aucun mcanisme pour dfinir et grer des contrats. Cela dit, quand on y regarde de plus prs, il existe dj des "contrats" en C++.

5.1.1 - Le typage, vu du point de vue contrat


Lorsque je dclare
int f(int a, double b);

J'exprime en ralit trois choses qui sont trs fortement apparentes un contrat : a est un entier (prcondition pour l'appel de f). b est un rel en double prcision (idem). la valeur de retour est un entier (post-condition).

Si l'on travaillait avec un langage faiblement typ, on devrait exprimer ce contrat. Comme on travaille en C++, ce contrat est non-seulement dj exprim, mais il est en plus vrifi par le compilateur. C'est malheureusement presque une exception. Le type des paramtres que prend une fonction est une prcondition. Son type de retour est une postcondition.

5.1.2 - Le cas de const


const est lui aussi une information qui s'exprime trs bien en terme de contrats. Elle signifie "je ne modifie pas". Ainsi, partir de :
int A::f(std::string const & chaine) const;

Nous avons deux informations importantes lies l'usage de const : chaine ne sera pas modifie (bien que paramtre d'entre/sortie) l'objet (de type A) ne sera pas modifi

Ce sont donc deux postconditions, qui sont exprimes par l'usage de const. L encore, ces contrats sont garantis par le compilateur (modulo dtournement volontaire au moyen de volatile ou const_cast).

5.2 - Dfinir un contrat, utilisation du prprocesseur


Il est possible, l'aide du prprocesseur, de dfinir des macros qui simplifieront l'criture des contrats. L'ide tant que le code doit tre le plus lisible possible, toute la complexit sera laiss l'criture de la macro. Nous allons donc pour l'instant nous contenter d'crire :

- 11 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

double sqrt(double d) { REQUIRE(d >= 0, "d positif"); ENSURE(retour >= 0,"Valeur de retour positive") } class MaClasse { public: int size(); BEGIN_INVARIANT_BLOCK(MaClasse) INVARIANT(size() >= 0, "La taille est positive"); END_INVARIANT_BLOCK }

Il faut remarquer deux choses. Nous avons exprim la condition que doit respecter l'appelant, et ce la fois sous la forme du test qui sera effectu par le programme, et sous une forme textuelle dcrivant la prcondition. Il est important de bien assimiler ceci pour comprendre les messages d'erreurs qui seront remonts en cas de violation de contrat. Le texte mis sera utilis pour dcrire l'exception. Il peut paratre trange de passer "MaClasse" comme paramtre BEGIN_INVARIANT_BLOCK, alors qu'on est dj dans le corps de la classe. D'un point de vue C++, c'est strictement inutile. En revanche, cela se rvle ncessaire pour gnrer la documentation avec doxygen. La gestion des invariants est malheureusement un peu plus problmatique. Certes, on a bien dfini nos invariants de classe, mais ils ne seront pas vrifis. On va devoir rajouter en entre et en sortie de chaque fonction membre un appel une macro CHECK_INVARIANTS.

double MaClasse::f(double d) { CHECK_INVARIANTS(); REQUIRE(d >= 0, "d positif"); ENSURE(retour >= 0,"Valeur de retour positive") CHECK_INVARIANTS(); }

5.2.1 - Comment grer une violation de contrat ?


Avant toute chose, il faut bien comprendre qu'il n'y a pas une bonne manire de grer une violation de contrat, mais bien plusieurs. La seule chose qu'il ne faut pas faire, c'est de vouloir rcuprer une violation de contrat dans le code de l'appelant. En effet, le comportement du code changerait si la vrification de contrat est active ou pas, ce qui n'est absolument pas le but de la programmation par contrats. La mthode la plus simple consiste appeler abort(). En cas de violation de contrat, le programme n'est pas correct, il faut donc le corriger, et appeler abort() semble une bonne option. Une autre mthode, similaire, consiste utiliser assert(), si elle est disponible. Ceci a l'avantage qu'on pourra avoir un peu plus d'informations sur la mthode qui a chou. L'autre mthode couramment employe est de lever une exception. Cette mthode prsente certains avantages : Les informations sur la rupture de contrat peuvent tre stockes dans l'objet exception lev.

- 12 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

L'exception, du fait qu'elle remonte la pile d'appels, peut s'enrichir d'informations de contexte permettant d'identifier plus prcisment le problme.

Une erreur qu'on voit malheureusement est qu'il est tentant de vouloir se raccrocher aux branches, en grant l'exception renvoye. On passe alors de la programmation par contrat de la programmation dfensive. Dans le cadre de tests automatiss, il est en revanche trs intressant de pouvoir se raccrocher aux branches, et donc de rcuprer l'exception renvoye. En effet, on veut continuer l'excution des tests mme si l'un choue. La gestion par exception est la seule permettre facilement l'intgration dans un framework de tests unitaires. Le fait d'avoir plusieurs stratgies possibles impose d'en choisir une. On voit ici l'avantage d'tre pass par une macro pour dfinir nos contrats. Il nous sera alors possible de changer de stratgie de gestion de rupture de contrat simplement l'aide d'une option de compilation.

5.2.2 - Pourquoi dsactiver la vrification du contrat ?


On l'a dit, la vrification du contrat a une incidence sur la vitesse d'excution du code qui peut ne pas tre ngligeable. Pour prendre un cas extrme :

int factorielle(int n) { REQUIRE(n >= 0, "n positif"); if(n > 1) return n * factorielle(n-1) else return 1; }

La diffrence de temps d'excution entre le code avec vrification de contrat et celui sans est de plus 20% sur ma machine. Dans de nombreux cas, ceci ne posera pas de problme, mais dans d'autres cela peut tre critique. On va donc vouloir dsactiver la vrification du contrat lorsque celle-ci a un impact sur les performances, et une fois qu'on a vu que le code est correct. Comme "ne pas grer les violations de contrat" est finalement une stratgie de gestion des violations de contrat, on va grer ceci dans les macros utilises pour dfinir les contrats.

5.2.3 - Macros utiles


Petit rappel du besoin : on veut pouvoir changer de stratgie la compilation, y compris ne pas vrifier. on veut pouvoir activer indpendamment la vrification des prcondtions, postconditions et des invariants de classe. On va donc utiliser les options de compilations suivantes : Pour la stratgie de compilation, les constantes suivantes : CONTRACTS_NO_CHECK : pas de vrification de contrats CONTRACTS_USE_ABORT : utiliser abort() en cas de violation de contrat CONTRACTS_USE_ASSERT : utiliser assert() en cas de violation de contrat CONTRACTS_USE_EXCEPTION : lever une exception en cas de violation de contrat Pour dsactiver unitairement certaines vrifications : CONTRACTS_NO_PRECONDITION : pas de vrification des prconditions CONTRACTS_NO_POSTCONDITION : pas de vrification des postconditions

- 13 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

CONTRACTS_NO_INVARIANT : pas de vrification des invariants

#ifndef CONTRACTS_H #define CONTRACTS_H #ifdef CONTRACTS_USE_ABORT #include <stdlib.h> #define REQUIRE(cond,texte) if(!(cond)) abort() #define ENSURE(cond, texte) if(!(cond)) abort() #define INVARIANT(cond, texte) if(!(cond)) abort() #define BEGIN_INVARIANT_BLOCK(className) void _contract_check_invariants() { #define END_INVARIANT_BLOCK } #define CHECK_INVARIANTS _contract_check_invariants #endif #ifdef CONTRACTS_USE_ASSERT #include <assert.h> #define REQUIRE(cond, texte) assert(cond) #define ENSURE(cond, texte) assert(cond) #define INVARIANT(cond, texte) assert(cond) #define BEGIN_INVARIANT_BLOCK(className) void _contract_check_invariants() { #define END_INVARIANT_BLOCK } #define CHECK_INVARIANTS _contract_check_invariants #endif #ifdef CONTRACTS_USE_EXCEPTION #include <string> #include <sstream> class contract_violation_exception : public std::exception { std::string message; public: contract_violation_exception(char const * condition, char const * file, int line) { std::ostringstream str; str << file << " : ligne " << line << " - Violation du contrat : " << condition; message = str.str(); }; char const * what() const throw() { return message.c_str(); }; ~contract_violation_exception() throw() {}; }; #define REQUIRE(cond, texte) if(!(cond)) throw contract_violation_exception(texte, __FILE__, __LINE__) #define ENSURE(cond, texte) if(!(cond)) throw contract_violation_exception(texte, __FILE__, __LINE__) #define INVARIANT(cond, texte) if(!(cond)) throw contract_violation_exception(texte, __FILE__, __LINE__) #define BEGIN_INVARIANT_BLOCK(className) void _contract_check_invariants() { #define END_INVARIANT_BLOCK } #define CHECK_INVARIANTS _contract_check_invariants #endif #ifdef CONTRACTS_NO_CHECK #define REQUIRE(cond, texte) #define ENSURE(cond, texte) #define INVARIANT(cond, texte) #define BEGIN_INVARIANT_BLOCK(className) #define END_INVARIANT_BLOCK #define CHECK_INVARIANTS() #endif #ifdef CONTRACTS_NO_PRECONDITION #undef REQUIRE

- 14 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

#define REQUIRE(cond, texte) #endif #ifdef CONTRACTS_NO_POSTCONDITION #undef ENSURE #define ENSURE(cond, texte) #endif #ifdef CONTRACTS_NO_INVARIANT #undef INVARIANT #undef BEGIN_INVARIANT_BLOCK #undef END_INVARIANT_BLOCK #undef CHECK_INVARIANTS #define CHECK_INVARIANTS() #define INVARIANT(cond, text) #define BEGIN_INVARIANT_BLOCK(className) #define END_INVARIANT_BLOCK #endif #endif

Cette approche introduit malheureusement une limitation importante : il n'est plus possible d'utiliser une instruction return en milieu de fonction, celle-ci court-circuitant la vrification des postconditions. On verra pourquoi dans les faits, ce n'est pas si gnant que a. On pourrait tre tent d'utiliser une approche RAII pour la gestion des invariants. Cela permettrait de n'crire qu'une fois la vrification des invariants au sein d'une fonction, et permettrait les instructions returns multiples. De mme, pour les postconditions, on serait tent de les mettre au dbut pour autoriser les return en milieu de fonction. Cette approche se heurte toutefois deux problmes. Le premier est que les postconditions ne sont pas garanties en cas d'exception, il faudrait donc ne pas excuter leur vrification dans ce cas, ce qui n'est pas trivial du tout avec une approche base sur le destructeur. L'autre est que cela empche la gestion des violations de contrat par exception, puisqu'il est trs fortement dconseill de lancer une exception dans un destructeur.

5.3 - Dfinir un contrat hrit grce l'interface non virtuelle


Le pattern NVI, pour "Non Virtual Interface", consiste dfinir une interface non virtuelle la classe, laissant les appels virtuels des mthodes protges. La vrification du contrat s'adapte extrmement bien ce pattern : Du fait que l'interface soit non-virtuelle, le contrat sera exprim et vrifi aussi pour les classes hrites. Les appels internes une classe peuvent ignorer la vrification du contrat (en appelant la mthode protge plutt que l'interface publique). Ceci permet d'activer la vrification des contrats mme sur du code release, faible cot. Ceci rgle en grande partie la limitation de notre implmentation, qui nous empchait les instructions return en milieu de fonction. Comme l'interface n'est plus qu'un proxy qui se charge de vrifier le contrat et d'appeler une fonction membre protge, l'criture n'en est que trs peu alourdie.

class A { private: virtual int do_f(int n); public: int f(int n) }; int A::do_f(int n) // cette fonction illustre le return multiple { // on ne vrifie plus le contrat dans cette fonction

- 15 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

if(n > 0) return 1/n; else return 2/n; // la prcondition n != 0 est exprime dans l'interface publique } int A::f(int n) { REQUIRE(n != 0, "n diffrent de 0"); double ret = do_f(n); ENSURE(ret >= -2 && ret < 1, "Valeur de retour dans l'intervalle [-2, 1]"); return ret; }

Cette mthode ne nous empche pas de redfinir le contrat. En effet, dans B, on peut redfinir f, et on aura alors le comportement suivant : Si le client croit avoir affaire un A, c'est le contrat dfini dans A qui sera vrifi. Comme celui dfini dans B est forcment plus permissif du point de vue de l'appelant, a ne saurait tre un problme Si le client sait qu'il a affaire un B, c'est le contrat dfini dans B qui sera vrifi.

5.4 - Documenter avec Doxygen


Doxygen fournit tout ce qu'il faut pour documenter de manire claire les prconditions, postconditions et invariants de classe. Cela se fait au moyen des balises \pre, \post, \inv. Toutefois, ces informations figurant dj dans le code, il serait la fois redondant et source d'erreurs de les rpter. Heureusement, Doxygen fournit la possibilit de dfinir des macros pour la gnration de documentation. Nous allons donc rutiliser nos macros C++, mais pour cette fois leur faire gnrer la documentation. Nous allons donc modifier le fichier Doxyfile afin qu'il interprte correctement les macros que nous avons crites :

PREDEFINED = "REQUIRE(a,b)=///\pre b" \ "ENSURE(a,b)=///\post b" \ "BEGIN_INVARIANT_BLOCK= " \ "BEGIN_INVARIANT_BLOCK(a)= ///\class a " \ "END_INVARIANT_BLOCK= " \ "INVARIANT(a,b)=///\invariant b"

La documentation de nos fonctions, nos invariants de classe, etc, est alors automatiquement prise en charge par doxygen.

- 16 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

6 - Pour aller un peu plus loin 6.1 - Notion de covariance, et respect du contrat
Dfinition : On appelle covariance le fait de, pour une fonction membre d'un objet B driv de A, remplacer dans sa signature le type A par le type B. On appelle contravariance le fait de faire l'inverse. La covariance des arguments a principalement t introduite et popularise afin de rgler de manire lgante une problmatique simple noncer : comparer deux objets et savoir s'ils sont gaux. Puisque pour comparer deux objets, il faut connatre leur type dynamique (en effet, deux B peuvent tre gaux du point de vue d'un A, mais pas d'un B).
struct A { int x; bool operator==(A const &amp; other) const { return x == other.x; }; }; struct B : public A { int y; bool operator==(B const &amp; other) const { return x == other.x &amp;&amp; y == other.y; } } int main() { B b1, b2; b1.x = b2.x = 1; b1.y = 1; b2.y = 2; std::cout << (A) b1 == (A) b2 << " " << b1 == b2 << std::endl; }

Le code prcdent montre bien la limitation d'une approche base sur l'oprateur d'galit. b1 et b2 sont gaux du point de vue d'un A, mais pas d'un B. L'ide est donc de pouvoir crire une mthode polymorphique plutt qu'un oprateur. Ainsi, le type rel serait utilis pour la comparaison.

struct A { int x; virtual bool IsEqual(A const &amp; other) const; } struct B : public A { int x; virtual bool IsEqual(B const &amp; other) const; }

Bien sr, on veut comparer un B avec d'autres B, pas avec des A, puisqu'on sait dj que si other est un A, il est diffrent. D'o l'ide de permettre de redfinir le type de l'argument. On se heurte toutefois un problme. On l'a vu, le type des arguments est un contrat, une prcondition. Or, restreindre un sous-type est une restriction de cette prcondition. On a ici un exemple flagrant de violation de contrat. Et dans les faits, cela se traduit par des erreurs l'excution, indtectables, ni par le compilateur, ni mme par les contrats intgrs au langage en Eiffel.

- 17 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

Bertrand Meyer tait parfaitement conscient de ce fait lorsqu'il a crit Eiffel. Il pensait alors que la puissance apporte par les paramtres covariants valait largement ces quelques erreurs l'excution, qu'il pensait d'ailleurs pouvoir relativement facilement dtecter la compilation. L'histoire a montr que l'exercice tait beaucoup plus dur qu'il n'y paraissait au dbut, puisqu'on ne sait toujours pas faire. Pourtant, cela fonctionne pour this... En effet, ce n'est pas parce qu'on crit b->func() que b n'en est pas moins un paramtre de func. Il faut alors se rappeler que nous sommes en single-dispatch, et donc que ce paramtre est un petit peu particulier. En effet, il dtermine quelle fonction va tre appele l'excution. C'est dire que quelque soit l'endroit dans le code, la fonction excute sera toujours celle correspondant au type rel de b. De fait, l'appel est sr. Des rflexions prcdentes, on peut dduire qu'un systme de type autorisant les paramtres covariants en multiple-dispatch serait "sr". Les paramtres contravariants, quant eux, ne posent aucun problme du point de vue des contrats. Leur utilit reste toutefois dmontrer. La valeur de retour, elle, est une postcondition. Il est donc tout fait sr d'utiliser des valeurs de retour covariantes, puisque cela consiste simplement en une restriction des postconditions, ce qui est tout fait autoris. C'est pour cela que les valeurs de retour covariantes sont autorises en C++. C++ tant single-dispatch, il n'autorise pas les paramtres covariants.

6.2 - Rfrences
Conception et programmation orientes objet - Bertrand Meyer Le pattern NVI Une proposition visant ajouter le support des contrats dans le langage Enforcing Code Feature Requirements in C++ - Scott Meyers Contract Programming 101 - Matthew Wilson

- 18 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

7 - Remerciements
Je tiens remercier l'ensemble de l'quipe C++ de developpez.com, et tout particulirement 3DArchi, Alp. Luc Hermitte et SpiceGuid pour leurs encouragements et la pertinence de leurs remarques tout au long de l'criture de cet article. Merci aussi Furr et Wachter pour leur attentive relecture.

- 19 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Programmation par contrat, application en C++ par Julien Blanc

- 20 Ce document est issu de http://www.developpez.com et reste la proprit exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise l'obtention pralable de l'autorisation de l'auteur.
http://julien-blanc.developpez.com/articles/cpp/Programmation_par_contrat_cplusplus

Vous aimerez peut-être aussi