Astuces DotNet (Sébastien Courtois)

09/05/2010

[Nouveautés C# .NET 4] Code Contracts

Filed under: .NET, C# 4, Débutant, DevLabs — Étiquettes : , , , , , , , — sebastiencourtois @ 16:12

Code Contracts est une fonctionnalité ajouté à .NET 4 cette année mais qui a, pendant des années, été un projet DevLabs sous le nom de Spec# ou Code Contracts. L’idée est de permettre au développeur de fournir des informations sur son code au travers du code lui même.

  • Un exemple concret vaut mieux que de long discours

Prenons un exemple d’une classe utilitaire.

public class Division
{
    public Division(decimal _num,decimal _denom)
    {
        this.Denominator = _denom;
        this.Numerator = _num;
    }

    public decimal Numerator { get; set; }
    public decimal Denominator { get; set; }

    public void Invert()
    {
        decimal tmpNum = this.Numerator;
        this.Numerator = this.Denominator;
        this.Denominator = tmpNum;
    }

    public decimal Compute()
    {
        return this.Numerator / this.Denominator;
    }

    public static decimal StaticCompute(decimal numerator, decimal denominator)
    {
        return numerator / denominator;
    }
}

public class Absolu
{
    public static decimal StaticCompute(decimal value)
    {
        return Math.Abs(value);
    }
}

La première classe de division permet de créer des fractions et d’éxécuter le résultat. Un méthode statique permet de faire la même opération sans créer une instance de la classe. Une deuxième classe qui permet, au travers d’une méthode statique, de récupérer la valeur absolu d’un nombre.

Si vous vous rappelez vos cours de mathématiques, vous savez surement qu’une valeur interdite dans le calcul d’une division est la valeur 0 pour le dénominateur. Si on regarde la méthode division, on voit que la valeur Dénominator peut être changé par le constructeur, par la propriété elle même (public get;set;), par la méthode Invert (si le numérateur = 0, alors l’invert donne un dénominateur = 0. Une solution avec .NET 3.5 serait de mettre un condition dans le set du numérateur afin de lancer une exception si une valeur = 0 tente d’être assigné. Toutefois cette façon de faire peut entrainer des codes dans les set assez long car il peut y avoir d’autres processus dans ce set ( NotifyPropertyChanged en WPF par exemple).

  • Contract Invariant / PréConditions (Requires)

Afin de sortir le code de validation des propriétés, Code Contract propose de créer une méthode dite “Invariant” où l’on mettra toute les conditions à vérifier lorsqu’une méthode ou un constructeur est exécuté. Pour la classe Divsion, on pourrait créer la méthode “Invariant” suivante :

[ContractInvariantMethod()]
protected void DivisionInvariant()
{
   Contract.Invariant(this.Denominator != 0, "Le dénominateur doit toujours être différent de 0.");
}

Ainsi si on tente de créer une instance Division d = new Division(2,0), on obtient l’exception suivantes

cc1jpg

Il est possible de faire cela sur des méthodes pour vérifier les paramètres par exemple.

public static decimal StaticCompute(decimal numerator, decimal denominator)
{
    Contract.Requires(denominator != 0, "Le dénominator doit être différent de 0.");
    return numerator / denominator;
}

Remarque : Le Contract.Requires fonctionne aussi pour les méthodes non statique.

  • PostConditions (Ensures)

Les méthodes ci-dessus permettent de vérifier les entrées. Il serait aussi intéressant de définir les sorties. Toujours grâce à vos rappels de mathématiques, vous savez que la méthode absolu renverra toujours une valeur supérieure ou égale à 0. Cela est une spécification d’une sortie de méthode. Ainsi la classe absolu ci-dessus, devient :

public class Absolu
{
    public static decimal StaticCompute(decimal value)
    {
        Contract.Ensures(Contract.Result<decimal>() >= 0);
        return Math.Abs(value);
    }
}

La méthode Ensures() indique que la condition qui suit est satisfait lors de la sortie de la méthode. La méthode Result() récupère le résultat afin de l’analyser. Il est aussi possible de vérifier l’état d’un paramètre au début de la méthode gràce à la méthode Contract.OldValue().

Si on remplace return Math.Abs(value) par return –1;, on obtient l’exception suivante :

cc2

  • Installation et Configuration

Afin d’utiliser les code contracts, il est nécessaire d’installer les outils Code Contracts : http://msdn.microsoft.com/fr-fr/devlabs/dd491992(en-us).aspx

Une fois installé, il est nécessaire d’aller dans les propriétés du projet pour activer Code Contracts :

cc3

L’écran ci-dessus dépend de votre version de Visual Studio.

    • VS Express 2010 : Aucune possibilité de d’activer Code Contracts
    • VS Pro 2010 : Runtime Checking  (Standard Edition)
    • VS Team System 2008 ou VS Premium / Ultimate 2010 : Runtime + Static Checkin.  (Premium Edition)

Juusqu’à maintenant, nous avons vu le Runtime Checking. Les vérifications se font lors de l’éxécution et génère des exceptions. Le Static Checking permet de voir ces problèmes dès la compilation lors d’une analyse statique de code.

  • Fonctionnement de Code Contracts

Les codes de vérifications Code Contracts ne sont pas utilisé uniquement à l’exécution mais aussi à la compilation. Lors de la compilation ,le compilateur analyse les codes et génère du IL pour l’insérer aux endroits nécessaire. Ainsi la méthode Compute de Division est représenté comme suit :

cc4 En Haut : Code généré sans code contract / En Bas : Code généré  avec Code Contracts

Il est donc possible de mettre ses codes Contracts.Requires/Contracts.Ensures dans n’importe quel ordre dans les méthodes car tout est reclassé lors de la compilation.

2 commentaires »

  1. Salut Seb !
    J’aurais plutôt levé une ArgumentException dans le constructeur de la classe Division plutôt que d’utiliser un contrat, mais pourquoi pas après tout… Est-ce que tu sais s’il y a des recommandations, des best practices indiquant quand lever des exceptions et quand utiliser des contrats ?

    Commentaire par Farid — 09/05/2010 @ 19:22

    • Oui c’est effectivement une solution possible. Maintenant il faut bien voir que il faudra aussi ajouter une exception dans la méthode Invert ou dans la méthode Compute… Si tu as beaucoup de mathodes il faudra multiplier ce code de levée d’exception ==> erreurs/oublis possibles. L’avantage des contrats est de regrouper le code de validation de tes classes à un seul endroit afin d’être sur que, à tout moment, ta classe est dans un état correct.

      Un autre avantage des Contracts est aussi le static checking qui ne pourrait pas marcher avec une levée d’exception ArgumentException.

      Pour les best practices et autres, il n’y en a pas à part, essayer d’utiliser les contrats au maximun par rapport aux levée d’exception pour la validation de paramètres (car au final, Contract lève aussi une exception).

      Commentaire par sebastiencourtois — 09/05/2010 @ 19:31


RSS feed for comments on this post. TrackBack URI

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Propulsé par WordPress.com.

%d blogueurs aiment cette page :