ModèleDernièrement mes collègues et moi exécutions le Kata FizzBuzz dans le cadre de notre Dojo pratiques d’ingénierie. L’objectif était de pratiquer le développement piloté par les tests. Je dois avouer que ce fut fort amusant et la solution à laquelle nous sommes arrivés est tout à fait adéquate et présente un modèle objet élégant qui résoud le problème. La séquence FizzBuzz est générée par la méthode CountTo de la classe FizzBuzz. Cette dernière utilise un ensemble de règles qui vérifient si elles s’appliquent et transforment, le cas échéant, l’entier question soit en “Fizz”, “Buzz” ou “FizzBuzzz”. class FizzBuzz {     private readonly IRule[] _rules = new IRule[] {         new FizzBuzzRule(),         new BuzzRule(),         new FizzRule()     };     private readonly IRule _defaultRule = new NumberRule();      public string[] CountTo(int upperBound)     {         var sequence = new List();         foreach (var n in NumbersUpTo(upperBound))         {             sequence.Add(Interpret(n));         }         return RespondWith(sequence);     }     private static IEnumerable NumbersUpTo(int n)     {         for ( int i = 1; i <= n; i++)         {             yield return i;         }     }     private string Interpret(int n)     {         foreach (var rule in _rules)         {             if (rule.Applies(n)) return rule.Apply(n);         }         return _defaultRule.Apply(n);     }     private string[] RespondWith(List result)     {         return result.ToArray();     } } La classe FizzRule est un exemple d’implémentation d’une rèlge. class FizzRule : IRule {     public bool Applies(int number)     {         return ShouldSayFizz(number);     }     private static bool ShouldSayFizz(int n)     {         return IsDivisibleByThree(n) || ContainsThree(n);     }     private static bool IsDivisibleByThree(int number)     {         return number % 3 == 0;     }     private static bool ContainsThree(int n)     {         return n.ToString().Contains("3");     }     public string Apply(int number)     {         return SayFizz();     }     private static string SayFizz()     {         return Fizz;     }     private const string Fizz = "fizz"; } Plus tard, je me suis demandé de quoi aurait l’air la solution si j’utilisais les fonctionnalités du langage C# 3.0 telles que LINQ, les expressions lambda et les méthodes d’extension. Avec notre batterie de tests comme garde-fou je me suis lancé dans une session de refactorisation au bout de laquelle je suis arrivé à la solution que je présente plus loin. Les premières pistes que je choisis d’explorer sont les méthodes que Resharper suggère de changer en méthodes statiques. En fait, les méthodes statiques me font toujours penser à des méthodes utilitaires qui en général sont très réutilisables. C’est le cas par exemple des méthodes IsDivisibleByThree et ContainsThree de la classe FizzRule plus haut. Je les rassemble donc dans la classe IntExtensions après avoir introduit un peu de généricité pour qu’elles soient utilisables également dans la classe BuzzRule. public static class IntExtensions {     public static bool Contains(this int n, int number)     {         return n.ToString().Contains(number.ToString());     }     public static bool IsDivisibleBy(this int number, int dividend)     {         return number % dividend == 0;     } } Avec ces modifications, la méthode ShouldSayFizz se lit comme suit: private static bool ShouldSayFizz(int n) {     return n.IsDivisibleBy(3) || n.Contains(3); } Je suis content car la lisibilité du code n’en souffre pas. Je m’attaque de la même façon à la méthode NumbersUpTo de la classe FizzBuzz. J’en fais une méthode d’extension que je rajoute à la classe IntExtensions. Je prends le soin de renommer la méthode FirstNumbers pour une meilleure lisibilité du code client qui se lira foreach(int number in n.FirstNumbers()) { … } Cela me rappelle mon cours d’analyse et la série des n premiers nombres entiers… En fouillant un peu dans MSDN, je retrouve comment générer une suite d’entiers grâce à la classe Ennumerable et je modifie mon implémentation en conséquence. J’obtiens le résultat suivant public static class IntExtensions {     public static IEnumerable FirstNumbers(this int n)     {         return Enumerable.Range(1, n);     }     public static bool Contains(this int n, int number)     {         return n.ToString().Contains(number.ToString());     }     public static bool IsDivisibleBy(this int number, int dividend)     {         return number % dividend == 0;     } } Avec ces méthodes utilitaires hors de la classe FizzRule, la responsabilité de celle-ci devient plus évidente et je la simplifie pour arriver au résultat suivant class FizzRule : IRule {     public const string Fizz = "fizz";     private const int Three = 3;     public bool AppliesTo(int number)     {         return number.IsDivisibleBy(Three) || number.Contains(Three);     }     public string Apply(int number)     {         return Fizz;     } } Une bonne chose de faite! Je tourne mon attention vers la classe FizzBuzz. Même si j’ai sorti la génération des nombres entiers hors de cette classe, elle a encore trop de responsabilités: maintenir la liste des règles FizzBuzz, appliquer les règles qui conviennent, formater le résultat. C’est une violation évidente du principe de responsabilité unique (Single Responsibility Principle) de l’oncle Bob. Je décide donc séparer tout ce qui touche à la transformation FizzBuzz dans une classe à part. Les règles et tranformations FizzBuzz sont maintenant rendues dans la classe FizzzBuzzExtensions. J’ai changé l’implémentation pour utiliser LINQ étant donné que le résultat est plus compacte et tout de même très lisible. static class FizzBuzzExtensions {     internal static IEnumerable AsFizzBuzzSequence (this IEnumerable sequence)     {         return sequence.Select(numeral => Transform(numeral));     }     private static string Transform(int n)     {         return FirstRuleThatApliesTo(n).Apply(n);     }     private static IRule FirstRuleThatApliesTo(int n)     {         return Rules.First(r => r.AppliesTo(n));     }     private static readonly IRule[] Rules = new IRule[] {         new FizzBuzzRule(),         new BuzzRule(),         new FizzRule(),         new NumberRule()     }; } L’astuce d’encapsuler cette fonctionnalité en arrière d’une méthode d’extension facilite grandement la lecture de la classe FizzBuzz qui devient class FizzBuzz {     public string[] CountTo(int n)     {         return RespondWith(n.FirstNumbers().AsFizzBuzzSequence());     }     internal static string[] RespondWith(IEnumerable result)     {         return result.ToArray();     } } Séparer ainsi les responsabilités dans différentes classes a grandement augmenté la clareté du design. De plus l’utilisation de LINQ et des méthodes d’extension fait en sorte que le code est très compacte quoique très expressif. À vous d’en jugez. Voyez-vous d’autres pistes d’améliorations ?

gabriel bélanger

Previous post

Écoutez "le concept de la dette appliqué au développement logiciel" – nouveau podcast (en)

Next post

Vin, fromages et initiation au poker estimatif avec jean-rené et le PMI de Lévis-Québec (en)