Le blog d'un informaticien passionné de partage, d'échanges et surtout, pas si zéro que ça.
Suite à l’article précédent sur TDD, abordons la programmation par contrat, terme inventé par le créateur du langage Eiffel Bertrand Meyer. Je sais bien qu’il existe de nombreux articles et publications sur le sujet. Je vais donc synthétiser tout ce que j’ai pu lire dans différents ouvrages (papiers ou en ligne) et illustrer le concept avec le projet des Pages blanches introduit dans le post sur TDD.
Une façon de programmer où l’on érige des règles à respecter par différentes parties qui signent, en quelque sorte, un contrat. Si les parties prenantes respectent ce dernier, alors on garantit le bon fonctionnement de l’objet du contrat.
En termes moins théoriques, l’utilisateur d’un morceau de code s’engage à respecter certaines conditions pour l’utiliser ; le code appelé s’engage lui aussi à fournir le résultat attendu. Les deux sont liés : si l’appelant respecte les conditions de l’appelé, il est en droit d’attendre des résultats corrects ; de même, si l’appelé voit ses conditions respecté, il doit tout faire pour répondre aux attentes de l’appelant.
Les conditions que l’appelant doit respecter sont appelées les pré-conditions. Celles que l’appelé doit garantir à la fin du traitement sont appelées les post-conditions. Celles qui doivent être garanties tout le long de l’exécution sont les invariants.
Illustrons avec un carré (exemple tiré de l’excellent livre Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++) de Philippe Dunski). Une pré-condition à la création d’un carré serait que la longueur des côtés ne soit pas négative ou nulle. C’est au créateur du carré de vérifier ça. Une post-condition est que, pour une longueur valide, l’aire du carré est bien égale au carré de la longueur. Si ce n’est pas le cas, c’est l’implémentation, l’appelé, qui est en défaut. Enfin, un invariant serait qu’il y ait bien quatre côtés de même longueur. S’il y en a plus ou moins, ou s’ils ne sont pas tous exactement de la même longueur, ce n’est pas un carré.
Exemples tirés respectivement de la page Wikipédia anglaise des invariants de classe et d’un article sur iContract.
Si on regarde le constructeur d’une page, on constate que celui-ci ne prend qu’un argument qui est l’URL à charger. Or, comme le programme est spécifique aux pages blanches, il ne sert à rien d’essayer de vouloir charger une page autre. D’où les deux pré-conditions suivantes.
Si l’utilisateur ne respecte pas ces conditions, alors je n’ai aucune raison de respecter le contrat moi non plus. De même, dans le cas du découpage d’une ligne, j’attends plusieurs choses, notamment que la ligne ne soit ni nulle ni vide et qu’elle contienne le bon nombre de champs. En retour, j’offre la garantie qu’il y a bien le bon nombre d’informations dans la Line que je renvoie.
Il ne faut pas tomber dans l’excès de mettre des assertions dans tous les sens. Certaines erreurs ne sont la faute ni de l’appelant, ni de l’appelé. Une connexion impossible à établir, une allocation qui échoue par manque de mémoire, un fichier manquant, tous ces cas sont exceptionnels. Là, mieux vaut privilégier l’utilisation des exceptions et leur possible rattrapage plutôt qu’une assertion qui claque bien sèchement.
Si le contrat est rompu, plus rien n’est garanti. Si le programme n’est pas critique (sous-entendu si des vies ne sont pas en jeu), alors mieux vaut corriger celui-ci en analysant le code fautif. Si le contrat a été rompu, c’est qu’il y avait un problème dans des données, donc inutile de continuer à travailler avec celles-ci.
Je suis partisan, dans le cas où une post-condition n’est pas remplie, d’envoyer une exception afin de le signaler précisément à l’appelant. Pourquoi ? Il semblerait qu’on le préconise, comme ici ou ici. L’idéal est d’envoyer une exception de runtime.
J’ai essayé de regrouper le plus possible ce que je sais tout en étant clair. Mais comme la force d’internet c’est d’offrir plusieurs sources et plusieurs points de vue, voici des liens collectés au fil du temps.