Accueil

Design by contract

Olivier Mangez - Cross Systems


« La qualité constitue sûrement l’un des enjeux majeurs du développement logiciel. Plusieurs techniques adaptées à la programmation orientée objet ont été imaginées pour s’assurer de cette qualité. Si l’eXtreme Programming a popularisé l’utilisation des tests unitaires, Bertrand Meyer, quant à lui, a proposé depuis longtemps la mise en œuvre de la notion de contrat.

Cette technique — on l’appelle Design by contract ou programmation par contrat — consiste à exprimer les spécifications des composants logiciels sous la forme de contrats et à vérifier en permanence le respect de ces derniers.

Après avoir présenté les principes du Design by contract, nous nous intéresserons à sa mise en œuvre dans des langages comme le C++, Delphi ou Java. Par ailleurs, nous montrerons pourquoi cette technique est complémentaire des tests unitaires préconisés par XP. »


Introduction

Les développeurs finissent parfois par oublier que ce que l'on attend d'eux, c'est de produire des logiciels de qualité. C'est-à-dire des logiciels sans bugs. Qu'est ce qu'un bug ? un bug est une erreur dissimulée dans un programme, nuisible à son bon fonctionnement. Ce qui signifie que l'on est en présence d'un bug lorsque le programme ne se comporte pas comme il devrait le faire, comme nous avions spécifié qu'il devrait se comporter. Nous allons découvrir dans la suite de cet article une technique qui permet d'exprimer clairement dans le code des éléments de spécification et de s'assurer lors de l'exécution du logiciel que ces spécifications sont vérifiées.Formulé autrement, nous allons demander à notre logiciel de nous prévenir s'il y a des bugs et de nous dires quels sont-ils.

Assertion

Ou comment exprimer les spécifications dans le code

une telle expression, qui affirme ce que l'élément doit faire et non pas comment il le fait, est appelée une assertion

l'une des façons d'y arriver consiste à inclure des éléments de spécification dans les implémentations elles-mêmes.

les assertions peuvent être contrôlées lors de l'exécution.

une assertion fausse correspond à un bug

les assertions vont être examinées comme une technique de construction des systèmes corrects, et de documentation, expliquent pourquoi ils sont corrects

Que faire quand une assertion n'est pas vérifiée ?

Il est également conseillé de conserver quelques contrôles actifs, même dans le programme le mieux contrôlé. Un appel à abort est donc rarement acceptable dans du code de production.

Les assertions ne doivent pas modifier le fonctionnement de l'application

Ça n'aurait dû jamais arriver !

Design by contract

Contrat

la relation entre une classe et ses clients est vue comme un accord formel, qui exprime les droits et les obligations de chaque partie.

Précondition, postcondition et invariant

Précondition
Un ensemble d'assertions qui doivent être satisfaites lorsqu'une méthode est appelée. Ces assertions peuvent porter sur l'état de l'objet ou les arguments passés à la méthode. Dans un système correct, aucun appel ne doit être exécuté dans un état ou la précondition d'une méthode n'est pas satisfaite.
Postcondition
Un ensemble d'assertions qui sont garanties par la méthode à son retour. Ces assertions peuvent porter sur l'état de l'objet ou la valeur de retour de la méthode. La présence d'une postcondition dans une méthode guaranti que le résultat de l'éxécution va satisfaire certaines conditions, dans la mesure où la précondition elle-même est satisfaite. Verifier que la méthode se termine en laissant l'environnement dans l'état attendu.
Invariant de classe
Un ensemble d'assertions qui doivent être satisfaites tout au long de la vie de l'objet (en dehors de l'exécution des méthodes). Ces assertions ne peuvent porter que sur l'état de l'objet. Chaque méthode lorsqu'elle est appelée peut supposer qu'elle trouvera l'invariant de classe vrai en entrée ; elle s'engage par contre à laisser l'invariant vrai en sortie.

Design by contract et documentation

Le design by contract détecte les bugs, pas les erreurs de saisie des utilisateurs

Design by contract et héritage

Mise en oeuvre

Comment exprimer la notion d'invariant : une façon simple consiste à définir une fonction de contrôle d'invariant et à insérer des apples destinés à cette fonction à la fin de chaque méthode publique

En C++ : utiliser la variable NDEBUG

#ifndef NDEBUG

le code de l'invariant

#endif

JBuilder

iContract

JDK 1.4

Et les tests unitaires ?

L'Extreme programming — imaginée par Kent Beck — a popularisé une pratique, les tests unitaires, dont le but est justement de s'assurer de la qualité logicielle. Le principe de cette pratique est relativement simple : il s'agit de tout tester à l'aide de tests unitaires automatiques — tant dans leur exécution que dans leur interprétation. Pour cela Kent Beck et Erich Gamma — le coauteur de l'ouvrage de référence à propos de design patterns — ont développé JUnit, un framework de test dont le but est d'assurer cette automatisation.

On peut alors se demander si design by contract et test unitaire sont des techniques compatibles ou redondantes. Kent Beck, dans son ouvrage de présentation de l'Extreme programming, remarque que l'on pourrait utiliser le design by contract plutôt que les tests unitaires pour s'assurer de la qualité du logiciel que l'on développe. J'avoue que je ne suis pas d'accord avec ce point de vue et qu'il me semble profitable de mettre en pratique ces deux techniques.

Un test unitaire consiste à coder un exemple d'utilisation d'une méthode ou d'une classe et de vérifier que le résultat de cette utilisation conduit bien au résultat attendu. Ce résultat attendu est exprimé sous la forme d'assertions que JUnit à la charge de vérifier. Par conséquent, la vérification de ces tests résulte d'une action volontaire — caractérisée par le fait que l'on va les exécuter — et ces tests vont vérifier le bon comportement du système dans des cas particuliers que l'on espère les plus généraux possibles. (parce que l'on code des cas particulier, on est capable de définir des assertions contraignantes) ; les tests sont en dehors du code du programme.

Le design by contract quant à lui consiste à exprimer des assertions qui vont être vérifiées au cours de l'exécution du programme. Ces assertion s doivent être vérifiées dans tous les cas. Alors que les tests unitaires s'appliquaient sur des cas particulier (le calcul de la racine carrée de 4 vaut 2) : les assertions peuvent donc être très précises mais on court toujours le risque que le test ne mette pas en évidence un bug dans un autre cas particulier ; le design by contract s'efforce de définir des assertions qui s'appliquent dans tous les cas (on ne peut calculer la racine carrée que d'un nombre positif et dans ce cas cette racine est positive

 

Références

Les notions de précondition, de postcondition et d'invariant sont apparues initialement dans les travaux de Floyd, Naur et Hoare, puis ont ensuite été popularisées par Bertrand Meyer et son language Eiffel.

Bertrand Meyer, Object-oriented software construction, Prenctice Hall, 1988

Bjarne Stroustrup, The C++ programming language, Third edition, Addison Wesley, 1997

Andrew Hunt, David Thomas, The pragmatic programmer, Addison Wesley, 2000