• Aucun résultat trouvé

2.3 Fondation de l’approche contractuelle

2.3.2 Etude de la "conception par contrat"

Dans cette partie, nous allons, dans un premier temps, présenter le langage de développe-ment conçu par B. Meyer et qui implédéveloppe-mente son approche par contrat. Dans un second temps, nous nous intéresserons au positionnement de l’approche par contrat par rap-port aux propriétés que nous avons retenues pour objectif dans notre introduction.

2.3.2.1 Eiffel

La "conception par contrat" est intégrée dans le langage Eiffel [72] sous la forme d’asser-tions exprimant les obligad’asser-tions de chacune des parties. Les obligad’asser-tions du client sont placées dans des préconditions, vérifiées à l’entrée de la méthode qu’elles contraignent, les obligations du fournisseurs sont placées dans les postconditions, vérifiées à la fin à l’exécution de la méthode. Enfin des invariants contraignent tous les états observables d’une instance de classe. Ces assertions sont notées "require" (préconditions), "ensure" (postconditions), "invariant" (invariants). Depuis leur expression, il est possible d’in-voquer des méthodes, l’implication logique est exprimée à l’aide du mot clé implies, le contenu d’une variable antérieur à l’exécution d’une méthode peut être utilisé après celle ci à l’aide du mot cléold. Les assertions peuvent être labellisées par un nom qui sera utilisé pour les désigner en cas d’erreur. Exemple de spécification :

class interface ACCOUNT feature -- Access balance: INTEGER -- Current balance deposit_count: INTEGER

-- Number of deposits made since opening feature -- Element change

deposit( sum: INTEGER)

-- Add sum to account. require

non_negative: sum>= 0 ensure

one_more_deposit: deposit_count = old deposit_count + 1 updated: balance = old balance + sum

invariant

consistent_balance: balance= all_deposit.total end -- class interface ACCOUNT

Les responsabilités en cas de non vérification d’une assertion sont distribuées de la manière suivante :

• précondition : le client est responsable,

• invariant : le fournisseur de la méthode à la sortie de laquelle l’invariant n’est plus vérifié,

A l’exécution les assertions sont factorisées [23] et évaluées par contexte (méthodes et classes) et catégorie (pre/post/invariant). Si une assertion n’est pas satisfaite, sa catégorie détermine l’endroit où se produit l’exception associée. Une violation de pré-condition génère une exception dans la méthode cliente, de post-pré-condition une ex-ception dans la méthode fournie, un invariant dans la méthode du fournisseur ayant causé la rupture de l’invariant. Les exceptions d’une méthode sont capturées et traitées au sein de la clause rescue et le langage permet de réévaluer la méthode dans laque-lle l’exception est capturée à l’aide de la commande retry. Dans ce cas les variables locales peuvent être modifiées avant la ré-exécution et ne sont pas réinitialisées au re-commencement de la méthode, par exemple :

local

attempts: INTEGER do

if attempts< Max_attempts then

last_character:= low_level_read_function(f) else failed:= True end rescue attempts:= attempts + 1 retry end

Dans cet exemple, la variable locale attempts comptabilise le nombre de tentatives d’exécuter la fonction low_level_read_function(). Chaque exception levée par cette dernière, est rattrapée par la clause rescue, qui incrémente attempts avant de relancer la méthode.

Eiffel permet une configuration d’armement paramétrée par un niveau sur une échelle d’importance et dont la portée sur le code peut être spécifiée. La vérification des asser-tions d’un niveau donné entraîne celle des niveaux inférieurs : no < require < ensure < invariant < check all. La portée est décrite en terme de cluster (équivalent de package java pour Eiffel), ou d’ensembles de classes prédéfinis. L’héritage de classes supporte la redéclaration des assertions [tutorial]. Une classe fille, peut ajouter des assertions à celle de sa classe parente, tout comme une classe d’implémentation à son(ses) inter-face(s) ou classe(s) abstraite(s). Les règles mises en oeuvre sont les suivantes :

• invariant : somme des invariants des classes(interfaces) parentes

• require : devient require-else, les préconditions ajoutées sont alternatives (" ou ") • ensure : devient ensure then, les postconditions ajoutées sont obligatoires (" et ") Eiffel est une solution pragmatique à certains besoins de contractualisation. Son for-malisme est accessible aux développeurs, l’évaluation des assertions peut être désar-mée de manière graduelle et sur un ensemble de classes donné. La relation d’héritage du modèle objet auquel s’applique le contrat est prise en compte. Par contre les con-trats ne sont pas réifiés et n’offrent pas de services de réflexion, et ils sont statiquement définis directement dans le source de la classe.

2.3.2.2 Propriétés de la conception par contrat

La conception par contrat s’applique à l’approche objet de la programmation. Dans ce cadre une application est constituée d’un ensemble d’objets qui collaborent (comme peut le montrer le diagramme de collaboration d’UML) en s’échangeant des messages, les appels de méthodes et leurs retours. Ainsi la précondition d’une méthode porte sur les paramètres reçus par cette dernière, tandis que la post condition contraint la valeur de retour qu’elle émet. La propriété essentielle qu’exprime la conception par contrat est que les paramètres fournis par un objet client à la méthode de l’objet serveur doivent vérifier sa précondition, et que dans ce cas, la valeur de retour du serveur doit vérifier la post condition de la méthode. La conformité du client et du serveur à la spécification de la méthode, constituée des pré et post conditions, est ainsi la propriété de base vérifiée par la conception par contrat.

Une seconde préoccupation concernant l’héritage des classes s’est faite jour dans la conception par contrat. Dans un premier temps les règles édictées dans le cadre de Eiffel, définissant le contrat de la classe enfant en fonction de celui de la classe par-ente, ont été reprises. Puis [33] les a affiné pour valider formellement l’expression, sur la base des contrats d’une classe parente et enfant, de la substituabilité de l’une par l’autre. Par ailleurs dans le cadre de la collaboration des objets, un client ne peut en-voyer un message à un serveur qu’en passant par une référence sur ce dernier. Or cette référence est nécessairement celle d’une classe enfant du serveur (du fait du polymor-phisme). Comme cette descendance est contrainte par les règles de substituabilité du contrat, la compatibilité d’un objet client et d’un autre serveur y est soumise. Ainsi la conception par contrat permet une première approche de l’expression de la compat-ibilité entre deux objets. Enfin, la conception par contrat affecte les responsabilités à la classe appelante et à la classe appelée vis à vis de leur conformité aux pré et post conditions. Toutefois celles-ci ne font pas partie d’une architecture qui permettrait de les manipuler, et donc la faute retombe sur leur développeur ou fournisseur.

Comme nous l’avons fait remarquer dans l’introduction ces propriétés sont contenues dans l’ensemble de celles fondamentales nécessaires à l’expression de la validité d’un assemblage d’objets, de composants ou de services. Toutefois elles sont l’objet dans le cadre de la conception par contrat d’assez fortes limitations. Ainsi les propriétés de conformité et compatiblité :

• ne concernent qu’un seul formalisme et un seul ensemble d’observations prédéfini (paramètres, valeur de retour d’une opération), or nous avons vu que la validité d’un assemblage dépendait de propriétés et de formalismes variés,

• sont implicites et fondues dans le code de l’application, c’est à dire que l’outil d’évaluation de ces propriétés, qui est le code du programme lui même, ne per-met pas de les exposer à celui qui en utilise le résultat. Elles ne sont pas réifiées en objets visibles dont l’état reflèterait leur satisfaction.

• relèvent d’un modèle de spécification plus large, l’approche hypothèse-garantie, sur lequel il est possible de s’appuyer pour valider les interactions au sens large entre des entités de types variés (composants, services...),

• ne concernent que l’interaction entre deux entités, un client et un serveur,

Par ailleurs les responsabilités ne sont pas associées à des objets visibles et manipu-lables par l’acteur qui met en oeuvre l’application. Dans le cadre de l’approche par

objets, ces responsabilités devraient au mieux être associées à des modules ou des li-brairies dynamiques qui pourraient être interchangées par l’administrateur de l’appli-cation pour répondre à une défaillance détectée par un contrat.