La programmation par contrat
Transcription
La programmation par contrat
La programmation par contrat Où comment concevoir des applications qui fonctionnent du « premier coup » L’ objet élément essentiel d’un programme Un objet est présent dans un environnement { Il interagit avec d’autres objets { { Communication par message (appel de méthodes) Consultation/modification de propriétés (accès aux champs) Il étend les fonctionnalités (services, méthodes, messages) offerts par des objets Il encapsule des données ou des concepts qui sont manipulés par des processus Problème : { Comment garantir qu’un objet s’intègre bien dans son environnement ? La fiabilité d’une application Deux notions clés relativement à la fiabilité { { La correction : permet de garantir que l’utilisation d’un objet dans l’application ne pourra pas engendrer d’erreurs La robustesse : permet de garantir que si un objet (ou une opération effectuée par un objet) est défaillante l’application récupérera de l’erreur Opposition entre les deux approches { { La correction : détection avant d’exécuter une opération illicite (compilation/exécution) La robustesse : traitement s’effectuant après qu’une opération illicite se soit produite Le contrat : un moyen de garantir la correction Idée : { { Un contrat formel est défini entre le processus utilisateur et l’objet utilisé Si l’objet utilisé (ou client) par le processus respecte toutes les clauses imposées par processus utilisateur, aucune erreur ne pourra se produire Principe : { { { Le processus utilisateur émet un ensemble de demandes (claims) L’objet utilisé définit un ensemble de propriétés (responsibilities) L’adéquation des propriétés de l’objet utilisé avec les demandes du processus forment le contrat Mise en œuvre d’un contrat Conception d’un objet/module On souhaite qu’un processus donné utilise cet objet { Adhésion de l’objet aux prérequis du processus Les processus définis dans cet objet/module seront utilisés d’autres objets { { Les objets ne sont pas définis : définition des prérequis nécessaires pour l’utilisation des objets Les objets sont déjà définis : adaptation des processus pour se satisfaire des propriétés des objets Idée Chaque programmeur doit respecter les prérequis et contraintes des autres programmeurs L’adhésion de tous les objets aux contrats garantit la correction Spécifier un contrat Les différents éléments pouvant être mis dans un contrat { { { { { { La liste des méthodes qu’une classe doit fournir et leur actions Les valeurs acceptables pour les champs et propriétés d’un objet Les conditions sur les arguments acceptables pour une méthode Les conditions sur les valeurs retournées par les méthodes d’un objet Les conditions sur le temps d’exécution d’une méthode d’un objet Les invariants propres à l’objet Précondition/Postcondition Les triplets de Hoare { P } A {Q} où { { { P : définit un ensemble de conditions sur les variables et champs qui doivent être vérifiées avant que A soit exécuté Q : définit un ensemble de conditions sur les variables et champs qui doivent être vérifiées après que A fut exécuté A : l’expression à exécuter Deux cas de figure Correction totale : { P } A {Q} si P est vrai A termine et vérifie Q. Correction partielle : { P } A {Q} si P est vrai et A termine alors A vérifie Q. Conditions fortes/faibles (Weak and strong conditions) Une condition peut-être plus ou moins fortes { { Précondition forte : favorable au concepteur de l’objet utilisé / défavorables à l’utilisateur Postcondition forte : favorable à l’utilisateur / défavorable au concepteur de l’objet utilisé Comment s’assurer qu’un objet respecte bien un contrat ? Premier point du contrat à vérifier: Définir les méthodes et champs qu’une classe doit fournir Deux approches possibles : Définir une classe de base purement abstraite à surcharger (approche C++) Ajouter au langage une construction permettant de définir les méthodes et champs qu’une classe doit fournir (approche Java) S’assurer de l’implantation des méthodes Nous considérons le compteur { { { En C++ { Doit fournir une méthode de remise à zéro (reset) Doit fournir une méthode pour passer à l’état suivant (next) Doit fournir deux méthodes pour accèder en lecture et écriture à la valeur du compteur (getCounter, setCounter) Définir une classe CounterBase purement abstraite qui expose ses méthodes virtuelles En Java { Définir une interface ICounter qui expose les méthodes devant être implantées S’assurer de l’implantation des méthodes En C++ En Java class CounterBase { … public: public interface ICounterBase { … public void next(); public void reset(); public int getCounter(); public int setCounter(); … } virtual void next() = 0; virtual void reset() = 0; virtual int getCounter() const = 0; virtual int setCounter(int) = 0; … } S’assurer de l’implantation des méthodes En C++ En Java class MyCounter: public CounterBase { … public: public class MyCounter implements ICounterBase { … public void next() virtual void next() {…} virtual void reset() {…} virtual int getCounter() const {…} virtual int setCounter(int) {…} … } {…} public void reset() {…} public int getCounter() {…} public int setCounter() {…} … } S’assurer de l’implantation des méthodes Comment s’effectue le contrôle ? { { A la compilation En C++ { En Java Refuse de compiler une classe si la classe n’implémente pas toutes les méthodes définies dans la classe « interface » Le contrôle vérifie : { { Refuse de créer une classe si la classe possède des méthodes virtuelles purement abstraites Que la méthode est bien exposée Que les types des arguments et du résultat sont les bons Le contrôle ne vérifie pas: { { Que l’exécution de la méthode est correcte Que les valeurs passées en paramètre ou retournées sont les bonnes Comment s’assurer qu’un objet respecte bien un contrat ? Deuxième point du contrat à vérifier: Vérifier que les arguments passés aux méthodes d’un objet sont correctes Ceci correspond à vérifier que la condition {P} de l’expression {P} A {Q} est vérifiée La vérification doit avoir lieu avant d’exécuter A Méthode : ajouter dans le code avant d’exécuter A une assertion. Comment implanter une assertion testant une precondition En C++ En Java void f(int* a, int i) { if(a != NULL || i == 0) throw Exception( "Assertion failed"); … } public void f(int[] a, int i) { if (!(a != null && i >= 0 && i < a.length)) throw new IllegalArgumentError( "Assertion failed"); … } void f(int* a, int i) { assert(a != NULL || i == 0) … } Fonctionnement de l’assertion Le code correspondant à la condition est exécuté avant que le code de A est exécuté Si la condition n’est pas vérifiée, une exception est lancée Problème : { L’erreur est détectée uniquement lors de l’exécution { Implique une phase de test précise du programme Par défaut le programme s’arrête ! Implique de rendre le programme robuste, ie. de récupérer et de traiter l’exception (cf. cours sur les exceptions) Comment s’assurer qu’un objet respecte bien un contrat ? Troisième point du contrat à vérifier: Vérifier que les valeurs retournées par un méthode sont correctes si l’exécution termine (correction partielle) Ceci correspond à vérifier que la condition {Q} de l’expression {P} A {Q} est vérifiée si l’exécution de A termine. La vérification doit avoir lieu après avoir fini d’exécuter A Méthode : ajouter dans le code après l’exécution de A une assertion. Si la condition n’est pas vérifiée, une exception est lancée Comment implanter une assertion testant une postcondition En C++ En Java void f(int* a, int i) { assert(a != NULL || i == 0); … assert(result != 0); return result; } public void f(int[] a, int i) { int result; if (!(a != null && i >= 0 && i < a.length)) throw new IllegalArgumentError( "Assertion failed"); … if (result != 0) throw new IllegalArgumentError( "Assertion failed"); } Ce qu’il reste à tester ? Les invariants propres à l’objet { Il est possible d’ajouter de vérifier au début de chaque méthode et à la fin de chaque méthode que l’invariant est respecté Les conditions sur le temps d’exécution d’une méthode d’un objet { { Faire confiance au programmeur ! Utiliser des outils de preuve automatique (cf. Module électif)