Code Contracts est une nouvelle fonctionnalité du .NET Framework qui permet de définir des contraintes dans du code .NET qui pourront être réutilisées par des outils d’analyse ou d’écriture du code.
Ce post fait partie d’une série de post consacré à Code Contracts :
- Partie 01 – Introduction
- Partie 02 – Les pré-conditions
- Partie 03 – Les post-conditions
- Partie 04 – Les invariants
- Partie 05 – Les contrats sur les interfaces
Depuis que je suis architecte, j’insiste énormément sur la qualité des développements et en particulier le fait de détecter les erreurs le plus rapidement possible (durant la phase de développement ! Et non à l’intégration, recette ou pré-production !).
A travers différents posts sur mon blog (au moins une quinzaine), je vais présenter cette technologie du .NET Framework 4.0 qui actuellement très peu utilisé, mais qui devrait être obligatoire d’utiliser dans tout développement logiciel sous .NET.
Qu’est ce qu’un contrat ?
Lorsque l’on regarde la documentation MSDN concernant les bibliothèques du .NET Framework, on se rend compte que Microsoft impose des contraintes que l’on doit respecter avant d’utiliser certaines fonctionnalités (par exemple l’appel à la méthode String.Substring()).
Si on regarde la capture précédente, les contraintes imposées par Microsoft pour utiliser la méthode String.Substring() sont surlignées en jaune. Cette contrainte est ce que l’on appel un contrat.
Pourquoi imposer des contrats ?
Lorsqu’un développeur conçoit une classe, il offre une partie “publique” qui sera certainement utilisé par un autre développeur, qui au passage, n’est pas forcement dans la même équipe (par exemple un intégrateur). Etant donné que l’autre développeur n’a pas directement connaissance de l’implémentation de la classe, il est donc nécessaire de contrôler et documenter les appels à la partie publique à l’aide de contraintes (où plutôt contrats). Les contrats permettent d’assurer au développeur de la classe que les données d’entrées sont valides et n’auront pas d’effet secondaires sur l’exécution de la classe.
Lorsqu’un contrat n’est pas respecté, il n’est théoriquement pas possible d’effectuer une compilation. Si le compilateur ne supporte pas la vérification des contrats, alors l’exécution de l’application doit-être stoppée.
Que ce passe-t-il si on n’impose pas des contrats ?
Prenons l’exemple suivant :
On propose une méthode Traitement() prenant en paramètre une interface que doit implémenter un intégrateur. Cette interface contient une méthode GetObject() qui retourne un objet qui sera affiché dans une application Console.
Voici le code de cette interface :
public interface IObjectProvider { object GetObjet(); }
Et le code de la méthode Traitement() qui prend en paramètre un objet implémentant l’interface précédente :
public class ConsoleDisplay { public void Write(IObjectProvider provider) { object obj; obj = provider.GetObjet(); Console.WriteLine(obj.ToString()); } }
Maintenant, imaginons que l’on compile et livre en Release la classe ConsoleDisplay et l’interface IObjectProvider à un intégrateur. Ce dernier va donc implémenter l’interface IObjectProvider comme ceci :
public class MyObjectProvider : IObjectProvider { public object GetObjet() { return null; } }
Voici maintenant un exemple d’utilisation de la classe ConsoleDisplay développé par l’intégrateur :
MyObjectProvider myProvider = new MyObjectProvider(); ConsoleDisplay display = new ConsoleDisplay(); display.Write(myProvider);
Si l’intégrateur exécute ce code, il obtiendra l’erreur suivante :
On obtient une exception de type NullReferenceException alors que les variables display et myProvider ne sont pas null. Le bug se trouve donc à l’intérieur de la méthode Write() (le StackTrace de l’exception nous donnera confirmation). Lorsque l’on obtient ce genre d’erreur, est-ce à l’intégrateur de le corriger ou à nous ? Bien évidemment, c’est à l’intégrateur… Mais comment peut-il savoir d’où vient le problème et comment le corriger ? Tout simplement en l’aidant à l’aide des contrats !
Ecrire des contrats avant le .NET Framework 4.0…
Du .NET Framework 1.0 jusqu’au 3.5 inclus, il était possible de définir des contrats sur les paramètres des méthodes en utilisant les exceptions :
public double Division(double dividende, double diviseur) { if (diviseur == 0) { throw new ArgumentException("Le diviseur ne peut pas être à 0", "diviseur"); } return dividende / diviseur; }
Le code précédent déclenche une exception de type ArgumentException si le contrat n’est pas respecté (ici le diviseur ne doit pas être égal à 0). En production, la levée des exceptions dérivée de ArgumentException indique un bug dans l’application dû au passage d’un paramètre incorrect. Ce n’est donc pas à l’utilisateur final de corriger cette erreur, mais bien au développeur de l’application. Ce genre d’exception ne devrait donc jamais être interceptées dans un bloc try/catch.
Les avantages de Code Contracts
Si on regarde le code précédent, on se rend très vite compte de plusieurs inconvénients :
- Les contrats ne sont pas vérifiés à la compilation
- Le nom du paramètre est une chaine de caractère (pas de typage pour le refactoring)
- Les contrats ne sont pas hérités lors de la redéfinition d’une méthode (nécessite de dupliquer du code)
- Il est nécessaire de documenter explicitement les commentaires XML
- Il n’est pas possible de définir des contrats dans une interface (ou méthodes abstraites).
- Il n’est pas possible de supprimer les contrats en production
Code Contracts permet de palier à tous ces problèmes en offrant :
- La vérification des contrats à la compilation
- Le typage fort des contrats
- L’héritage des contrats dans la redéfinition des méthodes
- La documentation automatique des contrats en XML
- La possibilité de définir des contrats dans les interfaces (et méthodes abstraites)
- La possibilité de créer un assembly séparé contenant uniquement les contrats
Code Contracts propose en plus de :
- Définir des post-conditions
- Définir des invariants
- De redéfinir la politique de vérification des contrats
- De créer de nouveau contrats (agrégations de contrats)
Concept de Code Contracts
Code Contracts s’utilisent en 2 étapes :
- La première consiste à définir les contrats dans le code en utilisant les classes contenues dans le namespace System.Diagnostics.Contracts. Ces classes ne produisent aucun effet à l’exécution. Il faut donc voir les contrats comme des meta-données sur le code (exactement comme des attributs .NET).
- La deuxième étape consiste à exécuter un outil qui analysera les contrats contenu dans l’assembly compilé et qui produira en sortie différents résultats. Microsoft propose avec Code Contracts trois outils :
- ccrewriter : Cet outils va convertir les contrats spécifiés dans un assembly compilé en un code .NET permetant de vérifier les contrats à la l’exécution.
- cccheck : Cet outil va analyser un assembly et vérifier statiquement que tous les appelants respecte bien les contrats.
- ccdoc : Cet outil va extraire les contrats et produire une documentation XML qui sera possible d’utiliser dans SandCastle.
Le schéma précédent montre un exemple d’utilisation de l’outil ccrewriter :
- Les contrats sont définit dans le code C# et doit-être vu comme des méta-données
- Après compilation, le contrats sont compilés mais ne produisent aucun effet
- Après exécution de l’outil ccrewriter les contrats compilé sont transformé en bloc if/then/throw permettant de contrôler les contrats et lever une exception si ces derniers ne sont pas valides.
Bien évidemment, il y a de grande chance que Microsoft et d’autres éditeurs tiers fournissent d’autres outils. Il est d’ailleurs possible de créer un outil de ce genre en utilisant le projet open source Microsoft : Common Compiler Infrastructure.
La classe principale permettant de définir des contrats est la classe Contract. Elle contient un ensemble de méthodes qui permettent de définir des pré-conditions, des post-conditions et des invariants. J’insiste sur le fait que ces méthodes ne produisent aucun effet à l’exécution si on utilise les assemblys compilés tel quel. Il faudra lancer l’outil ccrewriter après la compilation afin de réécrire les assemblys compilés et transformer les contrats déclarés en un code qui réalisera des tests et lèvera des exceptions si les contrats ne sont pas respectés.
Ce qu’il faut pour utiliser Code Contracts
Les classes permettant de définir les contrats sont incluses dans le .NET Framework 4.0 (assembly System.dll), il est cependant possible d’utiliser Code Contracts avec le .NET Framework 3.5 utilisant l’assembly Microsoft.Contracts qui peut-être téléchargé sur le site de Microsoft.
Les outils de réécriture et d’analyse de code ne sont pas fourni avec le .NET Framework ni avec Visual Studio. Il faudra télécharger ces outils sur le site officiel de code Code Contract sur le site de Microsoft Research : http://research.microsoft.com/en-us/projects/contracts/
L’équipe de Code Contracts propose aussi une extension pour Visual Studio 2010 qui propose des snippets de code et l’affichage des contrats dans l’info-bulle de l’éditeur de code.
Une fois les outils installé, le paramétrage de ccrewriter, cccheck et ccdoc se font depuis les propriétés du projet :
Si vous disposez d’une version Premium ou Ultimate de Visual Studio, vous avez la possibilité d’activer la vérification statique du code.
Dans les posts suivants, nous verront comment définir et activer des contrats avec Code Contracts.
Stay tuned !
C’est vraiment bon à savoir, surtout pour les développeurs en DotNET. Ces contrats peuvent effectivement améliorer leurs travaux de développements.