OVH Cloud OVH Cloud

Programmation par contrat en C++

7 réponses
Avatar
Stephane Wirtel
Bonjour,

Je viens de lire un article assez intéressant concernant la programmation
par contrat.

Que pensez-vous de cette méthode ?

Peut-on facilement la coupler avec des tests unitaires ?

Voici l'article en question :
http://www.zdnet.fr/builder/programmation/java_c_cplusplus/0,39020934,39176810,00.htm

Bien à vous,

Stéphane

7 réponses

Avatar
Jean-Marc Bourguet
Stephane Wirtel writes:

Je viens de lire un article assez intéressant concernant
la programmation par contrat.

Que pensez-vous de cette méthode ?


C'est une technique éprouvée (je n'ai pas lu l'article).

Peut-on facilement la coupler avec des tests unitaires ?


Je ne vois aucune difficulté. Elle offre même des aspects
intéressants dans ce cadre en documentant ce qu'il faut
tester précisément.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Loïc Joly
Bonjour,

Je viens de lire un article assez intéressant concernant la programmation
par contrat.

Que pensez-vous de cette méthode ?

Peut-on facilement la coupler avec des tests unitaires ?

Voici l'article en question :
http://www.zdnet.fr/builder/programmation/java_c_cplusplus/0,39020934,39176810,00.htm


J'ai parcouru rapidement l'article, et je n'y ai pas vu certains points
essentiels, surtout en présence de fonctions virtuelles (qui est AMA un
point où il faut faire particulièrement attention), avec une fonction
publique non virtuelle qui implémente la vérification du contrat, et une
fonction virtuelle privée appelée par la première contenant le vrai code.

--
Loïc

Avatar
James Kanze
Stephane Wirtel wrote:

Je viens de lire un article assez intéressant concernant la
programmation par contrat.


Que pensez-vous de cette méthode ?


Que malgré le titre, l'article passe à côté beaucoup de la
programmation par contrat. C'est une méthologie qui a fait ces
preuves, et même s'il y a des classes où il ne s'applique pas,
dans l'ensemble, je ne m'en passerais pas volentairement.

Peut-on facilement la coupler avec des tests unitaires ?


Tout à fait.

Voici l'article en question :

http://www.zdnet.fr/builder/programmation/java_c_cplusplus/0,39020934,39176810,00.htm


L'article ne parle pas vraiment du programmation par contrat,
mais simplement de l'utilisation des assertions. Chose qui était
courant déjà en C, il y a vingt ans. On a avancé beaucoup
depuis.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 pl. Pierre Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

Avatar
Stephane Wirtel
Peux-tu m'en dire plus concernant les évolutions et méthodes véritables de
la programmation par contrat ? Par exemple, un document, une URL, ou
quelque chose de comparable.

Je suis entrain de développer une API pour une application et j'aimerais
essayer d'appliquer de bonnes méthodes de développement afin de me
faciliter le debug.

Merci,


Stéphane
Avatar
kanze
Stephane Wirtel wrote:

Peux-tu m'en dire plus concernant les évolutions et méthodes
véritables de la programmation par contrat ? Par exemple, un
document, une URL, ou quelque chose de comparable.


Malheureusement, je n'en connais pas. (Ça fait un moment que je
me dis que je dois écrire un article là-dessus, mais le temps me
manque toujours.) Ça a été beaucoup discuté à diverses époques
dans comp.lang.c++.moderated, mais à part ça, je ne connais pas
d'informations disponibles ; les idées semblent passaient plutôt
de bouche à l'oreille.

Le principe de base est assez simple : quand une interface veut
imposer un contrat, elle rend les fonctiions virtuelles privées
(ou éventuellement protégées), et elle fournit des fonctions
publiques non virtuelles qui les appellent, en encapsulant les
appels aux fonctions virtuelles dans des vérifications du
contrat.

Typiquement, on définit des macros pour ces vérifications, de
façon à ce que la fonction publique ressemble à :

void
Interface::f( int i )
{
PRECONDITION( i >= 0 && i < 10 ) ;
doF( i ) ;
POSTCONDITION( state() == initialized ) ;
}

Les macros ressemble un peu au macro assert, mais on pourrait
imaginer des messages plus parlant.

Typiquement, on a des macros au moins pour PRECONDITION,
POSTCONDITION et INVARIANT. Je verais bien aussi quelque chose
du genre

#define USEOLD() boost::scoped_ptr< typeof( *this ) > old = clone
()

pour les cas où on veut utiliser l'ancienne valeur dans les
post-conditions.

Je verrais aussi éventuellement différents niveaux, qui peuvent
être supprimés séparément. Idéalement, on laisse les
vérifications dans le code livré -- c'est même là où elles sont
le plus utile. Mais certaines vérifications peuvent avoir un
effet négatif important sur le temps d'exécution, quelque chose
comme :

PRECONDITION( isSorted( parametreTableau ) ) ;

où parametreTableau est un tableau de plusieurs millions
d'éléments. On aimerait pouvoir supprimer ces vérifications-là
sans en supprimer les autres.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Laurent Deniau
wrote:
Stephane Wirtel wrote:


Peux-tu m'en dire plus concernant les évolutions et méthodes
véritables de la programmation par contrat ? Par exemple, un
document, une URL, ou quelque chose de comparable.



Malheureusement, je n'en connais pas. (Ça fait un moment que je me
dis que je dois écrire un article là-dessus, mais le temps me manque
toujours.) Ça a été beaucoup discuté à diverses époques dans
comp.lang.c++.moderated, mais à part ça, je ne connais pas
d'informations disponibles ; les idées semblent passaient plutôt de
bouche à l'oreille.

Le principe de base est assez simple : quand une interface veut
imposer un contrat, elle rend les fonctiions virtuelles privées (ou
éventuellement protégées), et elle fournit des fonctions publiques
non virtuelles qui les appellent, en encapsulant les appels aux
fonctions virtuelles dans des vérifications du contrat.


On a deja discuste de ca il y a quelques annees (mois?), mais il faut
tout de meme mentionner que ce principe peut mener a des bugs difficiles
a trouver notament parce qu'on se croit protege. Le mot clef sur lequel
tu passes un peu vite c'est INTERFACE. Cette technique fonctionne si
les pre/post conditions sont les memes pour toutes les classes derivees
ce qui est une contrainte tres forte et relativement rare. Et pour ce
qui est des invariants c'est encore pire puisqu'il s'applique
exclusivement a this.

class A { // interface
public:
void f(int i) {
f_precond(i); f_(i); f_postcond(); _invariant();
}

protected:
virtual void f_(int i) = 0;

private:
void f_precond(int i) { ... }
void f_postcond() { ... }
void _invariant() { ... }
}

class B : public A {
public:
void f(int i) { // probleme, pas possible
f_precond(i); f_(i); f_postcond(); _invariant();
}

protected:
virtual void f_(int i) { ... }

private:
void f_precond(int i) { ... }
void f_postcond() { ... }
void _invariant() { ... }
}

si B::f_ est definie, ce qui est generalement le cas, et si les
conditions (notament les invariants) changent dans B::f_, alors il faut
redefinir aussi B::f. Si ensuite on utilise A comme une interface, qui
est effectivement le cas le plus courant, sur une variete d'intances de
classes derivees de A (ex: B), ca ne remplit pas correctement le contrat
alors que l'on serait tente de le croire.

En revanche, l'inverse ne m'a jamais pose de probleme (il me semble que
tu en avait trouves mais je ne me souvient plus lesquels ;-) ), sauf du
point de vue de l'efficacite (mais tu as deja souligne ce probleme).

class A {
public:
virtual void f(int i) = 0;

protected:
void f_precond(int i) { ... }
void f_postcond() { ... }
void _invariant() { ... }
}

class B : public A {
public:
virtual void f(int i) {
f_precond(i); ... f_postcond(); _invariant();
}

protected:
void f_precond(int i) { A::f_precond(i); ... }
void f_postcond() { ... A::f_postcond(); }
void _invariant() { ... A::_invariant(); }
}

Si B ne redefinit pas les contrats, ce sont ceux de A qui sont
automatiquement utilises. Si B redefinit les contrats, elle peut aussi
se baser sur les contrats de A. Tout le couplage se fait de B vers A au
lieu de A vers B, ce qui est bien mieux.

Enfin dans le domaine, on peut mentionner R++, une surcouche de C++.
http://www.research.att.com/sw/tools/r++/

a+, ld.


Avatar
kanze
Laurent Deniau wrote:
wrote:
Stephane Wirtel wrote:

Peux-tu m'en dire plus concernant les évolutions et
méthodes véritables de la programmation par contrat ? Par
exemple, un document, une URL, ou quelque chose de
comparable.


Malheureusement, je n'en connais pas. (Ça fait un moment que
je me dis que je dois écrire un article là-dessus, mais le
temps me manque toujours.) Ça a été beaucoup discuté à
diverses époques dans comp.lang.c++.moderated, mais à part
ça, je ne connais pas d'informations disponibles ; les idées
semblent passaient plutôt de bouche à l'oreille.

Le principe de base est assez simple : quand une interface
veut imposer un contrat, elle rend les fonctiions virtuelles
privées (ou éventuellement protégées), et elle fournit des
fonctions publiques non virtuelles qui les appellent, en
encapsulant les appels aux fonctions virtuelles dans des
vérifications du contrat.


On a deja discuste de ca il y a quelques annees (mois?), mais
il faut tout de meme mentionner que ce principe peut mener a
des bugs difficiles a trouver notament parce qu'on se croit
protege. Le mot clef sur lequel tu passes un peu vite c'est
INTERFACE.


C'est un mot dont je me méfie un peu ; trop de monde l'associe
au mot clé de Java. Alors que si on veut des interfaces au sens
de la programmation par contrat, on ne peut pas utiliser les
interfaces de Java.

Cette technique fonctionne si les pre/post conditions sont les
memes pour toutes les classes derivees ce qui est une
contrainte tres forte et relativement rare.


C'est cependant la définition même qu'impose la programmation
par contrat. On ne permet que certaines modifications -- des
préconditions plus libérales, et des post-conditions plus
restrictives. Et en fait, je trouve que même là, c'est une
erreur de conception.

Si j'utilise une interface, en tant que client, je m'engage à
cette interface, et à rien d'autre. Si j'ai besoin de plus
(disons des préconditions plus libérales), et que je sais qu'en
fait, mon objet les supporte, je peux le dire, au moyen d'un
dynamic_cast. Sinon, j'ai violé les termes du contrat, et si je
permets la violation des termes du contrat, ce n'est pas la
peine de faire de la programmation par contrat.

Et pour ce qui est des invariants c'est encore pire puisqu'il
s'applique exclusivement a this.


Il y a en fait deux types d'invariants : ceux visibles du
client, et sur lesquels le client peut compter, et ceux
nécessaire à l'implémentation, pour qu'elle fonctionne
correctement. Dans l'interface (la classe de base), on ne peut
effectivement vérifier que ce premier type.

J'avoue que le deuxième type sont bien plus fréquent. Mais dans
la mésure qu'ils ne paraissent pas au niveau de l'interface (le
contrat) que voit le client, est-ce qu'on peut les inclure sous
la vocable « programmation par contrat » ? (Qu'on les y inclut
ou non, ils sont réelement importants.)

class A { // interface
public:
void f(int i) {
f_precond(i); f_(i); f_postcond(); _invariant();
}

protected:
virtual void f_(int i) = 0;

private:
void f_precond(int i) { ... }
void f_postcond() { ... }
void _invariant() { ... }
}

class B : public A {
public:
void f(int i) { // probleme, pas possible
f_precond(i); f_(i); f_postcond(); _invariant();
}

protected:
virtual void f_(int i) { ... }

private:
void f_precond(int i) { ... }
void f_postcond() { ... }
void _invariant() { ... }
}

si B::f_ est definie, ce qui est generalement le cas, et si
les conditions (notament les invariants) changent dans B::f_,
alors il faut redefinir aussi B::f.


Seulement dans la mésure où on veut offir un nouveau contrat.
Qu'on veut s'engager sur quelque chose de plus. Souvent, les
nouveaux invariants restent plutôt un détail de
l'implémentation -- si le client respecte le contrat de base
(enforcé par l'interface), on ne pourrait pas, en principe,
violer les invariants. À moins qu'il y a une erreur dans notre
code. Mais de tels invariants ne font pas partie du contrat
vis-à-vis du client. Si je change l'implémentation, j'ai le
droit de les modifier.

La distinction importante ici, c'est celle entre les choses sur
laquelle je m'engage, et les choses qui sont nécessaire
internalement pour l'implémentation actuelle.

Si ensuite on utilise A comme une interface, qui est
effectivement le cas le plus courant, sur une variete
d'intances de classes derivees de A (ex: B), ca ne remplit pas
correctement le contrat alors que l'on serait tente de le
croire.


Je ne suis pas sûr de saisir ton point. Si j'ai une interface A,
et je m'en sers selon le contrat de A, ou bien, il n'y a pas de
problème, ou bien, B n'aurait pas dû dérivé de A. C'est le b a
ba du LSP.

En revanche, l'inverse ne m'a jamais pose de probleme (il me
semble que tu en avait trouves mais je ne me souvient plus
lesquels ;-) ), sauf du point de vue de l'efficacite (mais tu
as deja souligne ce probleme).

class A {
public:
virtual void f(int i) = 0;

protected:
void f_precond(int i) { ... }
void f_postcond() { ... }
void _invariant() { ... }
}

class B : public A {
public:
virtual void f(int i) {
f_precond(i); ... f_postcond(); _invariant();
}

protected:
void f_precond(int i) { A::f_precond(i); ... }
void f_postcond() { ... A::f_postcond(); }
void _invariant() { ... A::_invariant(); }
}

Si B ne redefinit pas les contrats, ce sont ceux de A qui sont
automatiquement utilises. Si B redefinit les contrats, elle
peut aussi se baser sur les contrats de A. Tout le couplage se
fait de B vers A au lieu de A vers B, ce qui est bien mieux.


Le seul problème que je vois, c'est que rien n'oblige à B
d'appeler les fonctions f_precond, f_postcond, etc. C-à-d qu'en
présence d'une interface A, je n'ai pas la garantie que la
classe implémente le contrat de A.

AMHO, c'est tout l'intérêt ici des fonctions non-virtuelles.
Quand j'appelle une fonction non-virtuelle, je sais exactement
quelle fonction est appelée, indépendamment du type dynamique de
l'objet. Si, en tant que client, j'ai besoin des engagements de
A, la seule façon réele que je puisse être sûr de les avoir,
c'est en n'appelant que des fonctions non virtuelles de A.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34