Archive

Author Archive

FizzBuzz à la façon LINQ

February 15th, 2010 ernst perpignand posts profile No comments

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 ?

  • Share/Bookmark

A Coaching Session

January 29th, 2010 ernst perpignand posts profile No comments

Following is a conversation I had with a new Scum Master that had me thinking about how coaching works. It’s not about telling people what to do but allowing them to figure out what needs to bo done. One powerful tool to do just that is changing perspectives. Reliving the facts as they happened but from a different angle may generate insights that open new opportunities for the coachee. Below is the conversation as I remember it. I hope it generates some insights for you as well.

SM: So what do you think about the sprint planning the other day ?

Coach: Well one thing I noticed, and I did mention it on the spot, the team was about to bend some process improvement rules they agreed on implementing during the last retrospective. Your role as a Scrum Master is to make sure that the team follows its own process so that they may inspect and adapt it if need be. If the team partially implements the process in some cases and fully in some others, than interpretation of the results is more difficult and it will be harder to see what to improve next. You want to be careful when introducing changes to a process so that you know what you will be inspecting in the end.

SM: Well you are right. Sticking to our decision not to include big or unclear stories in the sprint made us realize that we were accepting to tackle underspecified functionality. No wonder our estimates were off and we were not able to complete all the stories in the sprint.

Coach: Good. But what about you ? What do you think about the sprint planning meeting ?

SM: Well, one thing that bugs me is that team members don’t participate in the process.

Coach: Oh? What do you mean ?

SM: I mean they don’t seem to care or are not motivated. For instance, in our Scrum Masters group we thought it would be a good idea to let developers take turn at the Scrum Master’s role in the daily meeting so that they realize what we are dealing with. When I asked my team if one of them would like to take my place I had to wait for a long time before someone reluctantly offered to conduct the daily.

Coach: So you want them to feel what it’s like to wear your shoes ?

SM: It’s not that. It’s just that team members are not getting involved. You witnessed what happened at the sprint planning meeting. When I asked them if they were able to commit on delivering the sprint backlog, I didn’t get an answer.

Coach: Well your organisation has a culture in which developers have been told what to do, when and how to do things. The change to self organisation is not going to happen overnight.

SM: I know. That’s why I want them to get involved in the process but they don’t want to get onboard.

Coach: And why do you think the don’t want to get onboard ?

SM: I don’t know. And that’s why I’m asking you this because I’m running out of ideas.

Coach: So… What are you doing that’s impeding them from getting involved in the process ?

SM: I’m not sure I understand the question… What do you mean ?

Coach: When you are not getting the response you’re expecting from others it’s often useful to consider that you are the one causing them to act the way they do. This empowers you to make some changes instead of waiting for others to change their behaviour.

SM: I’m not following you at all…

Coach: Ok. So let’s go back to that sprint planning meeting. Remember how the room was laid out and where everyone was sitting. You remember ?

SM: Yeah…

Coach: Let’s say you take John’s place. What do you see ?

SM: I’m really not sure where you want to go with this… But the only thing I see is that the monitor is too far away and its difficult to read.

Coach: Well that’s something to consider. And what are you doing in the meantime ?

SM: I’m updating the backlog and moving stuff around.

Coach: And how do you think John is perceiving this given the organisational culture ?

SM: I see… So we should probably use index cards that team members will be able to play with and update themselves.

Coach: Good! That way you solve two problems. By giving the team access to the sprint backlog you allow them to grasp more fully and own its content. They’ll probably feel more comfortable committing to it at the end.

SM (not really listening anymore): I can even update the backlog in the tool afterwards so that the flow is not interrupted. Thank you. That’s a great idea!

Coach: Well you know it wasn’t my idea. It’s yours. You just had to change your perspective on things.

SM (already leaving): Good. I’ll let you know how it turns out. Talk to you soon.

Coach (to himself): I’m not really sure what he saw exactly, but changing his perspective really allowed him to find new ideas on how to tackle this issue. This coaching stuff really works… I should blog this…

  • Share/Bookmark
Categories: Agile, Scrum Tags: ,

Comment une équipe Agile fait-elle pour s’assurer que les composants logiciels développés par différents membres vont s’arrimer entre eux?

August 12th, 2008 ernst perpignand posts profile 1 comment

Lors d’une journée portes ouvertes organisée par Pyxis pour rencontrer des candidats potentiels, plusieurs d’entre eux ont manifesté leur intérêt pour les méthodes Agiles dont la popularité ne cesse de grandir. Toutefois, quand nous leur avons présenté les fondements des méthodes Agiles − à savoir qu’une équipe Agile valorise plus les personnes et les interactions que les processus et outils, un logiciel fonctionnel qu’une documentation exhaustive, une collaboration avec le client que la négociation d’un contrat, une adaptation au changement que le respect d’un plan préétabli −, nous avons semblé prendre à contre-pied les façons de faire élaborées pour contrer le manque de professionnalisme causant la déroute d’un bon nombre de projets informatiques qu’un scepticisme très sain les a poussés à poser quelques questions pertinentes. L’une d’entre elles était la suivante : comment, sans processus et documentation, peut-on espérer que les composants développés par différents membres d’une équipe s’arrimeront correctement entre eux?

Ce n’est certes pas la question la plus importante lorsque l’on pense au développement logiciel en mode Agile, toutefois s’y attarder quelques instants nous révèle certaines caractéristiques fondamentales de nos pratiques que nous avons tendance à négliger à force de les appliquer machinalement. Faire cet exercice est intéressant car, en prétendant faire table rase des façons de faire traditionnelles, nous devons par la même occasion indiquer comment les méthodes alternatives proposées abordent les problématiques auxquelles sont confronté les méthodes traditionnelles. Sans aucune prétention méthodologique, nous allons tout d’abord nous intéresser à la manière traditionnelle d’assurer la cohérence des différents composants d’un logiciel pour ensuite montrer comment, à partir des valeurs du manifeste Agile mentionné plus haut (c.-à-d. fondements des méthodes Agiles), une équipe Agile réussit à atteindre le même objectif. Nous laisserons au lecteur le soin de juger de l’efficacité de chacune des approches.

Méthodes traditionnelles

Dans le but d’assurer la cohérence du logiciel développé, les méthodes traditionnelles s’appuient sur un processus rigoureux dont chacune des étapes utilise les résultats fournis par l’étape précédente et complète entièrement toutes les activités prévues pour valider les résultats qui seront fournis à l’étape subséquente. C’est l’approche classique des méthodes dites en cascade. L’expérience ayant montré que les coûts associés à la détection et à la correction des erreurs grandissent avec le degré d’implantation du logiciel, ces méthodes tentent d’en minimiser les impacts en précisant dans le moindre détail le travail à réaliser avant de procéder à l’étape suivante. Ainsi, en début de projet, tous les éléments requis sont recueillis, analysés et documentés dans le but de circonscrire le système. Par la suite, un design élaboré est conçu et validé théoriquement par rapport à l’ensemble des éléments requis. C’est généralement à cette étape que l’équipe s’attaque à l’arrimage des différents composants du logiciel. Ce design est alors expliqué dans un document d’architecture qui servira de référence pour les prochaines étapes d’implémentation, de tests et de déploiement. Ainsi, l’étape de design élaboré joue un rôle clé dans ce type de processus car beaucoup d’efforts intellectuels sont déployés pour arriver à l’architecture la plus complète, la plus cohérente et la mieux documentée possible. Le document d’architecture joue également un rôle crucial en étant le principal outil sur lequel l’équipe s’appuie pour s’assurer de la cohérence du logiciel.

Voilà pour les méthodes traditionnelles. Maintenant, comment les méthodes Agiles font-elles pour s’assurer de la cohérence du logiciel? D’entrée de jeu, rappelons que le rejet de processus, d’outils et de documentation comme nous le percevons à la lecture du manifeste Agile semble être une recette pour une tour de Babel. Effectivement, sans ces moyens de contrôle, comment espérer garder la cohérence?

Méthodes Agiles

Lorsqu’on leur pose cette question, les experts Agiles nuancent presque systématiquement leurs propos en disant que les méthodes Agiles n’excluent pas tout processus et toute documentation mais plutôt privilégient d’autres moyens tels l’interaction entre les différents partis et un logiciel fonctionnel. À mon sens, cette réponse, quoique juste, laisse beaucoup à désirer car elle ne fait qu’éviter la question. Nous ne savons toujours pas comment ces interactions assurent l’arrimage des différents modules qui composent le logiciel sur lequel les équipes Agiles mettent tant l’accent. Et si les méthodes Agiles s’en remettent de toute façon à des processus et à de la documentation lorsque le besoin se fait sentir, alors pourquoi tout cet engouement pour ces soi-disant nouvelles approches? Devant l’insistance des sceptiques, les experts fournissent souvent en guise de réponse la réplique suivante : les équipes Agiles misent beaucoup sur la communication face à face.

Communication face à face

En général, les membres d’une équipe Agile se rencontrent fréquemment pour faire le point sur l’avancement du projet. Dans le cadre de SCRUM par exemple, la mêlée quotidienne fournie l’occasion idéale pour que les problèmes d’incohérence soient exposés et les personnes concernées avisées. Ces dernières pourront par la suite s’attaquer à la résolution du problème de la manière qui convient. De plus, les fins d’itération fournissent une autre occasion à l’équipe de démontrer ce qui à été accompli et de réorienter les activités de développement en se basant sur ce qui a été appris. Précisons que ces occasions utilisent l’un des canaux de communication les plus riches, à savoir l’échange face à face qui, contrairement à la documentation écrite, favorise la participation de tous les membres de l’équipe. Par ailleurs, les développeurs d’une équipe agile improvisent fréquemment des séances de modélisation autour d’un tableau qui enrichit d’avantage la communication en permettant la visualisation des modèles évoqués. Cette communication est constante tout au long du projet et garde une certaine cohésion au sein de l’équipe.

Modélisation Agile

Pourtant, il y a un hic. Si l’échange face à face est le canal de communication le plus riche, il est aussi le plus volatile. Ce qui est convenu lors de ces rencontres, s’il n’est pas pérennisé dans un document quelconque, risque de ne pas survivre au-delà de la mémoire ou du départ des participants qui y étaient présents. De plus, en absence totale de documentation, l’arrivée de nouveaux membres dans l’équipe exige la reprise de ces conversations qui à la longue deviennent ennuyeuses. À cet égard, les principes de la modélisation Agile fournissent des pistes de solution quant à la production, à la conservation et à la mise à jour des modèles élaborés lors de ces rencontres. Et là, il faut tout de même faire attention de ne pas retomber dans les mêmes pièges des méthodes traditionnelles à savoir que le document est roi et unique détenteur de vérité.

Mais ne sommes-nous pas retournés à la case départ? Si nous faisons abstraction des autres avantages des méthodes Agiles et que nous nous concentrons uniquement sur la façon d’assurer la cohérence du logiciel, nous retrouvons-nous seulement avec plus de communication face à face et une manière plus légère de documenter? Est-ce suffisant pour garder le tout cohérent? À mon avis, c’est loin d’être certain et, heureusement, c’est là que les pratiques d’ingénierie Agiles viennent à la rescousse.

Pratiques d’ingénierie Agiles

En valorisant plus un logiciel fonctionnel qu’une documentation exhaustive, les équipes Agiles déploient beaucoup d’efforts pour s’assurer que tel est bien le cas en adoptant les pratiques d’ingénierie ci-dessous.

Tests automatisés

Habituellement écrits par les développeurs, ces tests vérifient que le code se comporte tel qu’il était prévu. On parle de la stratégie « tester en premier » lorsque les tests sont écrits avant le code qu’ils sont supposés valider. Cette stratégie est privilégiée pour les tests unitaires, qui vérifient le bon fonctionnement du code en isolation des systèmes externes tels qu’un serveur d’applications ou une base de données. Lorsque les tests sont écrits après le code, on parle de la stratégie « tester en dernier » qui est plus appropriée pour les tests fonctionnels ou d’intégration. Dans les deux cas, le fait que ces tests soient automatisés donne la possibilité à l’équipe de s’assurer de l’intégrité du logiciel à n’importe quel moment, et ce, à faible coût. De plus, cette batterie de tests constitue un filet de sécurité protégeant contre d’éventuelles défectuosités qui risquent d’être introduites par des modifications insouciantes apportées pour implémenter de nouvelles fonctionnalités requises. Par ailleurs, les tests automatisés, surtout lorsqu’ils sont écrits en premier, constituent une documentation toujours à jour du logiciel puisqu’en quelque sorte ils en spécifient le comportement. Finalement, on encourage chaque développeur à fournir avec ses modifications l’ensemble des tests validant leur comportement.

Intégration continue

Afin de minimiser les coûts d’assemblage du logiciel, les équipes Agiles ont l’habitude d’en automatiser complètement le processus. Pour profiter au maximum de cette automatisation, les tests de développeurs mentionnés plus haut sont également inclus dans ce processus de telle sorte qu’avec une ligne de commande il est possible de s’assurer de l’intégrité du logiciel avant de soumettre les modifications au dépôt central de code source. Toutefois, cela ne s’arrête pas là. Ce processus est exécuté sur un serveur d’intégration à chaque fois que des modifications sont soumises au dépôt de code source afin de valider l’intégrité du logiciel dans un environnement qui se rapproche de celui dans lequel il sera déployé. Par la suite, un indicateur de réussite est transmis aux membres de l’équipe qui sont ainsi informés en permanence de l’état de santé du logiciel. L’équipe a alors les outils en place pour s’engager à toujours maintenir le système en bonne santé en vérifiant le résultat du processus d’assemblage avant de soumettre les modifications au dépôt central et en corrigeant immédiatement les problèmes d’intégration qui sont révélés sur le serveur d’intégration. L’ensemble de ces pratiques constituent le mode d’intégration continue.

Propriété collective du code

Dans une équipe qui met en oeuvre les tests automatisés de développeurs et l’intégration continue, il arrive souvent qu’un développeur fasse échouer un test qu’il n’a pas écrit. Une modification est donc requise soit au test, soit au code associé à ce test car l’un ou l’autre ne correspond plus aux nouvelles règles d’affaires codifiées par les modifications. Ce test qui échoue a un impact sur l’échéance du projet car le développeur ne peut pas intégrer ces modifications au dépôt central étant donné qu’elles vont faire échouer le processus d’assemblage automatisé. Pour minimiser l’impact, les équipes Agiles pratiquent la propriété collective du code, qui permet à un développeur de modifier n’importe quelle partie du code même s’il n’en est pas l’auteur. Bien sûr, la batterie de tests existante rend possible cette appropriation du code d’autrui, et le développeur est encouragé à rajouter autant de tests qu’il faut pour préciser et valider sa compréhension du code en question.

La programmation en binôme

Une pratique Agile qui aide beaucoup à la propriété collective du code est la programmation en binôme. Cette pratique veut qu’il y ait toujours deux développeurs à travailler sur une partie du code. Par exemple, le développeur ayant fait échouer un test qu’il n’a pas écrit peut se faire accompagner par l’auteur original du code pour s’assurer que la logique initiale ne soit pas complètement dénaturée si ce n’est pas nécessaire. Cette pratique favorise également la dissémination de la connaissance du logiciel puisque les paires formées ne sont pas statiques. Chaque développeur a donc la possibilité d’apprendre plusieurs parties du code en compagnie de ceux qui les ont conçues tout en enrichissant les fonctionnalités du logiciel.

Conclusion

La synergie existant entre ces pratiques décuple leurs effets sur la production de code de qualité et, exception faite de la programmation en binôme, il est rare de voir une équipe Agile adopter l’une d’elles sans les autres. La raison en est fort simple : l’ensemble de ces pratiques permet de livrer de nouvelles fonctionnalités itération après itération tout en s’assurant de la bonne santé du logiciel. Dans le cas qui nous intéresse, une équipe Agile investirait sans doute dans l’écriture de plusieurs tests d’intégration qui démontreraient le bon comportement des différents modules. Ces tests seraient inclus dans un processus d’assemblage automatisé de façon à révéler toute anomalie au niveau de l’intégration des modules. Ce processus d’assemblage serait exécuté en mode d’intégration continue réduisant ainsi au minimum le temps de réaction de l’équipe qui s’engagerait à toujours maintenir le logiciel en bonne santé. Chaque membre de l’équipe aurait le droit de corriger toute partie défectueuse du logiciel et pourrait compter sur l’aide d’un coéquipier pour le faire. Alors, la prochaine fois qu’on vous demandera comment une équipe Agile, sans documentation et ni processus lourd, s’assure de la cohérence des modules développés par différents membres de l’équipe, vous pourrez répondre : tout simplement en s’assurant en permanence que les modules fonctionnent bien entre eux.

  • Share/Bookmark
Categories: Agile Tags: