Votre navigateur est obsolète !

Pour une expériencenet et une sécurité optimale, mettez à jour votre navigateur. Mettre à jour maintenant

×

Aurélien BOUDOUX

Développeur C# / .NET Freelance

Aurélien BOUDOUX
43 ans
Permis de conduire
MEYZIEU (69330) France
Situation professionnelle
Freelance
Indisponible
Présentation
Je suis développeur spécialisé dans les technologies Microsoft et les méthodologies Agile (avec une préférence pour le ScrumBan) et je suis toujours prêt à relever de nouveaux défis techniques et humains pour permettre à des passionnées de concrétiser leurs ambitions.

Mon parcours professionnel m’a mené à beaucoup travailler sur du client lourd (C# / WPF / Winform / SQL Server / Démons systèmes) mais je m’intéresse aussi très sérieusement la mise en œuvre d’applications web (ASP.NET MVC) ainsi qu’aux applications mobiles (XAMARIN)

Comme toute personne passionnée par son travail, je ne me sens satisfait que lorsque ce que je produis apporte une réelle valeur et provoque de l’enthousiasme chez les utilisateurs, c’est pourquoi j’apporte une grande importance aux méthodes éprouvées de qualités logiciels telles que le TDD, le BDD, les principes SOLID et les méthodologies Agile, qui pour moi sont les seuls moyens viables d’atteindre réellement ce résultat.


Visitez mon blog sur : http://aurelien.boudoux.fr
CV réalisé sur DoYouBuzz
Coding spree aurelien.boudoux.fr
Casse brique HTML5 en moins d'une heure avec Bridge .NET
20 sept. 2016

Cela fait plusieurs années que vous faites des applications de bureau en .NET / C# / Winform / WPF / SQL Server / EF, avec tous les patterns qui vont avec, et vous vous intéressez depuis peu au web ?

Si c'est le cas, vous avez surement découvert ASP .NET et son fameux MVC 4 et 5.

Lors de cette découverte, vous avez apprivoisé rapidement l'ensemble de l'écosystème coté "Backend" tant les concepts et méthodes sont proches de la programmation orientée objet traditionnelle.

Seulement voila, lorsque vous avez commencé a regarder la partie "Frontend", vous n'avez pas pu vous empêcher de gerber tant les principes et outils qui dominent sont chaotiques, improductifs, soumis au monde de la bidouille et sans aucune foi ni loi.

On vous a expliqué que pour faire une IHM, il fallait connaitre au minimum 2 syntaxes (HTML et CSS). Pourquoi pas...

Ensuite, on vous a expliqué que comme ces langages créent du contenu statique, il fallait faire du javascript pour les manipuler afin de rendre le contenu plus dynamique. (ça se complique, mais la démarche est compréhensible)

Sauf que le javascript est un langage interprété, avec une syntaxe franchement pas très net (des fonctions auto-appelantes qui sont utilisées comme des objets, aucun typage fort, des notions obscures, une exécution en fonction de l'ordre de déclaration des instructions, aucune vérification de syntaxe, ..., tout ce qu'il faut pour se tirer des balles)

Bref, c'est tellement n'importe quoi qu'il sort tous les jours une quantité incroyable de frameworks et outils pour combler les lacunes de javascript. Le pire, c'est que javascript est tellement ignoble que les frameworks javascript modernes préconisent de ne pas utiliser directement javascript ! mais plutôt des langages de scripts plus propres (avec typage fort et orienté objet) qui seront ensuite "traduit" en javascript.

Cette action de traduire un langage de programmation vers un autre s'appelle la transpilation.

Sachant cela, on est en droit de se poser les questions suivantes :

  • Si la tendance du moment consiste à utiliser des méta-langages pour transpiler du javascript, pourquoi s'embêter à apprendre l'un de ces langages (ou le javascript lui même) plutôt que d'utiliser notre langage préféré ?
  • N'existerait-il pas un transpileur de C# vers javascript histoire de gagner du temps ?

Ces questions sont parfaitement légitimes, surtout que ça permettrai aux développeurs C# que nous sommes d'avoir un seul langage pour maintenir le code de nos applications web.

J'ai donc fait pas mal de recherches sur le sujet, lorsque je suis tombé sur une vidéo qui m'a bien fait réfléchir.
Son nom : HTML5 Pong in 20mn With C#

Dans cette vidéo, l'auteur propose de développer le mythique jeu "PONG" en HTML5 sans taper une ligne de javascript grâce au transpileur open source et gratuit Bridge .NET

Devant une telle performance, je ne pouvais qu'essayer de réitérer l'exploit par moi-même.

Et à ma grande surprise, l'essai a été transformé, alors que je ne connais presque rien au triptyque HTML/CSS/Javascript... j'ai réussi grâce à mes simples compétences en C# (et un peu de HTML/CSS) à développer un jeu de casse brique en moins d'une heure !

Comme quoi, d'autres alternatives sont possibles...

Pour voir la vidéo, c'est par ici



Vous pouvez aussi jouer à ce jeu en ligne en cliquant ici : http://deck.net/9b1f63250be234abaf8562836c495dab/full

Have fun !
Développez facilement des services Windows configurables et robustes avec SystemHell
21 juin 2016
Si le monde informatique est plein de logiciels qui sont en interaction direct avec l’utilisateur, d’autres types de programmes s’exécutent dans l’ombre et permettent le bon fonctionnement de plusieurs services indispensables.

Ces logiciels fantômes qui servent à traiter vos mails, acheminer vos documents vers une imprimante, lancer des sauvegardes automatiques et bien d’autres choses sont appelés dans le jargon informatique des « démons systèmes ». Ils travaillent en arrière-plan et ont pour vocation à faire des traitements critiques, il est donc primordial que ceux-ci soient les plus robustes possibles.

Or, la plupart du temps, les concepteurs de ces programmes n’appliquent pas les bonnes pratiques comme ils pourraient le faire avec des logiciels de gestion traditionnels, et ce pour plusieurs raisons :
  • Les programmeurs travaillent souvent seul sur un démon système
  • Le .NET framework fourni des API natives très peu adaptées à la mise en place de design SOLID
  • Les démons systèmes sont (de premier abord) des programmes simples qui ne font que des choses répétitives.
De plus, il n’existe pas vraiment de bonnes pratiques quant à l’exploitation d’un démon système, ainsi chaque programmeur y va de sa créativité (enregistrement de logs dans des fichiers ou dans l’observateur d’événements, interface de contrôle obscure, configuration sous forme de fichier XML, INI ou base de registre, suivi des opérations à travers un fichier de sortie, …)


Tout cela peut rendre complétement fou ceux qui sont chargés d’installer, configurer et maintenir ces programmes, c’est pourquoi j’ai créé « SystemHell », un Framework Open Source permettant de mettre au point rapidement et facilement des services systèmes (démons) en .NET, avec l’avantage de donner plus de souplesse aux administrateurs pour les installer, les configurer et les maintenir.


Nous allons voir ensemble comment manipuler cet outil pour mettre sur pied des démons avec une vraie qualité professionnelle.


Principes de base


Un service système est un programme qui tourne en arrière-plan et qui doit être enregistré dans le SCM (Service Control Manager).


Service Control Manager
De ce fait, celui-ci est soumis à plusieurs contraintes :
  • Aucune interface graphique ou console de sortie ne permet de savoir ce que fait le service, à moins que le développeur n’en n’ait créé une adéquate.
  • Aucune norme ou principe ne fait foi pour installer ou configurer un service. Il existe donc une multitude de façon de faire ce qui complexifie la tâche des administrateurs.
  • Le débogage d’un service nécessite de s’attacher au processus pour pouvoir naviguer dans le code, ce qui s’avère très contraignant au quotidien.
Pour répondre à toutes ces problématiques, j’ai passé plusieurs mois à tester et sélectionner les outils et méthodes les plus simples qui formeront la base de l’interaction d’un démon développé avec SystemHell. Voici le résultat de cette réflexion :


En ce qui concerne l’interface graphique, le développeur doit disposer d’une fonction simple pour indiquer les informations qu’il désire présenter, et l’administrateur système doit pouvoir facilement disposer de ces informations pour les manipuler à sa convenance (recherche, enregistrement, filtrage, …)


Mon choix s’est donc porté sur l’outil DbgView (https://technet.microsoft.com/en-us/sysinternals/debugview.aspx) qui possède selon moins 3 avantages majeurs :
  • Le premier est sa taille : l’exécutable ne fait que 287Ko et ne nécessite aucune installation préalable. C’est donc un outil idéal pour accompagner un package de déploiement.
  • Le second est le nombre de possibilités que celui-ci a à offrir : Il permet de visualiser le flux d’informations comme dans un shell, d’appliquer des couleurs, des filtres, d’enregistrer les données selon une politique d’archivage, …
  • Le troisième est la simplicité d’utilisation pour le développeur : DbgView utilise le principe de l’OutputDebugString qui permet d’écrire dans un output spécifique à Windows les informations de debugging. Grace à ce mécanisme, un développeur n’a qu’à appeler la commande Trace.WriteLine pour afficher ses informations, sans se soucier des problèmes de concurrences d’accès.
En ce qui concerne les normes et principes qui permettent d’installer ou de configurer un service, mes choix se sont portés sur deux techniques :
  • Pour l’installation et la désinstallation, j’ai décidé de passer par l’outil sc.exe plutôt que par le couple classe d’installation / installutil.exe, car cela est d’une part beaucoup plus simple pour le développeur, et d’autre part beaucoup plus flexible pour l’administrateur. De plus, sc.exe étant disponible sur toutes les machines, un développeur peut aisément créer un outil d’installation qui se base sur celui-ci.
  • Pour la configuration du service, celle-ci se fera sans surprise par l’intermédiaire d’un fichier XML. Mais attention, le .NET Framework préconise de passer par un fichier de type app.config en utilisant les API qui vont avec, ceci ne sera pas du tout notre cas !
Quant à la problématique liée au débogage, SystemHell étant faiblement couplé avec les librairies qui feront office de démon, le développeur peut facilement utiliser toutes les méthodes de développement moderne (Test Driven Development, Behavior Driver Development, Domain Driven Design, architecture SOLID, …) afin de concevoir un système extrêmement robuste qui évitera de s’attacher à un processus externe lors de sa mise au point.

Fonctionnement de SystemHell


SystemHell.exe est un programme hôte qui s’exécute en tant que service système, et qui est capable de faire tourner un ou plusieurs démons dans leur propre contexte d’exécution.


Il peut donc faire tourner plusieurs démons différents ainsi que plusieurs instances d’un même démon en fonction de comment celui-ci est configuré.



Un démon est une DLL dont le nom se termine par « Daemon.dll », et qui doit être placée dans le même dossier que SystemHell.exe


Lorsque SystemHell démarre, il regarde dans son dossier s’il existe un fichier de configuration « Daemons.xml »


Si ce n’est pas le cas, SystemHell va rechercher dans son dossier toutes les DLL censées posséder un démon, et générer un « Daemons.xml » qui possédera la configuration pour une instance de chaque démon.

Comment développer un démon ?


Entrons tout de suite dans le vif du sujet. Pour ce faire, nous allons créer un démon simple puis le faire évoluer au fur et à mesure de nos besoins. Cela permettra de voir l’ensemble des possibilités offertes par SystemHell.


Allons-y :


Pour commencer, nous allons créer une nouvelle solution dans laquelle nous pourrons développer plusieurs démons. Nous l’appellerons « MyHellProject »


Création d'un solution vide MyHellProject

Nous allons créer dans cette solution un nouvelle DLL qui sera notre premier démon. Nous allons appeler cette DLL « HelloWorldDaemon »


Création d'une librairie HelloWorldDaemon.dll

Maintenant que nous avons une DLL, et que nous nous sommes bien assuré que le nom d’assembly se termine par le mot « Daemon », nous allons télécharger le package nuget « SystemHell » qui nous permettra de développer et de déployer notre démon.


Ouverture du gestionnaire de packages Nuget

Recherche du package SystemHell

Après avoir coché le projet « HellowWorldDaemon » et cliqué sur le bouton « Install », une référence à la librairie « SharedDaemonLib » s’ajoute à votre projet.


Ajout de la librairie SharedDaemonLib dans les références de la solution

A partir de là, nous allons pouvoir passer à la partie développement.


Pour commencer, nous allons créer une classe qui fera office de classe de configuration. Cette classe devra contenir tous les paramètres qui pourront être défini par l’administrateur pour faire tourner notre service.


Dans notre cas, nous allons mettre une phrase qui devra être écrite en boucle dans DbgView ainsi qu’un nombre de secondes qui définira la fréquence à laquelle afficher cette phrase.



public class HelloWorldConfiguration : DaemonModuleConfiguration
{
    private string _whatToSay = "Hello World !";
    private int _loopTimeInSecond = 5;

    public string WhatToSay
    {
     get { return _whatToSay; }
     set { _whatToSay = value; }
    }

    public int LoopTimeInSecond
    {
     get { return _loopTimeInSecond; }
     set { _loopTimeInSecond = value; }
    }
}

Comme vous pouvez le remarquer, notre classe doit hériter de la classe de base DaemonModuleConfiguration.


Une autre chose importante à savoir, c’est que toutes les propriétés de cette classe doivent être initialisées et accessibles en get / set. (Surtout pour les types string)


Si vous n’initialisez pas ces propriétés, elles risques de ne pas apparaître dans le fichier de configuration généré automatiquement par SystemHell. Il faudra donc que l’administrateur les connaissent par cœur pour les ajouter à la main.

Maintenant que nous disposons d’une classe de configuration, nous allons pouvoir créer le point d’entrée vers notre démon. Pour cela, nous allons ajouter une classe qui hérite de DaemonBase, et nous définirons en paramètre générique le type de notre classe de configuration défini plus haut.



public class HelloWorld : DaemonBase
{
 public override void Start(CancellationToken cancellationToken)
 {
  throw new NotImplementedException();
 }
}

Quand SystemHell va démarrer, il va appeler le Start de cette classe dans un nouveau Thread en lui passant un CancelationToken.

Pour ceux qui ne sont pas très familier avec la TPL, ce token permet de savoir si l’appelant a demandé à ce que l’exécution du Thread soit interrompu, donc à savoir si l’administrateur à fait une demande « Stop » sur le service.

Il est donc de la responsabilité du développeur de devoir gérer le CancelationToken.

Il ne reste plus qu’a implémenter notre démon comme ceci :

public class HelloWorld : DaemonBase
{
 public override void Start(CancellationToken cancellationToken)
 {
  DaemonStarted = true;
  do
  {
   WriteTrace("Daemon say : {0}", Configuration.WhatToSay);
  } while (!cancellationToken.WaitHandle.WaitOne(Configuration.LoopTimeInSecond*1000));
 }  
}

Quelques explications sur ce code s’imposent :


DaemonStarted est une valeur booleenne issue de la classe de base qui a pour rôle de notifier à SystemHell que le démon a démarré correctement.


Effectivement, comme la méthode Start à pour vocation à ne jamais rendre la main, SystemHell à besoin que vous définissiez cette valeur à "true" pour lui notifier que le démon à correctement démarré.


Nous arrivons ensuite dans une boucle do/while qui est conditionnée par l’attente d’un signal sur le CancelationToken. Si ce signal renvoi « true », la méthode Start sort de la boucle infinie et SystemHell termine l’exécution du démon.


Nous voyons aussi que ce code fait référence à une propriété de la classe de base appelée « Configuration ». Comme vous l’avez surement deviné, cette propriété correspond à la classe de configuration défini plus haut, à ceci prêt que SystemHell a injecté les données issues du fichier « Daemons.xml » qui est manipulable par un administrateur.


Pour mieux comprendre tous ces concepts, nous allons maintenant installer et configurer ce démon sur notre machine.

Installation et exécution de notre premier démon


Après avoir compilé « HelloWorldDaemon.dll », nous pouvons aller chercher le programme SystemHell.exe qui nous permettra de le hoster. Pour cela, faites un clique droit sur le projet, et choisissez le menu « Ouvrir le dossier dans l’explorateur »


Ouverture du dossier dans l'explorateur

Dossier ou se situe le projet HelloWorld

Remontez d’un dossier pour attendre « MyHellProject » puis allez dans le dossier « Package »


Navigation jusqu'au dossier Packages

Ce dossier contient tous les packages Nuget téléchargés pour notre solution. Allez dans le dossier « systemHell.x.y.z » puis dans le sous dossier « tools ». Vous trouverez un exécutable appelé « systemHell.exe »


Navigation jusqu'au dossier "Tools" qui contient SystemHell.exe

Copiez cet exécutable dans le dossier où vous désirez installer votre service système, et profitez-en pour y placer votre dll « HelloWorldDaemon.dll » compilée précédemment, ainsi que ses dépendances.


Copie de l'exécutable dans un dossier

Ouvrez maintenant un interpréteur de commande en mode « Administrateur », puis tapez la commande suivante pour installer le service sur votre ordinateur :


Installation du service "HelloWorld" avec "sc.exe"

Attention : il doit y avoir un espace entre binPath= et le chemin vers SystemHell.exe


Normalement, une fois cette commande exécutée, vous trouverez un service nommé « HelloWorld » dans votre SCM.


Présence du service "HelloWorld" dans la SCM

Très bien.


Démarrez le service « HelloWorld » grâce à la SCM. Normalement un message d’erreur indique que le service n’a pas pu démarrer.


Message d'erreur suite au démarrage du service "HelloWorld"

Pour comprendre un peu plus ce qu’il se passe, vous pouvez aller dans l’observateur événements


Erreur SystemHell dans l'observateur d'événements

L’erreur indique que le service s’est arrêté car il n’y avait pas de fichier de configuration. Un fichier a été généré dans « c:\temp\test\Daemons.xml », et il suffit d’aller le configurer puis de relancer le service.


Ouvrons ce fichier de configuration pour voir ce qu’il contient.


Fichier de configuration généré par SystemHell pour HelloWorld

Voici comment est structuré ce fichier :


Le nœud « Modules » contient un ou plusieurs nœuds « Daemon » qui représentent pour chacun une instance de démon à charger.


Un nœud « Daemon » possède les attributs suivants :


Name : représente le nom du démon, celui-ci doit être unique dans le fichier « Daemons.xml »


Actif : Défini si SystemHell doit exécuter ce démon. Attention, lorsque le fichier de configuration est auto généré, tous les démons ont l’attribut « Actif » à « false » par défaut. Pensez à le passer à « true » sans quoi vous constaterez que votre démon ne démarre pas.


Assembly : Défini le nom fort de l’assembly qui possède le code du démon. Cette assembly doit être située dans le même dossier que « SystemHell.exe ». Attention : Comme vous pouvez le constater, le nom fort de l’assembly possède le numéro de version. Si vous avez besoin d’attribuer des numéros version à votre démon, modifiez le numéro de version du fichier, et non celui de l’assembly, sinon 
SystemHell risque de ne pas pouvoir charger l’assembly de votre démon.



Modification du numéro de version de fichier plutôt que celui de l'assembly


Le sous nœud « Configuration » possède les propriétés que nous avons créées dans notre classe « HelloWorldConfiguration. ». Attention, si vous ajoutez de nouvelles propriétés dans votre classe, pensez bien à les répercuter à la main dans le fichier de configuration.


Maintenant que nous avons vu comment était constitué ce fichier, nous allons le manipuler pour bien comprendre comment celui-ci fonctionne.


Pour commencer, nous allons définir l’attribut « Actif » de notre démon à « true », pour notifier à SystemHell que ce démon doit être chargé.


Activation du démon HelloWorld dans le fichier de configuration

Maintenant procurez-vous l’exécutable DbgView disponible sur le site de Microsoft à l’adresse suivante : 

https://technet.microsoft.com/en-us/sysinternals/debugview.aspx

NB : si le lien de téléchargement est mort, faites une recherche de « dbgView » sur Google, vous le trouverez très facilement.


Une fois l’exécutable obtenu, lancez-le en mode « Administrateur » puis allez dans le menu « capture » et assurez-vous que l’option «Capture Global Win32 » est active.


Activation de l'option "Capture Global Win32" dans DebugView

Retournez dans la SCM, puis lancez le démarrage du service « HelloWorld »


SystemHell démarre notre démon dont l’activité est visible dans la console « DbgView »


Visualisation de l'activité du démon dans DebugView

Arrêtons notre démon pour faire un petit changement dans le fichier de configuration


Changements dans le fichier de configuration

Redémarrons le service « HelloWorld » pour voir ce qu’il se passe


Activité du démon "HelloWorld" après redémarrage

Comme vous pouvez le constater, il est extrêmement simple de configurer notre démon avec le fichier XML !


Maintenant, nous allons créer deux instances de notre démon pour que l'in répète en boucle "bonjour" toutes les 10 secondes  et l"autre "au revoir" toutes les 5 secondes.

Pour cela, il suffit de dupliquer le nœud « Daemon », et d’appliquer des configurations différentes. Attention à bien faire en sorte que l’attribut « name » soit unique dans le fichier de configuration.


Configuration pour activer deux instances du démon "HelloWorld"

Visualisation de l'activité des deux instances du démon "HelloWorld"

Et voilà, vous venez de créer votre premier démon. Celui-ci est paramétrable, configurable et administrable très facilement.


Allons un peu plus loin...


Les commandes personnalisées

Un service Windows à la possibilité de recevoir des commandes personnalisées via la commande « sc control », c’est-à-dire que nous pouvons demander à notre démon d’effectuer certaines actions à la demande de l’administrateur.


Pour implémenter cette fonctionnalité, rien de plus simple, il suffit de surcharger la méthode « OnCustomCommand » dans notre démon.


Par exemple, si nous reprenons le code de notre démon « HelloWorld », voici comment nous pourrions le modifier.



public class HelloWorld : DaemonBase
 {
  public override void Start(CancellationToken cancellationToken)
  {
   DaemonStarted = true;
   do
   {
    WriteTrace("Daemon say : {0}", Configuration.WhatToSay);
   } while (!cancellationToken.WaitHandle.WaitOne(Configuration.LoopTimeInSecond*1000));
  }

  public override void OnCustomCommand(int command)
  {
   WriteTrace("la commande numéro {0} a été reçue.", command.ToString());
  }
 }

Comme vous pouvez le constater, une commande est un numéro passé en paramètre. Le MSDN précise que ce numéro doit être compris en 128 et 255, les numéros en dessous de 128 étant des valeurs réservées au système d’exploitation.


Envoyons une commande à notre service « HelloWorld » à l’aide de la commande « sc control », et observons ce qu’il se passe :


Envoi de la commande 150 au service HelloWorld

Réception de la commande 150 par SystemHell

Nous voyons dans DbgView à la ligne 6 que SystemHell a reçu la commande « 150 » que nous lui avons envoyée, et celui-ci l’a transféré à tous les démons qu’il héberge.


Activer ou désactiver des instances de démon sans redémarrer SystemHell

La commande personnalisé numéro « 200 » est une commande spéciale qui permet à SystemHell d’activer ou désactiver un démon sans avoir besoin de le redémarrer !


C’est-à-dire que nous allons pouvoir modifier notre fichier de configuration « Daemon.xml » pour ajouter de nouveaux nœuds ou jouer sur les attributs « Actif » de nos démons existant, puis appliquer nos modifications via la commande « sc control mon_service 200 »


Regardons comment ça se passe dans la pratique avec notre démon HelloWorld.


Nous allons partir du principe que le service HelloWorld héberge 2 démons. Notre but est de désactiver le démon « HelloWorld_2 » sans redémarrer le service.


Visualisation de l'activité des deux instances de démon

Ouvrons le fichier configuration, et passons l’attribut « Actif » de « HelloWorld_2 » à « false »


Modification du fichier de configuration pour désactiver HelloWorld_2

On envoi maintenant une commande 200 à notre Service HelloWorld pour que SystemHell puisse appliquer la modification.


Envoi de la commande 200 au service "HelloWorld"

Observons l’impact de cette commande dans « DbgView »

Visualisation de l'impacte dans dbgview

Nous voyons à la ligne 222 que SystemHell a procédé à un rechargement des démons. Après cette opération, seul le démon HelloWorld_1 est fonctionnel.


Notez que nous aurions atteint le même résultat si nous avions supprimé le nœud XML du démon « HelloWorld_2 » dans le fichier de configuration.


Nota Bene : La commande « 200 » permet à SystemHell d’activer ou désactiver des démons sans redémarrer le service. Cette commande ne permet pas de recharger une nouvelle configuration pour un démon en cours d’exécution.


Si votre service héberge plusieurs démons et que vous avez besoin de modifier la configuration de l’un d’eux sans avoir à redémarrer le service, passez l’attribut « Actif » à « false », changez les informations de configurations souhaitées, appelez la commande 200 pour arrêter le démon, repassez l’attribut « Actif » à « true » puis rappelez la commande 200 pour redémarrer le démon.

Les fichiers de configurations complexes

Il arrive parfois qu’il soit nécessaire de structurer notre configuration pour que celle-ci soit plus facilement manipulable, ou qu’elle représente beaucoup de données. 

Dans ce cas, il devient nécessaire d’ajouter des classes et des sous-classes à notre classe de configuration.


Ceci est bien sûr possible avec SystemHell : pour cela rien de plus simple, il suffit d’ajouter des classes à votre classe de configuration qui contiennent l’attribut « Serializable »


Exemple :


public class HelloWorldConfiguration : DaemonModuleConfiguration
    {
     public HelloWorldConfiguration()
     {
      SubData = new Subclass1();
     }
     private string _whatToSay = "Hello World !";
     private int _loopTimeInSecond = 5;

     public string WhatToSay
     {
      get { return _whatToSay; }
      set { _whatToSay = value; }
     }

     public int LoopTimeInSecond
     {
      get { return _loopTimeInSecond; }
      set { _loopTimeInSecond = value; }
     }

  public Subclass1 SubData { get; set; }     
    }

 [Serializable]
 public class Subclass1
 {
  public Subclass1()
  {
   MyData = new Subclass2();   
  }
  private string _property1 = "this is property 1";
  private int _property2 = 1;

  public string Property1
  {
   get { return _property1; }
   set { _property1 = value; }
  }

  public int Property2
  {
   get { return _property2; }
   set { _property2 = value; }
  }

  public Subclass2 MyData { get; set; }
 }

 [Serializable]
 public class Subclass2
 {
  private string _myPoperty = "this is my property";

  public string MyPoperty
  {
   get { return _myPoperty; }
   set { _myPoperty = value; }
  }
 }


Voici ce que cela donne lorsque le fichier de configuration est généré par SystemHell :


Fichier de configuration complexe

Notez que les nœuds XML du fichier de configuration correspondent aux noms des propriétés, et non au nom des classes.


Pour que SystemHell puisse générer le XML correspondant à vos sous-objets, faites bien attention à ce que vos propriétés soient déclarées en « public », et que les objets soient bien initialisés dans le constructeur.


BadImageFormatException, ou comment passer SystemHell en x86

SystemHell.exe est un exécutable compilé en « Any CPU », c’est-à-dire que si celui-ci est lancé sur un système d’exploitation en 64 bits, il s’exécutera en 64 bits, sinon il s’exécutera en 32 bits (x86 pour les intimes)


Parfois, il arrive que nous mettions au point un démon qui utilise une dépendance compilée en x86, ce qui nous force à configurer la compilation de notre DLL en conséquence.


Définition de la plateforme cible du démon en x86

Dans ce cas, il est fort probable que SystemHell ne puisse pas démarrer à cause de l’erreur « BadImageFormatException »


Pour régler ce problème, nous allons devoir appliquer une astuce pour forcer SystemHell.exe à s’exécuter en 32 bits à l’aide de l’outil « corflags.exe ».


Cet exécutable se trouve en plusieurs exemplaires dans « programfilesx86\microsoft SDKs »


Recherche de l'outil CorFlags.exe sur le disque dur

Si vous ne le possédez pas sur votre machine, une simple recherche sur internet vous permettra de vous le procurer.


Une fois que vous avez mis la main sur cet exécutable, utilisez le pour vérifier les propriétés de SystemHell.exe


Consultation des flags CLR de SystemHell avec CorFlags.exe

Nous voyons ici que SystemHell n’est pas en 32 bits, heureusement nous pouvons l’y forcer en utilisant la commande « CorFlags.exe /32BIT+ SystemHell.exe »


Forcage du flag 32 bits sur SystemHell

Une fois la commande exécutée, nous pouvons voir que le flags « 32 bits » a bien été appliqué.


Consultation des flags CLR de SystemHell avec CorFlags.exe

Voilà, vous pouvez maintenant lancer SystemHell pour que celui-ci exécute votre démon 32 bits sans problème.


Exemples de produits


Voici quelques projets professionnels basés sur SystemHell pour vous montrer ce qu'il est possible de faire.

Astread

Page d'accueil de astread.com

http://www.astread.com est un service en ligne gratuit qui permet de convertir un livre électronique en livre audio. Celui-ci compte à l’heure actuelle plus de 4600 utilisateurs qui ont déjà généré environ 70000 heures d’écoute.

La génération de livre audio est basée sur un démon SystemHell qui reçoit un ordre depuis le site.

Une fois la demande gérée, le démon post une réponse après avoir créé et déposé le livre audio dans un endroit approprié à son téléchargement.


Visualisation de l'activité SystemHell de www.astread.com

Le démon embarque un service WCF pour communiquer avec le site web, et différent paramètres permettent de lui indiquer où générer les documents audio, et comment gérer la parallélisation du travail en fonction de la machine hôte.


Fichier de configuration SystemHell de astread

Avec SystemHell, Astread est capable de gérer sa monté en charge grâce à la mise en place rapide de nouveaux démons sur des machines virtuelles.


Le système tourne depuis 6 mois non-stop, et a souffert récemment d’un problème qui a été rapidement identifié.

Effectivement, un dead lock dans la librairie « DotNetZip » faisait planter la génération de certains fichiers audio. L’architecture du démon a permis de corriger et déployer très rapidement une nouvelle version en production.

Connecteur HL7

HL7 est une norme de communication dans le monde de la santé qui permet de transmettre des informations entre plusieurs systèmes logiciels au sein de cabinets, de cliniques ou d’hôpitaux.


Pour ce faire, l’éditeur qui désire pouvoir être compatible HL7 doit créer un connecteur capable de générer des messages au format texte en fonction d’événements qui se produisent dans le système d’information (admission d’un patient, création du compte rendu, facturation, …)


Une clinique ou un hôpital peuvent recevoir plusieurs centaines de patients par jour, et chaque patient génère une dizaine d’événements qui doivent être envoyés en HL7 à des tiers, il est donc primordial que le connecteur qui tourne en fond de tâche soit extrêmement robuste pour ne pas perturber le worflow de prise en charge.


Or, HL7 est une norme très complexe qui a bénéficié de nombreuses évolutions. Le connecteur doit donc pouvoir gérer plusieurs versions de la norme et s’assurer à chaque évolution qu’aucune régression n’a été introduite dans le code.


Grace à SystemHell, nous avons pu écrire en quelques semaines un connecteur HL7 capable de gérer plusieurs profils dans 2 versions de la norme. La grande souplesse du framework a permis de mettre en place des tests unitaires ainsi que des scénarios en « Behavior Driven Development » pour valider automatiquement le bon fonctionnement du connecteur à chaque modification de code.


Le déploiement et la configuration du connecteur ont ainsi été grandement simplifiés, ce qui a permis aux installateurs de s’approprier l’outil très rapidement, et de pouvoir mettre en place plusieurs types de stratégies en fonction du contexte d’installation (création de services séparés, création d’un seul service, ciblage de plusieurs types d’appareil, suivit et diagnostique des anomalies, …)


Exemple de scénarios BDD du connecteur HL7

Exemple de fichier de configuration du démon HL7

Ce démon est actif dans plusieurs cabinets de radiologie et a permis d’acheminer plusieurs millions de messages sans jamais défaillir.


Système de dictée numérique

Des utilisateurs qui font de la dictée numérique désiraient que leurs fichiers son puissent être placés automatiquement dans une GED (Gestion Electronique de Document), un répertoire, un partage réseau ou un serveur FTP.


De plus, le fichier son pouvait provenir soit d’un dossier faisant office de puit de fichiers, soit depuis un appareil spécifique qui agissait comme une clé USB.


La multitude de configurations et le mode fond de tâche devant tourner 24/24 étaient tout indiqués pour créer un démon SystemHell dont voici le fichier de configuration :


Fichier de configuration du démon de dictée numérique

Comme vous pouvez le constater, SystemHell permet de créer des fichiers de configuration très complexes, selon la stratégie choisie par le développeur.


Dans notre cas, le système possède deux concepts : des « watchers » permettent de configurer comment surveiller une entrée de fichier son (mode répertoire ou USB) et des « Behaviors » permettent de spécifier comment le démon doit se comporter lorsque un fichier a été détecté par le « watcher » (envoi dans une GED, un répertoire ou du FTP).


Ainsi, le démon peut être installé soit sur un serveur, soit directement sur un poste utilisateur en fonction du besoin final.

BubbleBugs

Page d'accueil du site www.bubblebugs.net

BubbleBugs est un site permettant de faire remonter les erreurs qui se produisent dans des applications « .NET » directement dans un serveur TFS ou VSO sous forme de WorkItem (http://www.bubblebugs.net)


Pour ce faire, l’utilisateur doit s’inscrire sur le site et donner les informations de connexion vers son dépôt de fichiers source, puis intégrer une librairie à son application.


Lorsque un utilisateur a validé les informations de connexion à son dépôt, un nouveau nœud XML est créé dans le fichier de configuration SystemHell, et une commande 200 est envoyée pour démarrer un démon spécifique qui a la charge de surveiller un dossier, et créer un workitem correspondant chaque fois qu’un nouveau fichier apparaît dans celui-ci.



En parallèle, un autre démon qui tourne dans le même service à la responsabilité de recevoir les erreurs issues des applications en ligne, et de déposer dans l’un des dossiers correspondant le fichier qui contient tous les détails de l’erreur.


Ce site exploite toutes les possibilités offertes par SystemHell : il combine deux démons différents qui travaillent de concert, et permet de créer une nouvelle instance à la volé pour chaque inscription sur le site, sans redémarrer le service.


Fichier de configuration SystemHell du site bubblebugs

Visualiation de l'activité du démon BubbleBugs

Conclusion


Les services Windows sont des applications spécifiques dont la robustesse et la maintenabilité ne sont pas négociables, il est donc nécessaire de mettre toutes les chances de son côté lorsque vous devez en développer un.


En utilisant SystemHell, vous serez en capacité de créer rapidement des services Windows facilement testables et configurables tout en assurant la scalabilité de votre architecture.


SystemHell est un Framework Open Source accessible sur Github à l’adresse https://github.com/aboudoux/systemHell



Si vous avez besoin de conseils pour votre architecture de services Windows, laissez donc un commentaire ! Je serai ravi d’en discuter avec vous.



Comment réanimer un projet de développement logiciel laissé pour mort - Retour d'expérience - Partie 1
10 janv. 2016

Plutôt que de lire, écoutez cet article avec ASTREAD

Chaque jour dans le monde, des milliers de personnes se jettent dans un nouveau projet de développement logiciel : Que ce soit une startup qui veut lancer un concept avec son site web, une entreprise qui veut développer un logiciel de gestion ou un étudiant qui désire créer une application mobile, tous voient en la technologie un moyen simple et rapide de réaliser leurs ambitions.

Mais savez-vous que selon « l’observatoire des projets » qui a réalisé une étude sur le taux d’échec dans le domaine de l’informatique en 2012, 55% des responsables sondés estiment qu’entre 16% et 50% de leurs projets stratégiques sont purement et simplement abandonnés ! 

L’histoire que je vais vous raconter parle de l’un de ces millions de projets qui a bien failli finir dans les oubliettes de l’informatique, tout en entrainant avec lui l’avenir d’une société et des salariés qui la composent.

Loin d’être un cas isolé, nous allons voir comment ce projet hautement stratégique a lentement dérivé, pour arriver à une situation tellement catastrophique que tous ceux qui y ont participé de près ou de loin s’en sont retrouvés profondément affectés.

Comme la fin du projet signifiait la fin de la société, il n‘était pas question pour la direction de l’abandonner. 
C’est dans ces conditions difficiles que j’ai été désigné pour en reprendre les rênes.

Or, l’équipe était tellement accablée par cet échec qu’elle ne pouvait accepter de recommencer.
Seule une profonde remise en question des méthodes de travail tant sur le plan technique que humain pouvait les convaincre de remettre le couvert.

J’ai donc été obligé de revoir ma façon de penser et d’aller à contre-courant de tout ce qui se faisait jusqu’alors, allant parfois à l’encontre d’un certain bon sens et des directives données par les dirigeants même de la société ! 

Et figurez-vous que ça a marché.  

Je vous propose donc de vous faire revivre cette expérience qui a changée à jamais notre façon de gérer des projets de développement…


Prologue


L’histoire commence en l’an de grâce 2008. Cela fait déjà 4 ans que je suis patron de ma société, voire plus exactement l’esclave de mon banquier, et mon activité consiste à mettre en place des réseaux d’entreprises et à développer des logiciels spécifiques en C++.

Un jour, un de mes contacts me présente une personne qui semble être dans la misère, car celui-ci s’est fait pirater son serveur et il ne sait pas comment s’en sortir. Étant toujours en quête de nouveaux clients, j’interviens un weekend et dépanne le type pour quelques euros.

Pendant ma prestation, il s’avère que lui aussi passionné de C++ et il en profite pour me présenter quelques un de ses travaux. Le niveau des échanges est très intéressant car le gars est un bon développeur chevronné.
Après tout cela, je lui laisse mes coordonnées et retourne à mes occupations…

Quelques mois plus tard, le gars me rappelle. La société dans laquelle il travaille a besoin d’un nouveau logiciel de gestion de contacts, et il voudrait savoir ce que je peux lui proposer. 

En bon commercial, je lui explique qu’il existe certainement de très bons produits sur le marché, mais que je ne suis pas revendeur. Par contre, s’il désire un développement spécifique : je suis son homme.

L’idée germe dans son esprit et il me demande un devis, chose que je fais sans problème. Je prévois quelque mois de travail pour une petite appli C++ avec une base de données SQLExpress. Après quelques négociations, il m’explique qu’il faudrait que le système soit en capacité d’interagir avec plusieurs outils tels qu’Exchanges, Outlook, Word ainsi que certains Webservices… (Liste non exhaustive)

Les choses se compliquent car le C++ n’est pas très cordial lorsqu’il s’agit d’interopérabilité. Il existe bien des librairies et autres composants COM, mais les quelques essais que je fais montrent que les choses vont rapidement devenir très pénibles.

Le C# semble tout indiqué pour ce type de besoin. J’étudie la question et je découvre un langage élégant et productif qui respecte parfaitement tous les principes de la POO. Je recontacte le gars et lui vend du rêve vis-à-vis de C#. 

Il est d’accord pour signer un nouveau devis mais il impose des conditions :
  • Il veut être le chef de projet sur ce dossier 
  •  Il veut participer activement au développement du produit avec moi
Ayant toujours été habitué à travailler seul et en mode cowboy, je me dis que ça me fera une très bonne expérience : alors BANCO !

Le projet débute et nous découvrons tous les deux le C#. Ayant pas mal d’expérience en C/C++ (7 ans pour ma part et 15 ans pour la sienne) nous avançons très rapidement. Lorsque l’on vient des langages de programmation bas niveaux, faire du C# est un vrai bonheur, et nous étions tous les jours émerveillés par la richesse du .NET Framework.

Une fois que nous avions fait le tour de la question vis-à-vis de nos besoins, en bon petit cowboy je crée un nouveau projet et commence à vouloir taper du code. Lorsque le gars voit ça, il m’arrête tout de suite : Mais qu’est-ce que tu fais ? N’a tu donc jamais fais de la vraie gestion de projet ?
Il m’explique les principes du cycle en V et l’importance de la conception en amont. Il en profite pour me présenter son document de conception : une véritable bible de plusieurs centaines de pages qui détaille tous les rouages de notre future application.

En lisant ce document, je découvre que nous sommes très loin de la simple application de gestion que j’avais imaginée : Le projet va en fait consister à créer une espèce de RAD (Rapid Application Development) qui intègre en natif des concepts de verrouillages pessimistes, de gestion de droits modulaire, de push et de grilles intelligentes. Bref, le mec est tellement un fondu de code qu’il a réussi à se faire financer par sa boite un de ses vieux délires sous couvert d’un projet banal. 

Bien sûr, à la découverte de toutes ces choses, j’ai évidemment demandé à ce que le budget soit rallongé : ça n’a visiblement pas posé de problème. Donc tout allait pour le mieux.
Le projet débute et le gars me prend sous son aile, il devient pour moi une sorte de mentor qui m’apprend les choses qu’un vrai codeur professionnel doit savoir : L’importance de faire des documents de conceptions et de spécifications, de l’architecture, de l’optimisation, les design patterns. C’est une époque où je suis encore jeune sans femme ni enfant, je m’investis donc à 200% en faisant des horaires de dingue et en lisant énormément de livres.

Après une bonne année de travail, le logiciel de gestion de contact est devenu un véritable monstre qui fait papa maman avec une pure architecture de barré ! Le démarrage de l’application met plusieurs minutes tellement il y a de modules génériques à charger, et de gros points faibles sont à déplorer sur les performances de SQLExpress 2005 (qui à l’époque possède des limitations de 1 CPU / 1 thread, 1 Go de RAM et 4 Go par base).

Mais bon, le Framework est sur le plan technique une vraie tuerie. Tellement convaincant que le gars en fait une présentation à l’un de ses amis qui est le co-fondateur et directeur R&D d’une société d’édition de logiciel. La présentation n’est pas désintéressée car cette société à une véritable problématique de vieillissement de son produit phare, et elle doit faire un choix stratégique majeur pour changer de technologie !

Je n’étais pas présent à cette démonstration, mais j’imagine que le CTO a dû en prendre plein les yeux : Tu glisses une grille dans ton designer, tu mets dans les propriétés les colonnes que tu veux afficher, et pouf, tout se génère tout seul ! Pour l’accès concurrentiel ? Pas de problème c’est géré en natif ! Pour le rafraichissement des données ? Le système envoie des notifications push pour que les formulaires soient toujours à jour ! Gestion de droits ? no problem, les modules et menus sont affichés en fonctions des rôles SQL paramétrés (donc couplables avec un AD) ! En termes de productivité ? Avec ça un stagiaire payé 10K te refait ton application en un temps record ! Pour la pérennité ? Tout est en .NET, ça ne risque pas de bouger ! Pour le déploiement ? C’est basé sur « ClickOnce », en un clic ton application est disponible all over the world ! Etc.

Bref, comme je l’ai déjà dit, je n’étais pas présent, donc j’imagine que les choses se sont passées ainsi. Mais une chose est sûre : l’ancien logiciel de cette société était fait avec un RAD, donc les arguments d’un système en .NET qui permet de créer des applications de gestion en un minimum de temps et de codes ont forcément touché l’âme et le cœur de son auditoire.

Après quelques temps, le gars m’explique qu’il a fait une démonstration de notre Framework, et que la société serait intéressée pour l’embaucher en tant que chef de projet pour mener à bien son virage technologique, et il me demande si je suis intéressé pour quitter ma boite et le rejoindre dans l’aventure.
Comme évoqué au début, j’étais l’esclave de mon banquier. J’ai donc vu en cette proposition une véritable aubaine pour briser mes chaines !
Les choses étant entendues, je suis mis dans la boucle des conversations emails qui se portent sur ce nouveau projet.  

Du côté de l’éditeur, ils veulent disposer d’un RAD propriétaire pour ne plus être dépendent de leur solution actuelle, car non seulement celle-ci leur pose plusieurs problèmes de cout, d’évolutivité et de maintenabilité, mais en plus ils sont pieds et poings liés avec la société qui édite ce RAD.
Du côté du chef de projet, il explique comment les choses vont s’articuler autour de plusieurs axes :
  • Axe organisationnel : le projet sera géré selon les bonnes pratiques en vigueur, à savoir un cycle en V. Il faudra donc que l’éditeur puisse fournir un cahier des charges exhaustif ainsi que des spécifications très détaillées de leurs besoins.
  • Axe technique : le Framework qu’il a présenté possède quelques lacunes à cause du moteur de base de données qui n’est pas adapté à tous les concepts qu’il a présentés. Il est en train d’imaginer un nouveau concept pour lever cette barrière.
  • Axe économique : Le but du chef de projet n’est pas de faire de vieux os chez l’éditeur. Il désire donc avoir la possibilité d’avoir des droits d’exploitations sur le système qui va être développé pour créer plus tard une société qui aura vocation à créer d’autres logiciels de gestion.
Après quelques négociations un peu âpres entre le chef de projet et la société, cette dernière demande à avoir une estimation pour savoir quand le nouveau logiciel pourra être disponible à la vente.

Cette question peut paraitre saugrenue du fait que nous ne savons absolument pas ce que le logiciel doit faire, mais le chef de projet ne se dégonfle pas : ça sera prêt dans 2 ans maximum !

Ce « détail » étant bouclé, nous intégrons cette société après quelques mois…

La naissance d'un projet stratégique

Nous arrivons dans cette nouvelle société où le chef de projet est attendu comme le messie pour sauver la boite de son produit vieillissant. Les dirigeants le connaissent déjà très bien pour avoir partagé avec lui des locaux pendant plusieurs années, et l’avoir vu mener avec brio certains projets de développement dans le domaine du jeu vidéo.

Une équipe de trois développeurs est déjà en place : Le directeur R&D qui code sur un L4G depuis plus de 15 ans, un développeur venant de la boite qui édite le L4G en question, et un développeur VB6.

Quelqu’un nous présente le progiciel actuel qui semble très compliqué : c’est un logiciel métier qui doit gérer un certain nombre de workflow, de règles monstrueuses soumises à des agréments ainsi que beaucoup d’interopérabilité avec des tiers.

Ce logiciel étant installé chez plus de 250 clients, je suis tout d’abord mobilisé pour régler certaines problématiques sur ce dernier, le temps que le chef de projet puisse concevoir toute la stratégie de l’entreprise…

Au bout de quelques mois de travail acharné dans son bureau, voici le plan de bataille qu’il nous présente :
  • En premier lieu, l’équipe de développeurs actuelle va-t-être formée au langage C#
  • En parallèle, le chef de projet et moi-même redévelopperons le Framework RAD en .NET
  • Le Framework devrait être terminé lorsque les équipes aurons fini d’être formées. A ce moment-là les développeurs pourront redévelopper le logiciel métier à l’aide du Framework, ce qui permettra de l’éprouver. 
  • Pour que l’étape précédente puisse se dérouler dans de bonnes conditions, le directeur R&D devra fournir un cahier des charges exhaustif de tout ce qu’il voudra voir dans le nouveau logiciel.
-          Une fois que le logiciel métier sera opérationnel, le chef de projet restera encore quelques temps, puis il démissionnera pour créer une nouvelle société afin d’exploiter le Framework. 

Le plan d’action était bien sûr beaucoup plus détaillé, avec énormément de recommandation et de cours magistraux sur comment un projet informatique doit être mené dans les règles de l’art, le tout illustré de plusieurs schémas de ce type :

Nous verrons qu’en réalité, les choses se sont plutôt déroulées de cette manière :

 

Étape 1 : Euphorie

Le chef de projet nous présente sa stratégie sur le plan technique.

L’une des fonctionnalités phare de ce Framework RAD réside dans le fait que toutes les données sont synchronisées en temps réel avec tous les clients connectés, et ceci sans aucune ligne de code de la part du développeur. 

Or, le procédé que  nous utilisons avec SQL server est inefficient pour mener à bien cette tâche.

Il dévoile alors des documents de conceptions très détaillés sur comment il compte régler le problème. 

Le concept général réside en 6 lettres : IMOODB pour « In Memory Oriented Object DataBase ». L’idée principale est la suivante : Les bases de données sont faites pour gérer des informations stockées sur disque, car le coût de la RAM est extrêmement cher. Toutefois, un accès disque est aujourd’hui de l’ordre de la milliseconde tandis qu’un accès RAM se situe autour de la nanoseconde. Un accès en RAM est donc un million de fois plus rapide qu’un accès sur disque ! 

Il nous explique qu’avec l’évolution sans cesse des composants matériels (dont la RAM), les moteurs de bases de données sont de plus en plus enclin à monter toutes les données en mémoire afin d’avoir des temps d’accès proches du temps réel ! 

C’est donc le premier point de cette nouvelle stratégie : les données seront TOUTES en RAM, ce qui permettra non seulement de gérer des objets (donc pas de contraintes d’ORM) mais aussi de pouvoir maintenir des contextes de connexions afin de gérer le push de données entre le serveur et les clients.

Second point : Le stockage des données doit tout de même se faire sur disque pour la persistance, car lorsqu’on éteint la machine, les données montées en RAM disparaissent. Pour cela, il nous suffit de développer un système de stockage basé sur le model NoSQL (système clé/valeur). Il appui ses dires sur d’autres documents de conceptions très détaillés expliquant comment mettre la chose en œuvre.

Troisième point : Pour développer ces deux systèmes rapidement et s’assurer qu’ils seront évolutifs tout en tirant pleinement parti des processeurs multi-cœurs, il nous présente une architecture en « pipeline » directement inspiré des systèmes électronique. Le concept est basé sur le développement de composants connectables entre eux à l’aide de canaux logiques. Chaque composant travaille sur un thread, et a la responsabilité de ne faire qu’un type de traitement basé sur des entrées et des sorties. Cela permet d’avoir une architecture réutilisable, contrôlable et adaptable en fonction de la puissance de la machine.

Quatrième point : Notre but est de fournir un RAD à des développeurs inexpérimentés sur les technologies .NET. Il faut donc que la mise en œuvre d’IHM soit non seulement simple, mais aussi très élégante et novatrice. Le bougre n’en est pas à son coup d’essai car c’est un spécialiste des systèmes graphiques avancés. Issu du monde du jeu vidéo, il est comme un poisson dans l’eau en ce qui concerne cette partie. Il nous présente alors un outil de sa conception permettant de créer des IHM de malades avec gestion de collision, attraction, rotation, déformation et plein d’autres animations… Son petit soft fait maison ressemble à Blend et permet de générer un fichier XML qui sera interprété par son moteur graphique. De quoi donner le tournis ! 

A la vue de ces éléments, tout le monde est très impressionné tant par le niveau technique que par les idées innovantes de cette stratégie. Il est clair que nous avons affaire à un visionnaire !

La première étape consiste donc à préparer une démonstration de quelques-uns de ces concepts pour obtenir l’adhésion de tous les membres de la société.

Pour ma part, je m’attèle à mettre en œuvre un petit logiciel censé représenter une version allégée du vrai logiciel métier, le but étant de charger en RAM cinq ans de données de production pour effectuer divers traitements et montrer à tous avec quelle vitesse les données en mémoire sont manipulées.

De son côté, le chef de projet peaufine une ébauche d’interface graphique avec son moteur tandis que le directeur R&D prépare une présentation PowerPoint pour vulgariser les choix techniques évoqués ci-dessus.
Au bout de quelques semaines de travail, une réunion interne est organisée pour présenter la stratégie à toute la société.

Après une belle présentation de la part du directeur R&D qui durera quelques heures, il est temps de montrer des « preuves de concepts ». Je lance donc mon petit logiciel dont l’IHM est remplie d’images humoristiques pour détendre l’atmosphère, ce qui ne manque pas de faire son effet, puis  j’explique à mon auditoire que ce programme a pour vocation de démontrer la puissance que pourra nous permettre un système IMOODB. J’ouvre alors un module et commence à faire quelques recherches dans une espèce de base de données locale de plus de 100 000 personnes, et le résultat apparait au fur et à mesure que je tape des caractères dans mon champ de saisi. Je lance ensuite un module de statistique (point faible de l’ancien logiciel), et tout le monde constate avec stupeur que je peux manipuler 5 années de données de production aussi facilement que si j’étais avec quelques lignes sur Excel. A ce moment, la salle reste bouche bée.

S’enchaine alors la démonstration du chef de projet qui commence par expliquer que l’interface graphique est quelque chose de très important pour la perception des utilisateurs, et qu’il est de notre devoir d’innover dans ce domaine. Dès le démarrage de son programme, un méga splash screen en jette directement plein la vue, puis une fenêtre s’ouvre. A partir de là, il se met à expliquer quelques concepts de base : « Vous voyez, par exemple, lorsque vous voulez ranger des éléments, vous utilisez une liste n’est-ce pas ? Ici nous allons utiliser un système de vortex qui simule une attraction afin de grouper ces éléments de façon intuitive ». Il prend alors quelques éléments issus d’une liste puis les glisse vers un endroit de l’écran, et lorsqu’il les lâche, les éléments sont comme attirés par cette zone pour venir se regrouper naturellement en se collisionnant entre eux. Il démontre ensuite pleins d’autres concepts du même type, amenant l’effet « wahou » à son paroxysme !

Toute la société est conquise par cette démonstration, et nous en repartons gonflés à bloc ! La direction demande à ce que le projet commence au plus vite.

Le chef de projet me met immédiatement sur le développement du système de stockage en NoSQL.
Ce premier projet nous permet notamment de mettre en œuvre le système de pipeline afin d’en éprouver le concept. Je me base sur ce système pour développer toute la partie socket, gestion de fichier, compactage et correction de données corrompues. Une fois que le système de stockage a atteint un niveau de maturité suffisant, le chef de projet me demande de mettre en place une synchronisation bidirectionnelle entre plusieurs serveurs, avec gestion de tolérance de panne et verrouillage pessimiste. Il exige de ne rien commencer sans lui avoir montré des documents de conceptions fonctionnels et techniques très détaillés.

Au bout de plusieurs mois de travail et d’échanges basés sur ces documents, je m’attaque à la partie implémentation, et là les choses vont très vite. Je constate alors pleinement l’intérêt de prendre du temps pour faire de la conception, et je suis séduit par la pertinence du cycle en V.

Comme nous avons bien avancé, une nouvelle présentation est organisée pour que toute la société reste convaincue du bien-fondé de la stratégie.

Au cours de cette démonstration, je montre avec quelle aisance un serveur « storage » peut être installé et configuré. Je fais ensuite preuve du bon fonctionnement de la synchronisation et du système de tolérance de panne, ce qui ravi toute l’assemblée.

Nous annonçons fièrement qu’il a fallu environ 8 mois pour faire le storage, et le chef de projet annonce que la partie IMOODB ne mettra pas beaucoup plus. Tout cela conforte les collaborateurs sur la pertinence de la stratégie, ce qui nous permet de continuer sereinement sur le projet.

 

Étape 2 :  Inquiétude

Les choses avancent bien, et certaines personnes commencent à s’identifier à ce succès.

Mais le chef de projet coupe court à ces élans d’appropriation : il est le seul et unique concepteur de toute cette machinerie, que ce soit sur le plan technique, organisationnel ou économique et il tient fortement à ce que tout le monde dans la société en soit bien conscient.

Après cette mise au point, je lui demande s’il peut me présenter les spécifications techniques de la partie IMOODB afin que je me mettre au travail. 
Mais il y a un Hic.

Cela fait plusieurs semaines qu’il a des débats avec le directeur R&D qui a une vision différente de celle évoquée au départ. Pour lui, le moteur doit absolument posséder tous les mécanismes présent dans le SGBD de son RAD, à savoir triggers, vues, contraintes sur clés, indexes, suppression en cascade et surtout l’isolation contextuelle qui est au centre de la polémique.

Le chef de projet m’annonce qu’il doit faire plusieurs prototypes pour tester la faisabilité des demandes du directeur R&D, et que les choses sont bien plus compliquées qu’elles n’y paraissent. De ce fait, il préfère travailler seul sur le sujet pour éviter de perdre du temps en collaboration.

De mon côté, je me mets  à travailler sur des sujets annexes pour éviter de le déranger, et je me rapproche du directeur R&D qui a de forts besoins pour l’ancien produit. 

Ma sortie du projet initial se fait petit à petit.

Plusieurs mois se sont écoulés, et toujours pas de système IMOODB qui fonctionne. La direction demande ce qui se passe et le chef de projet explique qu’il ne peut pas tout faire : entre la formation des développeurs, le développement du système IMOODB et les discutions conceptuelles interminables qui se font avec le directeur R&D, il est tout bonnement naturel que les choses n’avancent pas ! Il explique que les développeurs n’ont pas une progression suffisante car ils sont pollués par des années de programmation procédurale. Il faut donc utiliser leur DIF pour qu’ils soient pris en main par un centre de formation au plus vite.

La direction demande des estimations car un salon très important doit se tenir dans quelques mois : Pourrons nous présenter quelque chose ? Que devons-nous dire aux clients qui attendent la nouvelle version avec impatiente ?

Les réponses sont les suivantes : 
  •  Non, nous ne pourrons rien montrer, il faut donc s’organiser pour faire une énième démonstration de l’ancien produit à nos clients et prospects. 
  •  La nouvelle version sera présentée lors du salon de l’année prochaine, les clients qui l’attendent auront alors tout le loisir de la tester et de la commander.
Les mois passent, et le fameux salon arrive. L’équipe commerciale est présente, ainsi que quelques techniciens et le chef produit.

A leur retour, j’ai une discussion avec le chef produit qui me fait part des bruits qui ont circulé durant le salon. Visiblement, celui-ci a reçu plusieurs remarques négatives par rapport à l’avancement du projet. Les délais ne sont pas réalistes, les anciens développeurs n’ont pas leur place dans l’équipe, le directeur R&D semble perdre le contrôle de la situation et les débats entre le chef de projet et le directeurs R&D laissent à penser que ceux-ci se sont lancés dans le redéveloppement d’un système de base de données, ce qui constitue en soi une véritable folie. Personne ne comprend comment le nouveau logiciel va pouvoir sortir dans ces conditions.

Nous faisons part de ces remarques lors d’une réunion de service, et le doute commence à s’installer dans l’esprit du directeur R&D, mais le chef de projet tient à se défendre devant cette critique qu’il considère comme une attaque. Pour lui, il est normal que les gens ne voient pas les choses avancer car ils ne comprennent pas la finalité de ce qu’il est en train de faire. 

Lorsque le Framework sera opérationnel, la vitesse de développement deviendra exponentielle. Il appui ses arguments en parlant d’un projet de jeu vidéo pour lequel il n’avait que 6 mois de budget ; Il a mis les 5 premiers mois à concevoir un moteur graphique, ce qui lui a permis par la suite de développer entièrement son jeu en seulement 1 mois !

Le directeur R&D atteste de ce fait car il était présent lorsque cette histoire s’est passée. Il explique qu’effectivement, tout le monde le prenait pour un fou car au bout de 5 mois, la seule chose que l’on pouvait voir était un triangle qui tourne sur un écran, et il semblait inconcevable que le jeu puisse sortir dans les temps, et pourtant il l’a fait !

Après plusieurs heures de débat, tout le monde est plus ou moins convaincu que le projet suit un déroulement normal, et que le Framework va permettre de balayer rapidement les inquiétudes.

Les choses reprennent leur cours et un jour le directeur R&D débarque dans le bureau du chef de projet. 

Cela fait au moins 8 mois que les anciens développeurs sont en formation, et il serait peut-être temps qu’ils se mettent à produire quelque chose. De plus, cela fait bien un an qu’il rédige des spécifications mais il se rend compte que certaines fonctionnalités n’ont plus lieu d’être tant le marché est en constante évolution. Il a donc pris les devants en inscrivant l’équipe à une formation « Agile » car il semble que cette méthode soit bien plus adaptée à la création du logiciel métier.

Le chef de projet donne un avis mitigé sur la question. Il pense que le directeur R&D essaye de se soustraire à l’analyse, aux spécifications et à la conception du logiciel métier comme convenu au départ, ce qui aura une incidence sur le succès des opérations.

Après de vifs échanges entre les deux protagonistes, le chef de projet cède aux exigences du directeur R&D, et un début de malentendu commence à s’installer entre les deux personnages. Pour le directeur R&D, il ne fait qu’apporter son aide afin de faire bouger le projet qui s’enlise, tandis que le chef de projet s’imagine que le directeur R&D a pris le lead de la partie logiciel métier, ce qui l’arrange car ça lui permet de se concentrer sur le Framework.

Le directeur R&D emmène l’équipe devant travailler sur le nouveau produit à cette formation. Ils en reviennent tous assez emballés et ils m’invitent à participer à la deuxième session, chose que j’accepte avec plaisir.

La formation est de très bonne qualité, et je découvre un nouveau système de pensé à l’opposé de tout ce qu’on m’a expliqué jusqu’alors : Plutôt que de vouloir faire du prédictif avec des spécifications et de la conception imaginées des années à l’avance, il est préférable de faire de l’empirique en appliquant un système « essai / erreur » pour atteindre le but convoité. On nous explique les bases de l’agilité avec quelques « serious game » pour bien illustrer les concepts, et nous apprenons plein de nouvelles méthodes très intéressantes.

A la sortie de cette formation, le directeur R&D prend les devants. Il veut appliquer la méthode au plus vite pour que les gens puissent se mettre au travail sur le nouveau produit. Il prend donc rapidement l’ascendant ce qui conforte le chef de projet sur l’idée qu’il n’est pas responsable de la partie logiciel métier, mais bien seulement de la partie Framework.

Bien qu’il soit de bonne volonté, et que sa prise de leadership soit louable, le directeur R&D possède trois handicapes qui vont le desservir par la suite :

Premièrement : Il est l’unique développeur du logiciel historique déployé sur plus de 250 sites, il ne peut donc pas s’investir pleinement dans le nouveau projet.

Deuxièmement : Comme il ne peut pas s’investir à cause du point numéro 1, celui-ci ne s’est jamais formé à la programmation orienté objet. Mais il est persuadé de l’avoir bien comprise et que cela lui permettra de donner des directives techniques sur le sujet.

Et troisièmement : C’est un personnage de caractère très colérique, ce qui peut être gênant lorsque l’on doit mettre en œuvre une méthode qui prône les échanges et la communication orale plutôt que les processus et les outils.

Le développement du logiciel métier commence enfin, tout du moins sa préparation. Le directeur R&D élabore avec le chef produit le « product backlog » et il est demandé aux développeurs de faire quelques petits projets pour tester la faisabilité de certaines fonctionnalités.

Comme tous les débuts, mettre le pied à l’étrier est assez difficile. Les « daily stand up » durent des heures, tout le monde s’engueule pendant les « pokers planning », le « product backlog » possède des « users stories » aux granularités douteuses, les « sprints planning meeting » sont interminables et décousus, bref nous vivons les premiers « sprints » de cette nouvelle méthodologie sans être accompagné, ce qui éreinte sérieusement le moral des troupes. 

Quant aux développeurs, les petits projets sur lesquelles ils sont affectés n’avancent pas, mais alors pas du tout ! Nous sommes de l’ordre de 5 lignes de code par jours, ce qui rend totalement fou de rage le directeur R&D !

Celui-ci leur rend la vie impossible à tel point que les développeurs préfèrent démissionner plutôt que de continuer à vivre cet enfer.

Bien que ce départ est présenté comme étant bénéfique, nous nous retrouvons tout de même avec une problématique majeure : il nous faut des développeurs métiers, et vite !

Heureusement, la direction propose une solution :
Une jeune boite de la région possède un type de logiciel très intéressant qui serait complémentaire avec notre business. Or, un avenant réglementaire a fait de  leur produit un élément moins attractif sur le marché, et cela les a forcé à se rabattre sur notre type de solution, ce qui commence à devenir assez gênant. 

Un rendez-vous est organisé entre nos deux sociétés. Ceux-ci possèdent pas mal de développeurs .NET et ils sont en difficulté financière. Un rachat est proposé, et après plusieurs mois de négociation, un accord est conclu.

Ce rachat nous donne trois avantages : 
  1.  On récupère un logiciel complémentaire prêt à vendre. 
  2.  On récupère des développeurs .NET qui connaissent le métier, ce qui n’est pas si mal dans un contexte où il est très difficile d’en trouver. 
  3.  On élimine du marché un concurrent potentiel qui aurait pu faire mal s’il était prêt avant nous.
C’est donc tout bénef !

6 nouveaux développeurs arrivent et découvrent le concept de l’IMOODB.

Le chef de projet se présente comme le développeur du Framework qui leur permettra de créer le produit, et le directeur R&D se présente comme le « Product Owner » qui leur dira quoi créer.

Le chef de projet propose de faire passer quelques tests aux nouveaux arrivants histoire de connaitre leur niveau en développement, mais le nouveau salon pour lequel la société s’est engagée à présenter le nouveau logiciel se tiendra dans 3 mois ! Donc pas question de perdre du temps en futilité !
A partir de là, tout va très vite : le salon est un jalon important, et il faut montrer quelque chose ! 

Étape 3 : panique !

Le directeur R&D prend une nouvelle fois le leadership de la situation et demande au chef de projet de trouver un moyen d’accélérer la cadence.

Ce dernier s’attèle à expliquer aux développeurs comment fonctionne le Framework : Accès aux données, système de push, liste de données auto gérées, formulaires auto générées, asynchrone, … 

Le développement commence et je suis affrété à tester ce que les développeurs produisent pour vérifier la bonne implémentation des fonctionnalités.

Les journées s’enchainent à un rythme infernal, les nouveaux développeurs découvrent peu à peu dans quoi ils se sont fourrés : D’un côté, ils doivent travailler avec une technologie totalement inconnue dont toute la connaissance se trouve dans le cerveau d’une seule personne qui diffuse son savoir au compte goute, et de l’autre, on leur demande de développer des fonctionnalités ahurissantes à coup de conseils techniques douteux et maladroitement exprimés.

Tout cela se ressent fortement sur la qualité du logiciel. Les tests dévoilent des bugs catastrophiques (perte de données, plantage intempestif, formulaire qui ne s’ouvrent pas, incohérence, problèmes d’intégrité, …) accompagnés de performances très médiocres !

Le salon est bientôt là, et l’équipe n’a pas le temps d’ajouter d’autres fonctionnalités. Le directeur R&D et le chef de projet prennent une décision commune : il faut maquiller la mariée pour avoir quelque chose qui donne un semblant de logiciel terminé, car les clients nous attendent au tournant.

Les développeurs entrent dans une phase de stabilisation, et terminent les fonctionnalités manquantes en produisant des coquilles vides. Tous les cliques faisant planter le logiciel sont soigneusement répertoriés afin de permettre aux démonstrateurs de passer entre les gouttes.

En parlant de démonstrateur, nous sommes à une semaine du salon, et personne n’a encore vu le logiciel !

Quelques machines sont installées en urgence pour permettre aux experts métier de préparer un scénario de démonstration, mais c’est l’hécatombe : un plantage tous les trois cliques, des boutons factices qui ne font rien, une ergonomie qui mélange plusieurs types d’interfaces, des messages techniques incompréhensibles et des lenteurs excessives font bondir de peur tout individu qui s’approche à moins de 2 mètres d’un poste de démonstration ! Les démonstrateurs se révoltent et refusent catégoriquement de montrer ça à qui que ce soit !

Mais le service R&D est plein de ressources…

Puisque les démonstrateurs ne veulent pas présenter le produit, ce sera la R&D qui fera les démonstrations. Sont nominés le chef produit, le directeur R&D, le chef de projet et moi-même pour accomplir cette mission.

La situation sent très mauvais, mais le défi est relevé.

Pour pallier les manques fonctionnels, des PowerPoints très riches sont mis en place. Chacun répète sans cesse un scénario qui donne illusion que le logiciel est stable grâce à un soigneux jeu de slalom entre les bugs.
Un jour avant l’envoi des machines sur le salon, une erreur aléatoire venant de nulle part compromet très sérieusement notre scénario ! L’équipe fait 24H de travail non-stop pour en trouver l’origine et s’assurer que les machines partiront avec la dernière version corrigée.

Tout le monde est épuisé, mais il faut tenir bon. Des phrases laissant à penser que travailler 24 heures non-stop est quelque chose de normal avant une démo sont prononcées, ce qui est censé maintenir le moral des troupes.

Après un repos bien mérité, direction le salon au cours duquel je me suis découvert un véritable talent de pipoteur et de joueur de flute tellement j’arrivais à faire croire que le logiciel fonctionnait parfaitement. Cette expérience m’a appris une chose : Une démonstration de logiciel n’a aucune valeur tant que vous n’avez pas vous-même manipulé la souris, car le démonstrateur peut vraiment  vous faire gober ce qu’il veut. 

Le salon se termine sur un bilan positif. Les clients et prospects sont assez séduits dans l’ensemble et les commerciaux reviennent avec quelques commandes et demandes de démonstrations
Le directeur R&D maintient la pression pour que les développeurs implémentent rapidement les coquilles vides.

De ce fait, je suis convié à participer à l’effort de guerre pour ajouter un module assez compliqué. Je m’immerge malgré moi dans ce nouveau projet, car ni le chef de projet, ni le directeur technique ne savent ce qui se passe dans le code. On m’explique que cela fait partie de la méthode agile qui stipule dans son manifeste que « les meilleures architectures, spécifications et conceptions émergent d’équipes auto-organisées. »

Lorsque j’ouvre le projet, les bras m’en tombent.

La solution est composée de 60 sous projets censés représenter les différents modules, et énormément de ces projets sont vides. Le code métier est concentré dans un projet dont la DLL est partagée entre l’IMOODB et le client lourd, car le système a été conçu en couplage fort entre les deux entités.

Du coup, le code situé dans ce projet à une particularité étonnante : il est constitué d’énormément de classes statiques appelé des « Helpers », qui eux même sont architecturés pour pouvoir être chainé entre eux.

Je demande au chef de projet pourquoi une telle ignominie ? Il me répond qu’ils ont dû faire ça car le système IMOODB qu’il a mis en œuvre est incapable de gérer des objets. Ainsi nous avons d’un côté des structures avec des propriétés et de l’autre des méthodes situés dans des classes statiques qui sont capables de manipuler ces données.

A mon grand étonnement, je récapitule la situation à haute voix : la société a investi des centaines de millier d’euros pour disposer d’un système de gestion de base de données orientée objet, mais celui-ci n’est pas capable de gérer des objets. Du coup, les développeurs sont obligés de séparer les données et les traitements comme avec un langage procédural, chose que l’on veut éviter à tout prix !

Le chef de projet se défend de cette situation en m’expliquant qu’il n’a pas eu le choix, car le directeur R&D voulait absolument une isolation contextuelle, et c’est cette contrainte qui l’a forcé à devoir faire de la sorte.

Sur ce, je continue à analyser le code dans lequel je vais devoir évoluer. Au milieu des 60 projets, j’en vois un qui semble posséder pas mal de fichiers sources : le projet qui contient les IHM.

Et là, c’est la fête du slip : du code behind  fait directement référence à nos supers « Helpers », le tout formant une espèce d’énorme plat de spaghetti dans lequel il est quasi impossible de suivre le moindre chemin de code !

Je me rapproche de l’équipe de dev pour comprendre comment ils peuvent travailler dans ces conditions. Ils me répondent qu’ils sont bien conscients de la situation, mais la pression permanente exercée par le directeur R&D ne leur permet pas de travailler proprement, car il s’imagine que le Framework fourni par le chef de projet leur donne la possibilité de s’affranchir de toutes les problématiques traditionnelles.

De plus, les développeurs ne comprenant pas tout au sujet du Framework, ils doivent sans cesse aller tirer les vers du nez du chef de projet qui procède à des modifications au fur et à mesure des problèmes qu’ils rencontrent.

Nous sommes donc en présence d’une situation assez burlesque :
Le directeur R&D est persuadé que le chef de projet gère les développeurs en les formant en continu à l’utilisation du Framework, et le chef de projet pense que le directeur R&D gère « son » équipe de développement en attendant que le Framework devienne mature.

Au final, personne ne gère l’équipe de développement, et celle-ci est totalement livrée à elle-même !

Le chef de projet étant très susceptible, je m’abstiens de toute remarque et tente quand même une implémentation simple.

Au bout d’une semaine, lors du fameux "daily standup", j’explique que je n’arrive pas à mettre en œuvre la création en base d’un objet métier. Là-dessus, le directeur R&D entre dans une crise de rage ! Il ne comprend pas qu’il me faille 5 jours pour faire quelque chose d’aussi trivial. 

Lorsque je pose la question : « mais qui a déjà créé un objet métier en base ? » tout le monde baisse la tête, mais le directeur R&D explique que plein de choses ont déjà été créées en base, et que ça nous a permis d’ailleurs de faire le dernier salon.

En creusant la question, les développeurs avouent qu’en réalité, ils ont déjà créé des « structures » en base, mais le comportement associé n’a pas pu être correctement implémenté du faite de différents problèmes avec l’IMOODB.

Le directeur R&D commence à comprendre, mais une part de lui-même ne veut pas se l’avouer. Le projet doit continuer coûte que coûte et quoi qu’il en coûte !

Le développement continu et la situation s’envenime.

Les conditions de travail deviennent de plus en plus insupportables pour les développeurs : 
  •  Le Framework qu’ils utilisent à de nombreux bugs et personne ne les formes correctement à son utilisation. 
  • Tous les matins, ceux qui arrivent en premier téléchargent la dernière version du code source et celle-ci est corrompue. Ils passent donc leur matinée à essayer de comprendre d’où vient le dysfonctionnement qui dans 80% des cas dû à un changement dans le Framework poussé par le chef de projet à 22H. 
  • La solution possède tellement de DLL avec des références circulaires que le logiciel met plus de 8 minutes à compiler sur chaque poste. 
  • Le système de push crée plusieurs problèmes de race condition et de deadlock qui rendent extrêmement difficile le débogage de l’application. 
  • Le couplage fort de la DLL où se trouve le code métier est assujetti à plusieurs régressions, forçant tous les développeurs à dépenser chaque jour une énergie folle pour revenir à un état stable est enfin commencer à travailler. 
  • Le directeur R&D donne des indications techniques avec un vocabulaire qu’il ne maitrise pas, donc complétement incompréhensibles pour tous. Du coup, les développeurs se font régulièrement crier dessus à cause de leur incompétence supposée.
  • La méthode Agile n’est pas correctement appliquée, les US sont à moitié faite, les daily standup durent des heures, le directeur R&D influence les « poker planning » et il n’y a jamais eu de démonstration interne du produit.
Certains d’entre eux commencent à s’intéresser aux solutions qui existent. Des messages sont envoyés au directeur R&D et au chef de projet pour expliquer les biens faits de l’intégration continue, des tests unitaires, du découplage total, de l’architecture SOLID, des coachs Agiles, etc.

Mais le chef de projet ne veut pas en entendre parler : pour lui tout cela s’applique aux grosses sociétés qui doivent gérer des projets à plusieurs centaines de jours / hommes. Un simple auto cliqueur (qu’il estime à 3 jours de travail) permettra de faire des tests automatiques de façon simple et efficace sans avoir à mettre en œuvre toutes ces histoires.

Le directeur R&D entre en conflit avec le chef de projet. Il lui demande de trouver des solutions car le nouveau salon arrive, et il faut absolument que le logiciel soit installé chez un premier client afin qu’il puisse faire un témoignage.

Le chef de projet passe des Weekend entier à essayer de refactoriser le projet à lui seul, mais sans succès. Il s’attèle à créer son auto cliqueur, mais la règle des 80/20 vient le frapper de plein fouet car une limitation due aux popups l’empêche de faire ce qu’il avait imaginée.

Le projet continue de sombrer techniquement et humainement, mais de nouveaux formulaires sont disponibles ce qui laisse à penser au directeur R&D que les choses ne se passent pas si mal.

Et un jour, c’est arrivé : les commerciaux ont signé un premier client, et il faut l’installer le plus rapidement possible !

Le directeur R&D se met à la tâche avec beaucoup de confiance. Il coordonne la logistique, les déployeurs, les formateurs, appelle les clients pour préparer les paramétrages…

Le service R&D observe la situation comme une maman qui regarde jouer son enfant avec un pistolet chargé. Quand le chef de projet ou les développeurs essayent de faire comprendre au directeur R&D que le logiciel n’est peut-être pas encore prêt, il rétorque qu’il faut savoir se jeter dans la piscine !

Une grosse crise de dépression s’installe dans l’équipe. Personne ne dis un mot lors des repas alors que d’habitude les blagues salaces fusent à tout va. Des discours défaitistes et cataclysmiques sont exposés à qui veulent les entendre. 

Les autres services commencent sérieusement à s’inquiéter de cet état d’esprit négatif et remontent le problème à la direction. Il est alors demandé aux pessimistes de faire bonne figure, et d’arrêter d’avoir des discours aussi exagérés, car ils plombent sérieusement le moral de toute la société !

Nous sommes un vendredi, et c’est le jour du déploiement qui doit se faire dans la soirée. Les développeurs continuent à coder sans rien dire, comme il leur a été demandé, et le directeur R&D me demande si je pourrai l’aider ce soir en tant que soutien à distance. J’accepte en proposant la chose suivante : je prends mon après-midi, puis je reviens au bureau ce soir à 21H pour l’épauler.

La chose est entendue, je rentre donc chez moi en souhaitant bon weekend à tout le monde.

Le soir arrive, et je retourne au bureau pour le déploiement. 

Lorsque j’ouvre la porte de la société, je découvre que l’alarme n’a pas été désactivée, j’imagine que le chef de projet a oublié de l’activer, mais en prêtant un peu plus attention, j’entends des bruits discrets qui viennent du fond du couloir. J’avance doucement dans le noir en direction du service R&D qui est resté allumé, imaginant que je vais tomber nez à nez avec la femme de ménage. Mais lorsque j’arrive au niveau de la pièce éclairée, je vois deux développeurs sur le banc de tests avec leur tête posée sur les claviers, montrant clairement que tout espoir les a abandonnés.

Je crie : « Mais qu’est-ce que vous foutez encore là ?! »

Les deux développeurs se relèvent d’un coup et me regarde avec des yeux tout aussi étonnées que fatigués. L’un d’eux me répond la chose suivante avec quelques sanglots dans la voix :

« Rien ne marche. Plus on fait des correctifs, et plus ça merde. C’est une catastrophe ! »

A ce moment-là, le chef de projet sort de son bureau en affichant un sourire crispé. Les développeurs se taisent instantanément.

Personne ne dit rien, mais tout le monde prend conscience que nous vivons les premiers instant d’une ère de ténèbres et de chaos qui va s’abattre sans vergogne dès lundi matin.

Je décide de prendre la parole :
« Ok, je vois. Écoutez, je pense que vous avez fait le maximum, donc rentrez tous chez vous car lundi risque d’être une journée difficile. De mon côté, je vais aider le directeur R&D pour le déploiement, et nous verrons bien ce qu’il adviendra »

Chacun acquiesce et le chef de projet demande à rester avec moi pour appliquer quelques correctifs en cas de problèmes bloquants. Les autres développeurs prennent leurs affaires et viennent me voir pour confesser ce qu’ils ont sur le cœur :

« On ne peut plus continuer comme ça, il faut vraiment trouver une solution, car tout le monde est au bord de la crise de nerf ! »

Je réponds avec une phrase laconique du genre « tout va bien se passer », mais je ressens vraiment dans leurs propos qu’une autre façon de travailler doit être mise en place au plus vite, car la situation que nous vivons est très loin d’être normale.

Après le départ des développeurs, nous appelons le directeur R&D. L’installation à distance va se faire pendant une bonne partie de la nuit, et comme attendu, beaucoup de problème se produisent sur le terrain.

Au bout de plusieurs heures à  faire en sorte que les choses fonctionnent à peu près, nous arrivons à un niveau d’impuissance qui nous oblige à attendre que l’équipe de développement soit présente pour contourner certains bugs qui nous empêche de continuer l’installation. Nous n’avons pas d’autres choix que de rentrer chez nous et attendre la fin du week-end.

Lorsque je retourne au bureau lundi matin, je m’attends à voir l’équipe de développement en pleine effervescence, mais dès mes premiers pas dans la société, je sens comme une sorte de chape de plomb.

Le chef de projet qui est visiblement très épuisé m’explique la situation :
Samedi matin, le directeur R&D lui a demandé de tout mettre en œuvre pour que le logiciel soit opérationnel lundi, car il est inenvisageable de former les utilisateurs avec un système complétement buguée. Du coup, il est venu travailler samedi et dimanche pour essayer de redresser la situation.

Comme il n’y arrivait pas tout seul, celui-ci a appelé les développeurs à la rescousse pour qu’ils viennent le rejoindre dans son calvaire. Quelques-uns ont répondu à l’appel pour le dépanner.

Dimanche soir, malgré de très grosses lenteurs, la situation semblait se stabiliser, mais tout le monde a convenu de venir très tôt le lundi pour le démarrage du client.

Lundi matin, la formation des utilisateurs étant quasiment impossible du fait des multiples dysfonctionnements, le temps de formation a été sacrifié pour encore tenter de stabiliser le logiciel.

Quelques heures plus tard, le client a ouvert ses portes obligeant le logiciel à passer en production. Les utilisateurs se sont retrouvés sans formation avec un outil présentant des problèmes graves (plantages, pertes de données, mélanges dans les informations, boutons non fonctionnels, lenteurs extrêmes …)

La tension étant à son comble, les intervenants sur place ont dû prendre une décision en urgence : basculer le client sur l’ancien logiciel pour rétablir une situation stable.

Voilà où nous en sommes. Après plus de 3 ans de développement, le logiciel n’a pas tenu une journée chez le premier client. 

Le projet est donc officiellement un échec.


Étape 4 : Recherche des coupables

Le déploiement du premier client commence à arriver aux oreilles de la direction, sonnant le début d’une véritable chasse aux sorcières !

Il est demandé à toute personne ayant travaillé sur le projet d’envoyer sa version des faits par email à la direction pour tenter de mieux comprendre la situation.

Chacun s’adonne à cet exercice, et la direction décortique soigneusement chaque récit en prenant soin de rassembler les meilleurs passages dans une présentation PowerPoint.

Après quelques semaines, une réunion est organisée entre la direction et la R&D.

Objectif : Débattre de l’expérience de chacun pour tenter de déterminer d’où à bien pu venir le dysfonctionnement. 

La réunion commence fort. La direction explique à l’ensemble du service que l’échec du projet engendre une perte de plus de 600 000 euros, et qu’à la vue de la situation, des têtes vont tomber !
S’en suit la projection du PowerPoint qui rassemble toutes les phrases « anonymisées » relatant les déboires du projet vues de l’intérieur.

Il en ressort que le projet a toujours été mené de façon vague, sans direction précise, laissant les développeurs se débrouiller seul sous couvert d’une méthode qui prône l’auto organisation.
Le directeur R&D incrimine le chef de projet qui pour lui n’a pas tenu son rôle. Il expose que celui-ci avait rédigé lui-même sa lettre de mission dans laquelle se trouvait des tâches telles que la formation continue de l’équipe, la mise en place de bonnes pratiques, le suivi de la qualité du code et la création du Framework censé accélérer la productivité générale. 

De son côté, le chef de projet se défend en expliquant que ses préconisations n’ont pas été respectées, que les développeurs n’étaient pas assez compétents et que les spécifications fonctionnelles n’ont jamais été totalement rédigées. Pour lui le directeur R&D a outrepassé son rôle en voulant s’accaparer une partie technique qu’il croyait maitriser mais sur lesquelles n’avait pas les compétences requises. Sa position hiérarchique a mené le chef de projet à devoir s’écraser.

Le conflit s‘envenime, et tout le monde se met à nourrir un débat très houleux.

Après plusieurs heures de discutions, le chef de projet reconnait qu’il a une part de responsabilité dans cet échec, et tout le monde s’attend à ce que le directeur R&D en fasse de même, mais cela ne sera pas le cas.


Étape 5 : Punition des innocents

Une réunion de crise invitant tous les collaborateurs de la société est organisée pour annoncer l’échec du projet.

La direction explique très sèchement l’état de la situation, laissant comprendre que l’ensemble de la société est menacée par cet échec majeur.

La salle entre en ébullition, et les langues se délient : Faute à l’esprit mégalo du directeur R&D et du chef de projet qui ont vu trop grand, faute aux développeurs qui sont des incompétents, faute au chef produit qui ne comprend rien au métier, faute à la direction qui n’a pas su stopper l’hémorragie à temps alors que tout le monde voyait bien que les choses ne se passaient pas correctement, …

La direction essaye de calmer les choses, et annonce que la mort du projet signifie clairement la mort de la société. Nous n’avons donc pas d’autre choix que de réitérer notre confiance en l’équipe R&D pour repartir du bon pied.

Et là, c’est l’hécatombe : toute la salle est consternée par cette déclaration. « On prend les mêmes et on recommence ? » s’écrient certains participants.

Le directeur R&D et le chef de projet essayent de tirer leur épingle du jeu en expliquant que les développeurs n’étaient pas au niveau, ce qui est la cause principale de l’échec. 

Les développeurs, qui sont des débutants pour la plupart, n’osent pas rétorquer et se laissent convaincre par l’assemblée qu’une grosse part de responsabilité leur incombe.

Une fracture se crée entre le service R&D et le reste de la société qui se sentait déjà spolié depuis plusieurs années par des développeurs un peu trop chouchouté à leur gout.


La tension est d’autant plus palpable que personne ne sait comment nous allons pouvoir nous tirer de ce mauvais pas, car ni le chef de projet, ni le directeur R&D, ni les développeurs n’ont de solution pour sortir de cette impasse.
La réunion se termine dans le chao, la confusion et un brouhaha de remarques injurieuses et blessantes vis-à-vis de tous les participants au projet. Tout porte à croire que la fin de la société est proche, et certains évoquent leur envie de quitter la boite.
Arrivé à ce stade, l’espoir a quitté une bonne majorité des collaborateurs, laissant place à un sentiment d’impuissance et d’incompréhension qui ne présage rien de bon.

Étape 6 : promotion des non participant

Comme vous pouvez vous en douter, l’ambiance était au plus bas au service R&D car tout le monde en a pris pour son grade.

J’étais dans mon bureau avec le chef de projet lorsque le directeur R&D débarque avec un air dépité

Celui-ci demande au chef de projet comment il voit les choses pour redresser la situation.

Le chef de projet explique qu’il est complétement dépassé par les évènements, et que la qualité de code du logiciel métier ne permettra pas de repartir sur de bonnes bases. A part trouver rapidement 5 ou 6 développeurs chevronnés pour reprendre le dossier, il ne voit pas comment les choses vont pouvoir s’arranger.

Je profite alors de ce moment de désespérance pour expliquer mon point de vue : A mon sens, le gros problème de ce projet n’est pas technique comme tout le monde le croit, mais managérial. Pour moi, il faut absolument que l’équipe puisse se raccrocher à une personne capable de la motiver à remonter la pente en la formant et en mettant en œuvres des méthodes qui sont conformes à notre temps.

J’explique que je suis prêt à endosser ce rôle à condition d’avoir carte blanche.

Voyant mon élan d’enthousiasme, le directeur R&D m’emmène aussi tôt voir la direction pour que j’expose mon plan. La direction ne sachant que faire me donne le feu vert, et acquiesce à toutes mes exigences pour que je reprenne les rênes du projet.

Me voilà donc promu « lead developer » d’une équipe au plus bas moralement, et dont la légitimité a été fortement remise en cause par l’ensemble des collaborateurs de la société.

Tout le monde pensait que mon action était peine perdue, mais en réalité, j’étais comme un agriculteur qui a racheté un terrain sur lequel un volcan a tout dévasté : il ne pouvait en être que plus fertile et j’y voyais une magnifique opportunité de faire de grandes choses !

Mais ceci sera l’objet d’un prochain article de blog…
Comment créer un contrôle graphique WPF avec Blend.
20 nov. 2014
Mon incompétence en terme d’IHM étant assez importante, j’ai suivi récemment une formation sur les outils de modélisation WPF, et je dois dire que cela m’a assez bien réconcilié avec le monde de l’interface graphique.


Cela a surtout démystifié pas mal d'aprioris que j’avais sur le sujet, et il est vrai que Microsoft a fait de très gros efforts pour permettre à tout un chacun de créer de jolies interfaces, même pour les amoureux des programmes en mode console comme moi.



Avec cet article, je vais tenter de vous montrer comment on peut facilement faire un contrôle graphique en partant d’une page blanche, et ce sans aucune compétence particulière en graphisme.

Introduction

Pour commencer, voici l’environnement de base que nous allons utiliser :
  • Visual Studio 2012
  • Microsoft .NET 4.0
  • Langage C#
  • Blend for Visual studio (normalement installé en même temps que VS 2012)

Le composant graphique que nous allons réaliser sera une horloge à aiguille avec les spécificités suivantes :
  • Les aiguilles se placent aux bons endroits en fonction d’un TimeSpan passé en propriété
  • Un bouton permet de changer le cadran de l’horloge via un panel de choix qui s’ouvrira avec une petite animation.
Voici à quoi cela ressemblera lorsque ce sera terminé :




Ceux d’entre vous qui font du Winform et qui ne connaissent pas WPF peuvent trouver ça irréalisable, surtout en partant de zéro !



Ne vous inquiétez pas, une fois que vous aurez lu cet article, vous ne voudrez plus faire vos IHM autrement qu’avec du WPF tant la manipulation graphique est simple, et donne en peu de temps des résultats bluffants !

Au travail

Nous allons commencer par ouvrir Visual studio et créer un nouveau projet en .NET 4.0 de type « WPF User Control Library » que nous allons appeler « MaLibrairie »



Une fois le projet créé, vous arrivez dans Visual studio à quelque chose qui doit ressembler à ceci :




Cliquez sur « UserControl1.xaml » et renommez le fichier en « MonHorloge.xaml »


Si Visual ne vous a pas fait de refactor automatique, n’oubliez pas de renommer aussi le « UserControl1 » dans « MonHorloge.xaml » ainsi que dans « MonHorloge.xaml.cs »






Voilà, une fois ces détails réalisés et enregistrés, nous allons pouvoir commencer le « design » de notre contrôle d’horloge.



Pour cela, ouvrez l’application « Blend » qui se trouve dans Démarrer à Tous les programmes à Microsoft Visual Studio 2012 à Blend for Visual Studio 2012




Dans la fenêtre d’accueil, cliquez sur « Open Project »



Allez chercher votre solution puis cliquez sur « Ouvrir »




Vous allez vous retrouver dans un logiciel qui ressemble plus à photoshop qu’a un IDE.

Bienvenue dans la partie visible de l’IceBerg  :)

Vue d’ensemble de Blend

Ok, faisons un peu le tour du propriétaire :

Pour que nous soyons dans la même configuration, je vous conseille de cliquer dans le menu « Window » à « Workspace » à « Animation »



A votre droite vous avez 4 onglets : « Properties, Projets, Ressources et Data ». Les onglets qui vont le plus nous intéresser sont les deux premiers. L’onglet « properties » va permettre de manipuler les propriétés graphiques des éléments que nous allons ajouter dans notre composant, et l’onglet « Project » est l’équivalent de l’explorateur de solution sous visual studio



Au centre, vous serez face au designer de votre contrôle graphique. Celui-ci est vide et vous regarde d’un air menaçant, mais vous verrez qu’il est très sympa pour peu qu’on apprenne à le connaitre.


C’est ici que nous allons ajouter, manipuler et agencer  des formes et des images pour que notre contrôle ressemble à quelque chose.





En bas, plusieurs onglets avec des noms obscures tels que « Assets, Triggers, States, Parts et Objects and Timeline » sont présents.



Tous ces outils permettent de créer des animations. Nous verrons comment les utiliser lorsque nous traiterons de la palette de configuration de notre horloge.



Sur la gauche, vous avez une palette d’outils un peu comme ce qui vous est proposée dans visual studio pour le design.



Nous utiliserons cette palette pour ajouter des composants à notre layout de design au centre.

Commencement

Alors, par quoi commencer pour faire notre horloge ?

Dans un premier temps, nous allons mettre en place le cadran. Pour cela rien de plus simple, allez sur « google image » puis faites quelques recherches pour trouver votre bonheur.



Choisissons un cadran avec une taille qui soit la même aussi bien en largeur qu’en hauteur. Celui en gris me parait très bien.



Tout d’abord, téléchargez l’image sur votre disque



Une fois l’image téléchargée, allez dans l’onglet « Projects » de blend, puis créez un dossier appelé « Cadrans » à votre projet.





Renommez l’image que vous avez téléchargée en « cadran1.jpg » puis glissez-la de votre disque au dossier que vous venez de créer dans blend.



Allez dans la barre d’outils située à gauche, puis cliquez sur le bouton « Assets » représenté par deux petites flèches sur la droite, puis tapez « Image » dans le panel de recherche qui apparait.




Faite glisser le composant Image dans le designer situé au centre de l’application.



Pour que le composant image prenne toute la place, cliquez sur l’onglet « Properties » situé à droite.

Cliquez ensuite sur le carré blanc des propriétés « Width », « height » et « Margin » puis sélectionnez « Reset »



Sélectionnez aussi l’option « Stretch » pour « HorizontalAlignement » et « VerticalAlignement »



Nous allons maintenant assigner notre image de cadran au composant, pour ce faire, allez dans la propriété « Source » puis sélectionnez « Cadran1.jpg »




Voila ! Notre cadran d’horloge est opérationnel !

Passons maintenant à la deuxième partie, celle de la disposition des aiguilles.

Pour faire simple, nous allons utiliser des rectangles que nous allons paramétrer pour être en mesure de tourner autour du point central. Let’s do it :


Cliquez une nouvelle fois sur le bouton « Assets » de votre barre d’outils puis tapez le mot « Rectangle » dans le champ de recherche.



Prenez le composant « Rectangle » et glissez-le dans la zone de dessin.



Ajustez-le à l’aide de votre souris pour que celui-ci soit disposé comme une aiguille sur le cadran pointant vers midi.



Dans l’onglet « properties », assignez une couleur rouge à ce rectangle






Maintenant, voici la partie magique. Nous allons paramétrer le rectangle pour que celui-ci puisse tourner autour de l’axe central du cadran.



Pour que vous vous rendiez bien compte du délire, ouvrez la partie « Transform » dans l’onglet des propriétés de votre rectangle, puis choisissez le sous onglet « Rotate » représenté par une flèche qui fait un demi-cercle sur la gauche.



Dans cet onglet, vous allez trouver une propriété « angle ».


Si vous vous amusez à faire varier sa valeur, vous remarquerez que le rectangle tourne sur lui-même, l’axe de rotation étant en son centre.







Notre but est donc de changer le point de rotation pour que notre rectangle ne tourne plus en fonction de son centre, mais en fonction de sa base.


Remettez la propriété « Angle » à 0, puis cliquez sur l’onglet « Center Point »




Les propriétés X et Y représentent les coordonnées du point de rotation. Celles-ci sont sous forme de ratio (valeur décimale entre 0 et 1) le point 0,0 étant en haut à gauche de la forme et le point 1,1 en bas à droite.



Ainsi, si je veux que mon point de rotation se fasse au centre de la base de mon rectangle, X doit avoir la valeur 0.5 et Y la valeur 1.




Pour le vérifier, remettez-vous sur l’onglet « Rotate » et jouez une nouvelle fois avec la propriété « Angle ». Normalement vous verrez votre rectangle tourner autour du centre du cadran comme une aiguille.




Cool n’est-ce pas ?



En considérant que nous venons de faire l’aiguille des minutes, faite de même pour l’aiguille des heures et des secondes. Nous dirons que l’aiguille des heures sera de couleur verte, et l’aiguille des secondes de couleur bleu.



Allez-y, je vous attends…


Voilà, au bout de quelques minutes, vous devriez avoir ce résultat (en partant du principe que tous vos angles ont des valeurs différentes :




Maintenant que toutes nos aiguilles sont placées sur notre cadran, et qu’elles sont capables de tourner correctement, nous allons créer une propriété à notre contrôle afin de placer les aiguilles par code en fonction d’un TimeSpan.



Pour ce faire, nous allons devoir jouer sur les propriétés « Angle » de chacune de nos aiguilles, il est donc important de les nommer pour pouvoir les manipuler plus tard.



Sélectionnez par exemple l’aiguille rouge, puis retournez dans l’onglet « Rotate » de la fenêtre des propriétés.



Cliquez sur le petit carré blanc situé à droite de « Angle », et choisissez le menu « Go to source »



Le code Xaml lié à notre rectangle s’affiche dans Blend avec la propriété « Angle » sélectionnée.



Pour manipuler celle-ci depuis l’extérieur il existe plusieurs possibilités.


L’une d’entre elle consiste à faire du « data binding », c’est-à-dire de la liaison de données. C’est un mécanisme très puissant qui permet de découpler les données de l’IHM afin de pouvoir faire du code testable.



Dans notre cas, le code que nous voulons ajouter n’est pas du code métier qu’il est nécessaire de tester, mais purement et simplement du code de manipulation graphique, c’est pourquoi nous opterons pour du code « Behind », c’est-à-dire embarqué à notre contrôle.



Pour faciliter la manipulation de la propriété « Angle » par le code behind, nous allons la nommer afin de pouvoir y accéder plus facilement.


Rajoutez dans le code xaml un attribut nommé « x :name » auquel nous donnerons le nom « AngleMinutes »



Nous ferons exactement la même chose avec « AngleSecondes » et « AngleHeures »





Nous allons maintenant passer à la partie programmation, celle que vous préférez.

Pour cela, retournons dans Visual Studio que nous avons laissé ouvert en arrière-plan. (N’oubliez pas au préalable de bien tout sauvegarder dans blend)



Normalement, une boite de dialogue d’avertissement doit apparaître dans VS.




Cliquez sur le bouton « Reload All »



Le designer de Visual Studio vous montre le contrôle que nous venons de créer dans « Blend », il s’agit maintenant de créer la propriété qui va bien pour que notre horloge soit contrôlable depuis l’extérieur.



Pour cela, WPF nous impose de créer une « DependencyProperty ».



Ouvrez « MonHorloge.xaml.cs » puis tapez « propdp » + tabulation.


Un snippet de visual studio vous génère un bout de code un peu obscure tel que :




Première étape : on modifie le code pour proposer un TimeSpan du nom « Time »




Deuxième étape : lorsque le programmeur assignera cette propriété, nous voulons pouvoir définir la valeur des angles de nos aiguilles.



Pour cela, le 4ème paramètre de notre fonction « Register » permet d’ajouter une CallBack à son constructeur qui sera appelée lorsque la DépendencyProperty sera définie. C’est exactement ce que nous voulons :)



Voici comment faire :



public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof (TimeSpan), typeof (MonHorloge), new PropertyMetadata(default(TimeSpan), PropertyChangedCallback));

private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
    throw new NotImplementedException();
}

Il ne reste plus qu’à taper le code qui va bien dans la Callback.



private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
    var component = dependencyObject as MonHorloge;
    var timeSpan = (TimeSpan)dependencyPropertyChangedEventArgs.NewValue;
    if (component != null && timeSpan != null) {
          component.AngleHeures.Angle = (timeSpan.TotalMinutes/60/12)*360;
          component.AngleMinutes.Angle = (timeSpan.TotalMinutes%60)/60*360;
          component.AngleSecondes.Angle = (360/60)*timeSpan.Seconds;
    }
}

A partir de là, nous avons déjà une première version d’horloge fonctionnelle !

Teste de la première version de notre contrôle

Pour la tester, créer un nouveau projet d’application WPF.



Ajouter « MaLibrairie » dans ses références :



Compilez le projet, puis allez dans la « Toolbox ».


Le contrôle « MonHorloge » Apparait en haut de la barre d’outils !



Prenez le contrôle et glissez le dans le designer de votre fenêtre principale



Notre horloge apparait !



Par contre, un petit détail risque de vous chiffonner.

Si vous redimensionnez la taille de l’horloge, les aguilles ne se trouvent plus au centre du cadran.


Ceci est dû au fait que nous avons dessiné notre composant dans une grille qui travaille avec des positions relatives. Pour régler ce problème, il suffit de remplacer notre grille par un « Canvas » afin que nos aiguilles travaillent avec des positions absolues.


Pour cela, ouvrez l’horloge dans VisualStudio, puis remplacez les tags par dans le code xaml




Enregistrez et recompilez.

Lorsque vous retournerez dans le designer de votre projet de test, tout rentrera dans l’ordre.




Maintenant que ce petit détail est réglé, vous aimeriez voir les aiguilles tourner, non ?



Rien de plus simple !



Commencez par donner un nom à cette horloge, par exemple « HorlogeFr » en utilisant la fenêtre de propriétés de Visual studio.




Ouvrez ensuite « MainWindows.cs », puis créez un DispatcherTimer pour définir l’heure toute les secondes.



public partial class MainWindow : Window
    {
        readonly DispatcherTimer _timer = new DispatcherTimer();
        public MainWindow()
        {
            InitializeComponent();
            SetHorlogeFr();
            _timer.Interval = new TimeSpan(0,0,1);
            _timer.IsEnabled = true;
            _timer.Tick += timer_Tick;
        }

        void timer_Tick(object sender, EventArgs e)
        {
            SetHorlogeFr();
        }

        private void SetHorlogeFr()
        {
            HorlogeFr.Time = DateTime.Now.TimeOfDay;
        }
    }

Comme vous pouvez le voir, à chaque seconde, nous assignons à la DependencyProperty « Timer » de notre contrôle horloge l’heure qu’il est afin que la position des aiguilles se recalcule.



Il ne vous reste plus qu’à démarrer votre projet de test pour constater le résultat.



Alors ? Pas mal pour des mecs qui ne font que du mode console :)



A ce stade, on a déjà un truc bien, mais pour explorer encore un peu plus WPF, nous allons rajouter la possibilité de changer le cadran d’horloge à la volée, le tout en passant par un petit panel de configuration animé.

Panneau de configuration du cadran

Donc, pour commencer, nous allons retourner sous Blend, qui soit dit en passant tournait en fond de tâche.



Blend a détecté que le projet a été modifié depuis la dernière fois. Normal, car nous avons changé la grille en canvas.

Cliquez sur le bouton « Yes to all » pour accepter tous les changements.



Je rappelle que notre but est de pouvoir changer le cadran de l’horloge à la volée. La première chose à faire consiste donc à trouver d’autres cadrans, et à les dimensionner selon la taille du cadran originale.




Partons sur google Image à la recherches de quelques cadrans supplémentaires :



Je vais prendre par exemple le cadran avec un cheval ailé.


Je vois que ses dimensions sont en 1616x1616 alors que le cadran original était en 500x500.



Le problème c’est que je ne sais pas à quoi correspondent ces dimensions.



Je vais vous montrer comment nous allons régler le problème très simplement avec Blend et MSPaint.


En premier lieu, j’enregistre cette image sur mon disque dur, puis je la glisse dans le répertoire « Cadrans » de ma solution



Je renomme l’image en cadran2.jpg, puis je fais un clique-droit à « Edit externally » sur cadran1.jpg


Celui-ci s’ouvre dans Paint



Cliquez sur « Image » puis « Redimentionner », une fenêtre s’affiche.

Choisissez le bouton radio « Pixels »



Nous voyons que notre image de référence fait 225x225 pixels. Voici donc la taille dans laquelle la nouvelle image doit être redéfinie.




Pour cela, on referme Paint, puis on retourne sous Blend pour faire un clic droit à « Edit externally » sur cadran2.jpg



On refait « Image » puis « Redimentionner » à bouton radio « Pixels », et on entre la valeur 225 dans « Horizontal ». La valeur « Vertical » se met automatiquement à 225 elle aussi.



Cliquez sur OK



Il ne reste plus qu’a enregistrer l’image redimentionnée, et à refermer Paint.


Touvez deux ou trois autres cadrans sur google, et mettez les à la bonne taille en utilisant cette méthode.



Ajout de la liste

Une fois ceci fait, nous allons passer à l’incrustation d’une liste et d’un bouton dans notre contrôle, autant vous dire qu’on va devoir manipuler un peu le designer et le code xaml généré pour arriver à nos fins.




Donc, voici comment se présente votre composant dans Blend



Nous allons créer une grille avec deux colonnes.



La première contiendra le canvas qui contient notre horloge, et la deuxième notre liste de choix.




Dans l’onglet « Object and Timeline », faites un clic droit sur « canevas » puis choisissez « Group into à Grid »



Sélectionnez ensuite « UserControl » pour activer les outils de manipulation dans le designer.



Dans le designer, sélectionnez le carré sur le côté droit, puis étirez jusqu’à avoir un espace suffisant pour ajouter une grille.




Dans l’onglet « Object and Timeline », sélectionnez « Grid » pour activer les outils de manipulations dans le designer, puis ajouter une nouvelle colonne à votre grille en cliquant sur le bord supérieur droit de l’horloge.



Allez ensuite dans la barre d’outils, et choisissez un composant de type « ListBox »



Faite glisser la « ListBox » dans la deuxième colonne de votre grille



Nous allons maintenant aller changer quelques petites propriétés directement en xaml.




Cliquez sur le petit bouton « code » situé dans la barre du coin supérieur droit du designer.



Un éditeur xaml va apparaitre, le code doit ressembler à peu près à ceci :



Nous allons changer ce code pour que :

-          Le canvas soit sur la première colonne, et ne déborde pas sur la deuxième

-          Le ListeBox soit sur la deuxième colonne, et ne déborde pas sur la première

-          La deuxième colonne de la grille soit en taille automatique pour s’adapter à la taille du listBox



Tout cela pourrait se faire avec les propriétés de design, mais c’est beaucoup plus rapide de passer directement par le xaml.



D’ailleurs, une fois que vous serez familiarisé avec Blend, vous remarquerez que beaucoup de choses peuvent directement être faite en xaml, ce qui fait gagner pas mal de temps.




En premier lieu, voici les modifications à apporter au niveau des définitions de colonnes :




Ensuite, nous indiquons au canevas qu’il est dans la colonne 0 de la grille


Descendez tout en bas, et supprimez tous les attributs du nœud « ListBox » à l’exception de « Grid.Column= »1 » »



Voilà pour cette partie.


En retournant dans le designer, votre composant doit ressembler à ceci.



Template et Animation

Alors, maintenant nous allons commencer à entrer dans le dur, là ou WPF apporte pas mal d’avantages par rapport à du Winform standard.



Si je vous dis que dans notre ListBox, je veux afficher chaque candran sous forme d’un libellé, que me répondez-vous ?



Facile !



Ok, maintenant si je vous dis que le libellé doit avoir une police un peu stylé ?



Jouable !



Très bien, et si je vous dis que je veux un aperçu de l’image à gauche de chaque libellé qui soit assez gros pour voir quelque chose ?



Ça se complique !



Et si en plus je veux qu’au survol de la souris, l’image s’agrandisse pour avoir un meilleur aperçu ?



Oula ! On n’est pas chez Apple ici !



Grace à WPF, vous pouvez prendre n’importe quel contrôle de base et le redessiner comme bon vous semble grâce aux templates ! C’est ça qui est magique !



Mais comment faire ?




Faites un clic droit sur l’objet « ListBox » dans l’onglet « Object and Timeline », et choisissez le menu « Edit Additional Templates  à Edit generated Items (ItemTemplate ) » à «Create Empty»




Une boite de dialogue s’ouvre.




Choisissez dans la ComboBox de « This document » le choix « ListBox » car seul ce contrôle se verra appliquer le template (pas besoin de partager le template à tous les composants du contrôle)



En cliquant sur « OK », le designer ne montre plus le contrôle de l’horloge, mais affiche a quoi va ressembler graphiquement le Template




Donc là, pour ceux qui n’auraient pas compris, Blend nous donne la possibilité de dessiner à quoi va ressembler chaque item de notre ListBox !



Il suffit donc de  concaténer une image avec un TextBox, et le tour est joué !




Dans « Object and Timeline », faites un clic droit sur « Grid » et choisissez « Change Layout Type à Stackpanel». Cela permettra d’avoir quelque chose de plus rigide pour construire notre item 



Augmentez le zoom de votre designer pour pouvoir insérer des composants au stackpanel



Allez chercher dans la barre d’outils une image et glissez la dans le stackpanel.



Glissez-y ensuite un TextBlock.




Votre template va ressembler à quelque chose comme ça :



Nous voulons que le texte soit à droite de l’image, nous sélectionnerons donc le stackpanel dans « Objects and Timeline » puis nous irons dans la fenêtre des propriétés dans la partie « Layout » changer la propriété « Orientation » de « Vertical » à « Horizontal »



Voici l’impact sur le designer



Comme vous pouvez le constater, le texte n’est pas centré. Nous allons donc nous en occuper.




Cliquez sur le TextBlock puis ouvrez l’onglet « Properties ». Dans la partie « Layout » choisissez « Left » pour « HorizontalAlignement » et « Center » pour « VerticalAlignement »



Le TextBlock dans le designer devient tout de suite beaucoup mieux :



Pour avoir une meilleure vision de à quoi cela va ressembler, cliquez sur le composant « Image » puis dans les « Properties », choisissez un cadran dans la combobox proposée par la propriété « Source » de la partie « Common »




Ceci permet de bien prendre conscience du rendu final.




Attention, cette affectation est temporaire, vous devrez la remplacer par un data binding lorsque les manipulations seront terminées.



Bien. Jusque-là les choses se sont assez bien passées.



Mais maintenant, comment faire pour que l’image s’agrandisse lors d’un survol de la souris ?



Une fois de plus, le moteur WPF va nous sauver la vie en nous permettant de mettre en place ce genre d’effet sans taper une ligne de code.



Tout d’abord, commençons par mettre l’image du cadran à une taille raisonnable, disons 50x50.


Cliquez sur l’image et changez les propriétés With et Height




Très bien.



Maintenant, nous voulons que lorsque l’utilisateur passe sa souris au-dessus de l’image, celle-ci s’agrandit, et lorsqu’il sort le curseur de l’image, celle-ci reprend son état initial. Facile :)



Pour faire cela avec WPF, nous allons mapper des « StoryBoard » aux événements « OnMouseEnter » et « OnMouseLeave » de notre image.



Mais c’est quoi un storyboard ?



Pour faire simple, c’est une ressource éditable qui se présente sous la forme d’une ligne de temps, un peu comme dans les logiciels de montage vidéo.
Cette ligne de temps permet de spécifier à WPF des propriétés qui doivent changer dans le temps. Et là où c’est bien fait, c’est que WPF s’occupe de la transition graphique à votre place.



Ici par exemple, nous allons créer un storyboard pour que la taille de l’image passe de 50x50 à 150x150 en 200ms. Il nous suffira simplement de créer une image clé dans laquelle notre composant possède les nouvelles dimensions, et WPF s’occupe de tout pour nous !!



Allez zou, je vous montre comment faire.




Dans les onglets situés en bas de Blend, cliquez sur l’onglet Triggers. Et sélectionnez dans l’onglet « Objects and Timeline » le composant « image »



Cliquez sur le bouton « + Event » pour créer un nouvel évènement à mapper à un storyboard.



Dans les listes déroulantes situées à côté de « When », changez « target-element » par « image » et changez « Loaded » par « MouseEnter »



Cliquez sur le bouton « + » situé à droit des listes déroulantes.




Normalement ce message apparait :



Cliquez sur « OK » pour créer un storyboard qui sera lié avec cet évènement



Nous venons ici de créer un storyboard du nom de « OnMouseEnter1 », celui-ci possède un petit rond rouge sur sa gauche ce qui signifie qu’il est en cours d’enregistrement. D’ailleurs, vous remarquez aussi que le designer est entouré d’un cadre rouge avec une petite annotation en haut.



A ce stade, nous allons définir le comportement graphique qui va-t-être joué lorsque la souris va passer au-dessus de notre image.




Cliquez sur le composant « image » dans « Objects and timeline » puis déplacez le curseur de votre ligne de temps à 0:00.200



Allez dans les propriétés de votre image, et changez le « Width » et le « height » pour qu’ils soient tous les deux à 150



Vous remarquez dans la ligne de temps qu’une image clé est apparue.



Et aussi que l’image dans le designer est plus grosse.



Pour voir l’effet que cela va donner, cliquez sur le bouton « Play » de la ligne de temps de votre storyboard.




Normalement, vous voyez dans le designer l’image passez de 50x50 à 150x150.



Cool non ?



Faisons maintenant l’inverse, c’est-à-dire que lorsque je vais retirer ma souris de l’image, celle-ci va se rétracter.




Ajoutez un nouvel évènement en cliquant sur le bouton « +Event »





Dans les listes déroulantes situées à côté de « When », changez « target-element » par « image » et changez « Loaded » par « MouseLeave »




Si vous ne voyez pas « Image » dans les choix de la liste de « target-element », prenez garde à bien avoir cliqué sur « image » dans « Objects and Timeline »



Je vais vous montrer comment gagner un peu de temps en évitant de récréer complétement un storyboard.



Il suffit de dire à blend de dupliquer notre premier storyboard, puis de l’inverser :)




Dans « Objects and Timeline », cliquez sur le petit menu déroulant à la droite du « + » situé au niveau du nom du storyboard puis cliquez sur « Duplicate »



Renommez le nouveau storyboard de « OnMouseEnter_Copy1 » en « OnMouseLeave 1»






Déroulez une nouvelle fois le petit menu et choisissez « Reverse »


Il ne vous reste plus qu’à assigner ce nouveau storyboard à votre évènement « MouseLeave » :)



Cliquez sur le « + » situé à droite de votre événement.



Puis choisissez-le storyboard « OnMouseLeave1 »



Et voilà ! Vous venez en quelques clics de créer une petite animation sympa sans effort !



Bon, pourquoi ne pas continuer sur notre lancée et maintenant faire l’ouverture de la liste de paramétrage de cadran ?




Sortez du designer de template en cliquant sur « MaListe »





Bouton de paramétrage

Nous allons ajouter un bouton dans le coin supérieur droit de l’horloge qui nous permettra d’ouvrir la liste à droite afin de choisir un cadran approprié.



Dans votre barre d’outils, recherchez un contrôle de type « Button »



Glissez le bouton dans le coin supérieur droit de l’horloge



Vous conviendrez que ce bouton n’est pas très sexy.



Ce que nous voulons, c’est un petit bouton assez discret contenant une image représentative.




Pour trouver notre image, allons sur www.iconfinder.com à la recherche d’une icône gratuite pour notre bouton.



Celle du milieu semble bien. Je clique dessus afin de la télécharger au format 16x16



Une fois celle-ci enregistrée sur mon disque, je la renomme « param_button.png » puis je la glisse directement dans mon projet Blend.



Passons au paramétrage du bouton.



Cliquez dessus dans le designer et allez dans la fenêtre des propriétés.


Effacez en premier lieu le contenu de la propriété « Content », puis définissez le « Width » et le « Height » à 16



Pour définir l’image du bouton, allez dans la partie « Brush » puis cliquez sur « Background ».



Choisissez le bouton onglet « Tile brush » puis mettez en « imageSource » notre « param_button.png » téléchargée précédemment.




Définissez aussi le « Stretch » à « None »



Il ne vous reste plus qu’à placer le bouton en haut à droite du composant horloge



Maintenant, nous allons définir ce qui doit se passer lors d’un clique sur ce bouton.



Pour rappel, nous voulons que la liste située à droite de l’horloge s’étende vers la droite pour pouvoir sélectionner un cadran. Nous allons donc mapper un storyboard qui fait cette action à l’évènement « Click » de notre bouton.



C’est parti !


Cliquez sur le bouton « + Event » de l’onglet « Triggers »




Après avoir bien vérifié que l’élément « Button » est sélectionné dans « Object and Timeline », choisissez dans les listes déroulantes à côté de When « Button » et « Click », puis cliquez sur le bouton «+ »



Un message proposant de créer un nouveau storyboard apparait. Cliquez sur OK.



Un storyboard « OnClick1 » a été créé.




Sélectionnez le composant « MaListe » dans « Object and Timeline », puis déplacez le curseur de l’image clé sur 0 :00.300



Dans la fenêtre « Properties » de « MaListe », essayez de changer la valeur de la propriété « Width »



Si la valeur est paramétrée en « Auto » comme dans la capture d’écran, un message d’erreur apparait vous expliquant qu’il n’est pas possible d’animer une propriété qui possède une valeur auto-calculée.



Dans ce cas, il faut fermer le storyboard et appliquer une valeur fixe à la propriété « Width » de votre liste.




Une fois la propriété défini, re-sélectionnez le storyboard « OnClick1 » dans « Object and Timeline »



Sélectionnez le composant « MaListe » dans « Object and Timeline », puis déplacez le curseur de l’image clé sur 0 :00.300



Dans la fenêtre « Properties » de « MaListe », changez la valeur de la propriété « Width ».



Normalement, vous n’avez plus le message d’erreur vu précédemment.



Une image clé vient d’apparaitre dans le storyboard.




Vous pouvez maintenant cliquer sur le bouton « play » pour visualiser l’animation.




Voilà, nous venons de faire en sorte que la liste se déploie sur la droite lorsque l’on clique sur le bouton.

Nous allons maintenant mettre en œuvre le mécanisme de rétractation de la liste qui se produira lorsque la souris sortira de celle-ci.




Nous allons ajouter un évènement dans les triggers en cliquant sur le bouton « +Event »




Après avoir pris soins de sélectionner « MaListe » dans « Objects and Timeline », on déroule les listes de choix à coté de « When » pour définir l’événement « MouseLeave » de « Maliste »



Ensuite nous dupliquons le storyboard « OnClick1 »



Puis nous le renommons « OnListMouseLeave »




Nous inversons le storyboard pour donner l’effet de rétractation.



Puis nous assignons ce storyboard à l’évenement « MouseLeave » défini précédemment.





Voila ! Tous nos comportements graphiques sont définis !



Il ne nous reste plus qu’à passer à la partie programmation pour ajouter des items à la liste de choix, puis à définir quelques options de « binding », et le tour est joué !

Databinding

Fermez le programme « Blend » et retournez dans VisualStudio.


Double cliquez sur « MonHorloge.xaml » pour visualiser le code Xaml de votre horloge



Si vous regardez ce code, vous trouverez tous les éléments que nous avons définis dans blend. Les storyboard, les évènements de clique, les boutons, les templates, tout y est.



Nous allons jeter un œil à ce code pour variabiliser les parties liées aux images de cadans, qui pour l’instants sont toutes définies en dur.




Par exemple le cadran principal est défini pour être le fichier cadran1.jpg



Nous avions aussi défini ce même fichier dans le « item template » de notre liste afin d’avoir une meilleure visualisation.



Nous voyons aussi dans ce même bloque de code que le texte est défini en dur.



Donc, quel est le plan d’action ?



Dans un premier temps, nous allons variabiliser le texte et l’image de nos items de liste, puis ensuite nous allons lier l’image du cadran principal avec l’image de la sélection courante dans notre liste.



Pour variabiliser les items, nous allons utiliser le mécanisme de « Data Binding » utilisé par WPF.



Ce système très puissant permet de dire à un composant graphique qu’il doit aller puiser une valeur dans une propriété dont le nom lui est indiqué.



Mais attention, le data binding est une arme à double tranchant car celui-ci est sous forme de couplage faible.

C’est-à-dire que nous allons indiquer à WPF : « Pour la propriété graphique X, tu appliqueras la valeur Y contenue dans la propriété de classe de nom Z »



Et WPF nous fait confiance. Il n’ira pas vérifier à la compilation que le membre est bien lié à une classe dont l’une des propriétés possède bien le nom Z. C’est à nous de nous débrouiller pour lui fournir les bonnes informations.




Voyez comment nous allons définir le binding pour la source de l’image ainsi que pour le texte de notre TextBlock :



Nous venons d’indiquer à WPF : « Tu trouveras le chemin de l’image dans la propriété ‘Image’ et le texte à afficher de la propriété ‘Texte’ de l’item que nous te passerons »



Grâce à ce procédé, nous pouvons ajouter des items sous forme de classes anonymes à notre liste. Tant que celles-ci possèdent nos deux propriétés, WPF n’y vois aucun inconvénient !



public MonHorloge()
{
    InitializeComponent();
    MaListe.Items.Add(new { Image = "Cadrans/cadran1.jpg", Texte = "Standard"});
    MaListe.Items.Add(new { Image = "Cadrans/cadran2.jpg", Texte = "Romain" });
    MaListe.Items.Add(new { Image = "Cadrans/cadran3.jpg", Texte = "Jaguar" });
    MaListe.Items.Add(new { Image = "Cadrans/cadran4.jpg", Texte = "Emeraude" });
    MaListe.Items.Add(new { Image = "Cadrans/cadran5.jpg", Texte = "Classique" });
}

L’avantage avec le databinding, c’est que celui-ci permet aussi d’aller chercher des informations dans les propriétés d’un sous composant de notre arborescence. C’est exactement ce dont nous avons besoins pour définir notre cadran principal !




Allez dans le code WPF de l’image, et définissez le binding de la façon suivante :



La syntaxe indique la chose suivante : « tu iras chercher l’image à afficher dans la propriété ‘SelectedItem.Image’ de l’élément ‘MaListe’ »



Une fois ceci fait, on a presque terminé.



Effectivement, il faut définir dans le code au moins un « SelectedItem » par défaut, sinon notre horloge n’aura pas de cadran au démarrage.



Pour cela, il suffit de rajouter la ligne de code suivante à la fin du constructeur de « MonHorloge.cs »




MaListe.SelectedItem = MaListe.Items[0]

Et voilà, c’est enfin terminé !


Maintenant que vous disposez d’un contrôle d’horloge, Vous pouvez l’utiliser dans toutes vos applications qui en ont besoins comme bon vous semble !

Conclusion

Nous venons de voir comment WPF facilite grandement la conception d'IHM pour votre application, mais ce n'est pas tout !

Car grâce au langage Xaml et aux mécanismes de "Data binding", WPF permet enfin un vrai découplage entre votre code et votre IHM, ce qui présente encore plein d'autres avantages :
  • Vous pouvez intégrer un graphiste/designer à votre projet sans que celui-ci possède nécessairement de grosses compétences en développement.
  • Le découplage total permet de pousser la testabilité de votre logiciel sur la partie interface, notamment grâce au pattern MVVM.
  • Les interfaces que vous réalisez en Xaml sont compatibles avec toutes les technologies de développement Microsoft.
Bref, si un allergique à la programmation graphique comme moi est convaincu par cette technologie, autant vous dire que vous pouvez y aller tranquille :)

Vérifiez la validité d’un numéro de sécurité sociale avec SQL Server.
09 oct. 2014
Lorsqu’on est en France et que l’on travaille dans les logiciels de gestion pour le domaine de la santé, les questions de comment remonter tous les numéros de sécurité sociale « non valide » d’une table d’identifiants ou comment vérifier la validité d’un NIRPP dans un trigger arrivent assez rapidement.


Or, le contrôle d’un NIR n’est pas si facile lorsque l’on veut couvrir un maximum de cas (numéro temporaire pour les étrangers, département d’outre-mer, corse, calcul de clé, …) .


Pour ceux qui veulent éviter de perde plusieurs heures (voire plusieurs jours) sur la meilleure solution à adopter pour ce problème très franco-français, je vais essayer de vous synthétiser ma solution pour que vous puissiez la réutiliser dans votre base de données grâce à une bonne vielle requête SQL.



Tous d’abord, nous allons devoir référencer une assembly dans SQL Server pour nous permettre d’accéder à des fonctionnalités de RegEx.


Ces fonctionnalités ne sont pas fournies en natif par Microsoft pour la simple et bonne raison que celui-ci conseille de passer par du code C# pour des questions de simplicité et de performances.


Pour éviter de vous prendre la tête, exécutez au préalable le script SQL suivant.

Une fois ceci fait, vous allez pouvoir facilement contrôler la validité d’un numéro de sécurité sociale via une simple ligne de commande SQL !

Par exemple, pour obtenir tous les NIRPP non valides de la table « Identifiant » dont le numéro est dans le champ « Valeur », exécutez la requête suivante :


select * from Identifiant where (dbo.RegexMatch(Valeur, '^([1-37-8])([0-9]{2})(0[0-9]|[2-35-9][0-9]|[14][0-2])((0[1-9]|[1-8][0-9]|9[0-69]|2[abAB])(00[1-9]|0[1-9][0-9]|[1-8][0-9]{2}|9[0-8][0-9]|990)|(9[78][0-9])(0[1-9]|[1-8][0-9]|90))([0-9]{3})([0-8][0-9]|9[0-7])',1) = 0
OR 
 CAST(SUBSTRING(Valeur, 14, 2) AS int) <> CASE 
 WHEN valeur like '%A%' THEN (97 - ((CAST(SUBSTRING(REPLACE(Valeur,'A','0'), 1, 13) as bigint) - 1000000) % 97)) 
 WHEN valeur like '%B%' THEN (97 - ((CAST(SUBSTRING(REPLACE(Valeur,'B','0'), 1, 13) as bigint) - 2000000) % 97)) 
 ELSE (97 - (CAST(SUBSTRING(Valeur, 1, 13) as bigint) % 97)) 
 END ) 

Pour obtenir les NIRPP valides, il suffit de replacer le 0 par 1, le OR par un AND et le <> par un = ce qui donne :



select * from Identifiant where 
(dbo.RegexMatch(Valeur, '^([1-37-8])([0-9]{2})(0[0-9]|[2-35-9][0-9]|[14][0-2])((0[1-9]|[1-8][0-9]|9[0-69]|2[abAB])(00[1-9]|0[1-9][0-9]|[1-8][0-9]{2}|9[0-8][0-9]|990)|(9[78][0-9])(0[1-9]|[1-8][0-9]|90))([0-9]{3})([0-8][0-9]|9[0-7])',1) = 1
AND 
 CAST(SUBSTRING(Valeur, 14, 2) AS int) = CASE 
 WHEN valeur like '%A%' THEN (97 - ((CAST(SUBSTRING(REPLACE(Valeur,'A','0'), 1, 13) as bigint) - 1000000) % 97)) 
 WHEN valeur like '%B%' THEN (97 - ((CAST(SUBSTRING(REPLACE(Valeur,'B','0'), 1, 13) as bigint) - 2000000) % 97)) 
 ELSE (97 - (CAST(SUBSTRING(Valeur, 1, 13) as bigint) % 97)) 
 END ) 

Et voilà ! Avec peu d’effort, vous allez pouvoir faire des requêtes pour nettoyer vos bases de données, puis faire des Check Constraint et autres triggers pour que tout le monde arrête de pourrir vos tables avec des numéros de sécu invalides !


Alors, merci qui ? :p

La transformation de données – Pierre angulaire de l’architecture SOLID.
02 sept. 2014
Si la programmation est une activité déjà bien complexe lorsque l’on travaille seul, la difficulté est largement amplifiée quand le programme nécessite d’y travailler à plusieurs, car ce dernier cas nécessite en général une architecture divisée en modules à périmètre restreint pour permettre une bonne séparation des concepts.

Bien que ce type d’architecture soit souvent mis en œuvre, l’interdépendance entre les modules constitue rapidement le point faible qui oblige les développeurs à bafouer toutes bonnes pratiques de séparations pour arriver à leur fin.


Dans cet article,  je vais traiter d’une solution évoluée de « Transformation de données » pour garantir à votre architecture modulaire de rester le plus intègre possible au niveau des principes SOLID.

Read the english version of this article on CodeProject


Introduction

La séparation des parties d’un logiciel en plusieurs couches ou modules est quelque chose de très répandu dans le monde du développement.


Cette pratique qui est censée faciliter la maintenabilité, l’évolutivité et la testabilité du code possède plusieurs pièges dont le plus classique consiste à créer de l’interdépendance lorsqu’il est nécessaire de manipuler des objets à la frontière des modules.


Cette problématique est d’autant plus grande que la plupart des Framework, outils et exemples poussent les développeurs à tomber dans ces pièges architecturaux qui semblent être pour le néophyte pratiquement inévitables !


Cet article va traiter d’une architecture spéciale dans laquelle la transformation de données jouera un rôle important. Cette mécanique permettra de mettre en place une parfaite séparation entre vos modules tout en permettant une communication efficace.



La dérive d’une architecture

Pour vous exposer les problèmes les plus fréquemment rencontrés sur le terrain, prenons le cas classique d’une application de gestion avec une architecture  en 3 couches :




La couche présentation contient le code lié aux vues, la couche business le code qui concerne la logique métier (règles de validation, entités, workflow, …) et la couche data le code permettant d’interagir avec une base de données.


Tout cela semble apporter une bonne séparation dans la théorie, mais comment ça se passe la plupart du temps ?


En général dans une application ayant ce type d’architecture, ceux qui travaillent la couche DAL ont la responsabilité de fournir et enregistrer des objets business dans la base de données. Il est donc nécessaire pour eux de référencer la couche BLL afin de pouvoir les manipuler à leur aise.


Puis les développeurs du DAL se rendent rapidement compte que leur vie sera largement facilitée s’ils utilisent un Framework pour interagir avec leur base de données. Et comme la plupart des framework modernes (comme entity framework) manipulent des « entités » auto générées à partir du schéma de données, il semble donc beaucoup plus judicieux pour eux d’utiliser ces entités en tant qu’objets métiers. 

Surtout lorsque :
  •  Le framework possède un système de détection automatique des changements qui se base sur les instances de classes.
  • Le framework permet de travailler avec des objets dit POCO censés donner la possibilité de faire des classes libres de toutes les contraintes liées au schéma de données.


Le choix est donc très rapidement prit : la couche BLL possédera des entités générées par le framework qui permet de manipuler la base de données.


Passons maintenant à la couche « Présentation ».


Ceux qui ont la charge de développer cette partie doivent permettre aux utilisateurs de manipuler les objets métiers (clients, factures, pièces, tableaux de bord, états de workflow, …) depuis des composants graphiques parfois très complexes (tableaux croisés dynamiques, calendriers, listes évoluées, indicateurs, etc.)


Heureusement pour eux, il existe beaucoup de composants de ce type sur le marché, et la plupart d’entre eux permettent de se lier directement à des sources de données.


Pour cela, la couche présentation a donc besoin de connaitre les types d’objets qu’elle sera amenée à manipuler. Il est donc nécessaire de référencer la couche BLL à la couche présentation.


Mais mettre en place une interface graphique ne se limite pas seulement à dessiner ou arranger des composants graphiques entre eux. Il faut aussi du code pour manipuler les objets afin de les rendre présentables. Les design pattern permettant de traiter ce sujet (MVC, MVP, MVVM, …) semblent avoir besoin d’accéder aux objets présents dans la BLL. Il est donc très fréquent de retrouver du code d’IHM qui manipule directement ces entités.


On passe donc d’un système théoriquement parfaitement séparé à quelque chose qui dans la pratique ressemble à ceci :




Les choix techniques de communications entre nos modules ont conduit notre système à devenir quelque chose de fortement inter dépendant.


Certes, si l’on regarde le projet d’un point de vue arborescence, tout semble bien séparé, mais un rapide graph de dépendance montrera que les différentes DLL censées représenter les couches du logiciel sont en réalité des sortes de dossiers dans lesquelles le code n’est rangé que parce que cela parait logique.


Si quelqu’un a le malheur de vouloir revoir l’architecture, il se fera très rapidement opposé le fait qu’il est impossible de faire du remaniement de code pour cause d’enchevêtrement technique, et pour cause : les instances des entités métiers sont directement utilisées par le framework d’accès aux données, et sont manipulées directement par les composants graphiques ! Cette pratique semble d’autant plus légitime que beaucoup d’exemples de code présents sur MSDN, ou d’autres sites, mettent en avant ces techniques pour des soucis de simplicité. Seulement les débutants prennent ce code pour argent comptant, et ne se rendent pas toujours compte des conséquences néfastes que cela apporte, comme la non testabilité et le couplage fort qui freinent fortement l’évolutivité du logiciel et favorise les bugs par effets de bords.

Revenons aux fondamentaux

La séparation du logiciel en plusieurs unités appelées « couche » ou « module » vient du besoin de pouvoir isoler les différents domaines de responsabilités de notre système pour servir plusieurs objectifs :

  • Pouvoir travailler sur un domaine sans impact majeur sur les autres domaines.
  • Pouvoir substituer l’implémentation d’un domaine par une autre implémentation.
  • Pouvoir tester chaque domaine de manière indépendante, selon ses spécificités techniques

Une architecture en couches verticales n’est donc pas très adaptée.


Pour servir tous ces objectifs, il est nécessaire en premier lieu de remplacer l’architecture en couches verticales par quelque chose de plus logique.


Commençons par faire un petit diagramme de Venn afin d’avoir un aperçu de notre besoin :  


Nous voyons très clairement que le domaine de responsabilité business est l’élément central de notre système. Si on y réfléchi bien c’est même le seul périmètre dont nous aurions besoins si nos programmes se suffisaient à eux-mêmes.


Malheureusement, un logiciel métier doit présenter des données à des utilisateurs, en sauvegarder d’autres de manière organisée dans des bases de données, interagir avec des périphériques ou des systèmes externes, etc. Autant de domaines de responsabilités différents qui font des logiciels de gestion de véritables monstres qui peuvent très vite asservir leurs développeurs en esclavage s’ils ne sont pas bien maîtrisés.


Mais attention, tous ces autres domaines ne doivent pas être considérés comme faisant partie de la partie business. Ce ne sont que des composants proposant des entrées et des sorties avec le domaine principale.

Cette nuance est très importante car le chevauchement des périmètres de responsabilité amène généralement les développeurs à faire du « fixture code » qui peut rapidement rendre les modules fortement couplés entre eux.



La solution que je propose consiste donc à partir sur une architecture en oignon un peu particulière. 



Explication

Cette architecture englobe trois concepts principaux qui sont les suivants :
  •  La partie business (en bleu) qui est le centre de l’architecture. Physiquement, c’est un projet (une DLL) qui possède toutes les API permettant de manipuler du métier pur. Ici on trouve des classes factures, personnes, monnaies, des factorys permettant d’obtenir des entités purement métier.
  • La partie contrats (en jaune) est une zone dans laquelle ne se trouve AUCUN CODE. Physiquement, ce sont des projets dans lesquelles on ne trouve que des interfaces. Nous verrons plus tard que ces interfaces devront être écrites d’une manière particulière pour être en adéquation avec notre principe d’indépendance totale.
  • La partie périphérique (en vert) possède les implémentations des contrats (en jaune). Chaque périphérique est un projet spécifique qui doit implémenter tous les contrats qui le concerne. Le développeur du projet a aussi la charge de mettre en place l’environnement de test qui permettra de valider la bonne implémentation de son code.
Bien que le sujet soit très intéressant, je ne vais pas m’attarder sur cette architecture et comment la mettre en place, car je vais partir du principe que vous connaissez déjà  toutes les bonnes pratiques permettant de mettre en œuvre un système SOLID.


Je vais plutôt m’attarder sur un point qui peut paraitre surprenant : comment allons-nous faire pour passer d’un domaine à un autre sachant que nos modules sont tous séparés par des contrats ?

Si on prend par exemple l’interconnexion entre la partie Business et la partie SQL Server, toutes les deux ne sont référencées qu’a un seul projet : le « Data Access  Contract ». Elles ne se connaissent donc pas l’une et l’autre.


Cela signifie que la partie business possédera une classe « Personne » qui ressemblera très fortement à une autre classe « Personne » située dans la partie SQL. Mais quel est l’intérêt de toute cette tuyauterie ?  Comment allons-nous pouvoir manipuler les objets d’un monde à l’autre ? Cela ne fait-il pas redondance ? Je vais essayer de répondre à ces questions…


Quel est l’intérêt de toute cette tuyauterie ?


Ce système qui peut paraitre lourd au premier abord apporte énormément d’avantages :

  • Partager le travail : un groupe de développeur peut travailler sur la partie purement business tandis qu’un autre travaille sur un périphérique sans aucune interférence ni effet de bord possible. Plusieurs équipes peuvent alors travailler de façon verticale sur le même sujet mais à des niveaux différents.
  • Changement d’un comportement sans danger : Vous devez migrer votre SGBD en mode NoSQL ? Votre fournisseur Webservice passe en LDAP ? Une prochaine mise à jour va nécessiter de revoir les appels à un périphérique ? Votre IHM doit repasser en Winform ? pas de problème ! Grâce au couplage inexistant entre vos domaines de responsabilités, le fait de changer l’implémentation d’un projet périphérique n’aura aucun impact sur votre partie business (ni sur les autres périphériques d’ailleurs). Vous pourrez garder le focus sur la ré-implémentation du domaine ciblé sans vous soucier des effets de bords possibles. De quoi se permettre tous les délires.
  • Implémentations multiples : Votre logiciel doit travailler sur plusieurs types de bases de données ? switcher entre Oracle, SQL Server et un fichier Excel local ? Supporter plusieurs IHM en fonction qu’il soit exécuté sur une machine de bureau ou une tablette Surface ? Une fois encore, les contrats vont vous permettre de jouer avec plusieurs implémentations grâce à l’injection de dépendance.
  • Recherche et correction de bug : Les exceptions non gérées ainsi que les bugs de fonctionnement sont plus rapidement identifiés et corrigés du fait que chaque domaine de responsabilité possède son propre arsenal de tests spécifiques.
Comment allons-nous manipuler les objets d’un monde à l’autre ?


Pour répondre à cette question, je vais en premier lieu exposer le principe de base :


Lorsqu'un programmeur désire faire une vraie séparation entre deux domaines, celui-ci n’a pas d’autre choix que de créer des entités spécifiques à chaque domaine. 



Si ces deux domaines doivent manipuler des entités communes, la bonne pratique consiste à faire un contrat qui définit les propriétés de ces entités :




Grâce à ce contrat, nous sommes en mesure de nous assurer que lorsque les deux domaines parlent d’une personne, ils sont tous les deux d’accord sur le fait qu’il s’agit d’une entité possédant un nom et une date de naissance. Toutes les autres propriétés ne sont  que des informations propres à leur logique interne et ne concerne en aucun cas les autres domaines.


Mais comment faire pour appliquer le comportement de PersonA lorsque DomainA obtient une instance de PersonB ? C’est très simple : il suffit de faire une copie de PersonB dans une nouvelle instance de PersonA. Cette action de copier un objet vers un autre objet de type différent pour bénéficier de son comportement se nomme « la transformation ». C’est justement le sujet de cet article !


Cela ne fait-il pas redondance ?


Depuis votre tout premier programme, tout le monde vous rabâche sans cesse qu’il faut factoriser le code. C’est d’ailleurs le principe D de l’acronyme STUPID, à savoir la Duplication. Alors, faire plusieurs classes pour représenter la même entité n’est-il pas de la duplication de code ?


Un  programmeur débutant serait fortement tenté de créer un projet spécifique dans lequel se trouvent toutes les entités, puis il ferait en sorte que chacun des  modules en ayant besoin accède à ce projet.


Non seulement ce raisonnement est une mauvaise idée, mais c’est aussi une violation de plusieurs principes SOLID.


La duplication de code survient lorsque votre programme appel des jeux d’instructions différents pour appliquer des comportements équivalents. Cette mauvaise pratique force les programmeurs à passer sur chaque jeu d’instructions lorsqu’il est nécessaire d’apporter une modification au comportement voulu. Il est clair que ceci est une mauvaise pratique qu’il faut absolument bannir de vos projets.


Mais qu’en est-il de notre cas ? Pensez-vous qu’une entité « Personne » de votre logique métier a les mêmes comportements qu’une entité « Personne » dans votre couche d’accès aux données ? Assurément non !


Une « Personne » dans le métier possédera par exemple des propriétés calculées comme l’âge qu’il avait à sa date d’inscription au service, un numéro de téléphone correctement formaté ou une liste de produits qui pourraient l’intéresser, tandis qu’une « Personne » dans la couche d’accès aux données possédera quant à elle des clés étrangères, un identifiant unique, des méthodes de résolution de jointures, etc.


Nous ne sommes donc pas en présence de duplication de code, car nous devons traiter des comportements différents. Il est donc parfaitement logique pour une architecture SOLID que chaque comportement soit séparé dans des classes différentes.

Choix du type de contrats

Maintenant que tout le monde a compris ce qui motive cet article, attaquons nous au vif du sujet.

Notre but est de faire de la copie d’objets ayants des structures différentes.


Pour savoir quels membres doivent être copiés, il est nécessaire d’avoir une description des propriétés communes à chaque objet. Cette description qui peut être sous plusieurs formes se nomme un contrat.


Ceux d’entre vous qui possèdent une expérience en WCF ont surement déjà entendu parler de ce concept, car cette technologie propose une API de DataContracts permettant de faire fonctionner les mécanismes de sérialisations.


Si vous vous êtes intéressé à ces mécanismes, vous avez surement cherché des moyens plus optimisés de mener à bien cette tâche, et vous avez entendu parler d’autres systèmes de sérialisations par contrat comme protobuf.


Il existe aussi plein de méthodes permettant de faire de la copie de champs équivalents grâce à la comparaison par réflexion.


L’une ces méthodes ne pourrait-elle pas remplir notre besoin ?


En tant qu’architecte logiciel, votre rôle est de vous assurer que votre programme est facilement compréhensible, manipulable et modifiable par tous les développeurs qui seront amenés à travailler sur votre projet. Il est donc très important de veiller à mettre en place  des méthodes simples nécessitant un minimum de formation, et qui laissent peu de place à l’erreur d’implémentation.


Or, que nous proposent les outils décrits précédemment ?


Les DataContracts WCF travaillent avec des attributs. Faire un contrat consiste à tagger chaque propriété pour que le système de sérialisation soit en mesure de faire les correspondances adéquates. Une architecture basée sur ce principe obligera les développeurs soit à maintenir une documentation à jour, soit à créer un outil spécifique pour la conception des contrats. Dans les deux cas, la lourdeur de cette technique est trop assujettie à des oublis et ne permet pas de bénéficier du compilateur pour détecter d’éventuelles erreurs.


Protobuff propose plusieurs techniques. L’une d’entre elle consiste aussi à passer par des attributs. Nous ne retiendrons pas cette méthode pour les raisons évoquées précédemment. L’autre consiste à créer un fichier de description qui permettra avec un outil tiers de générer du code spécifique.


Un nouveau développeur qui arrive sur le projet devra se former à cet outil, à la syntaxe à utiliser, etc. Tout remaniement de code nécessitera une recompilation par les outils tiers ce qui génère une fois de plus du travail supplémentaire. Et bien sûr, cette technique ne permet pas de bénéficier de la détection d’erreurs lors de la compilation.


Quant aux méthodes de copies génériques utilisant la réflexion, je pense que ce n’est même pas la peine d’en parler, car comme vous l’aurez compris elles ne nécessitent pas de contrat vu qu’elles se basent sur la correspondance des noms et des types de propriétés pour effectuer leur travail. Je vous laisse imaginer les effets de bord possibles lorsque quelqu’un va renommer des propriétés ou revoir une hiérarchie de classe lors d’un remaniement de code…


Alors que nous reste-t-il ?


Il  nous reste les interfaces, qui ont le mérite d’apporter plusieurs avantages :

  • Elles sont natives au langage. Donc compréhensible par tous sans formation supplémentaire.
  • Elles peuvent être utilisées par des outils de manipulation de code (comme resharper), ce qui va permettre de générer rapidement les implémentations, faire du renommage massif, de la navigation dans le code, etc.
  • Elles génèrent des erreurs de compilation, ce qui laisse peu de doute au programmeur quant à la façon de les implémenter.
  • Elles respectent les principes de la POO


En tant qu’architecte, c’est donc visiblement le meilleur choix pour nous permettre de créer des contrats qui respectent nos prérequis.

Structurer des interfaces pour qu’elles remplissent leur rôle de contrat.

Si vous faites quelques recherches sur internet, vous remarquerez que l’utilisation d’interfaces pour aider à faire de la copie d’objets est quelque chose de très peu répandu. Mais pourquoi ? N’est-ce pas un moyen simple et naturel de créer une description de structure ?


Malheureusement, c’est bien plus compliqué qu’il n’y parait, et je vais essayer de vous en exposer les raisons.


Lorsque j’utilise une interface pour décrire une structure en vue de faire de la copie, tout se passe bien tant que je ne travaille qu’avec des types de base (string, int, DateTime, …)


Les premiers problèmes commencent à apparaître lorsque notre structure doit contenir une autre structure.

Dans ce cas, l’implémentation naïve consiste à placer directement dans notre première interface un nouveau membre qui sera du type de l’interface agrégée.


Exemple : 


public interface IPerson
{
    string FirstName { get; set; }
    string LastName { get; set; }

    IAddress Address{ get; set;}
}

public interface IAddress
{
   string Street { get; set; }
   string City { get; set; }
   string Country { get; set; }
}

Ici, notre contrat IPerson  doit contenir une adresse. Nous avons donc créé un contrat IAddress puis ajouté un membre de type IAddress dans IPerson


Cela est parfaitement logique pour tout bon programmeur C#


Passons à une première implémentation de ces contrats dans un domaine A :


public class PersonA : IPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }

    public IAddress Address { get; set; }
}
public class AddressA : IAddress
{
    public string Street { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}

Nous voyons dans l’implémentation de PersonA que nous avons un membre de type IAddress. Vous conviendrez qu’il serait préférable que ce type soit directement un AddressA pour que la manipulation de l’objet PersonA soit plus aisée.


Malheureusement, cela n’est pas possible malgré le fait que la classe AddressA implémente IAddress car une implémentation d’interface demande le type exact déclaré dans celle-ci.

Bien sûr, plusieurs contournements sont possibles comme par exemple déclarer un membre nommé Address de type AddressA, puis implémenter notre membre d’interface de façon explicite pour  pouvoir manipuler ce membre :

public class PersonA : IPerson
{
    public AddressA Address = new AddressA();

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }

    IAddress IPerson.Address
    {
        get { return Address; }
        set { 
            if( !(value is AddressA) ) 
                throw new Exception("Cannot assign the property because is does not have the matching type AddressA");
            Address = (AddressA)value;
        }
    }
}

Mais cette technique pose plusieurs problèmes. Déjà elle nécessite de taper plusieurs lignes de codes de programmation défensive, ensuite elle crée des propriétés qui font double emploi avec notre membre d’interface et enfin elle alourdie nos mécanismes qui permettront par la suite de faire de la copie.



Une autre problématique plus ou moins du même type se présente aussi lorsque notre contrat doit contenir des collections d’objets.



Comme nos contrats ne doivent pas posséder de membre fortement typé (car nous devons rester indépendant de toutes les couches adjacentes), la solution qui vient à l’esprit est d’utiliser des IEnumerable pour représenter nos collections.

public interface IPerson
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DateTime BirthDay { get; set; }

    IEnumerable Address { get; set; }
}

Mais comme pour les agrégations, cette technique apporte un lot de problèmes important, car non seulement elle nécessite de faire du remapping de propriétés en interne, mais en plus nous ne pourrons pas synchroniser nos collections du fait que nous les avons déclarées dans le contrat en «lecture seule »


Mais alors quelles est la meilleure solution à adopter ?

La bonne méthode consiste à utiliser des types génériques avec contraintes dans vos interfaces.


public interface IPerson where TAddress : IAddress
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DateTime BirthDay { get; set; }
    TAddress Address { get; set; }
}

Ainsi, lorsque vous passerez à l’implémentation, vous n’aurez plus qu’à déclarer le type voulu dans le générique, et le tour est joué !



public class PersonA : IPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }

    public AddressA Address { get; set; }
}

En ce qui concerne les collections, nous appliquerons la même technique à la différence que la contrainte doit se faire sur le type ICollection


Exemple avec une classe « Person » qui doit contenir plusieurs numéros de téléphones :


public interface IPhone
{
    string Number { get; set; }
    string Type { get; set; }
}
public interface IPerson
    where TAddress : IAddress
    where TPhone : IPhone
    where TCollectionPhone : ICollection
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DateTime BirthDay { get; set; }
    TAddress Address { get; set; }
    TCollectionPhone Phones { get; set; }
}

Ce type de contrat révèle toute sa puissance pour la transformation de données entre la couche business et la couche IHM, car la plupart du temps, la couche business a besoin de simples listes génériques tandis que les entités IHM ont besoins de collections observables afin d’être facilement liées aux vues.


Grâce à ce contrat générique, les deux mondes peuvent implémenter l’interface en déclarant d’un côté une List et de l’autre côté une ObservableCollection. Cela ne posera aucun problème pour le système de transformation, bien au contraire !


Mais comment ça se passe si nos sous-types contiennent aussi des sous-types pour faire les contrats correspondants ?


La meilleure méthode dans ce cas est de séparer notre contrat en deux interfaces (ou plusieurs).


Une première interface de base possédera tous les membres ayant un type de base, et la deuxième tous les membres possédants les types génériques. Ainsi nous pourrons appliquer des contraintes de types en se basant sur l’interface de base ce qui permettra d’éviter d’avoir à déclarer tous les génériques lors l’implémentation.


Imaginons par exemple que nous devions ajouter des coordonnées GPS à notre adresse. Les contrats deviendraient alors :


public interface IGpsLocation
{
    double Latitude { get; set; }
    double Longitude { get; set; }
}
public interface IAddress
{
    string Street { get; set; }
    string City { get; set; }
    string Country { get; set; }
}
public interface IAddress : IAddress
    where TGpsLocation : IGpsLocation
{
    TGpsLocation GpsLocation { get; set; }
}

Et au niveau de l’implémentation dans notre domaine A :


public class GpsLocationA : IGpsLocation
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}
public class AddressA : IAddress
{
    public string Street { get; set; }
    public string City { get; set; }
    public string Country { get; set; }

    public GpsLocationA GpsLocation { get; set; }
}
public class PersonA : IPerson
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }

    public AddressA Address { get; set; }
}

Ici notre classe PersonA reste très simple malgré le fait que le contrat IAdresse possède un sous membre qui logiquement doit être défini à cause du générique.


Pour résumer, si vous n’avez rien compris voici l’essentiel à retenir :
  •  Les contrats sont des interfaces
  •  Les membres qui ne sont pas des types natifs doivent être déclarés sous forme de type générique.
  •  Les membres de types collection doivent aussi être déclarés sous forme de type générique avec une contrainte sur l’interface ICollection.
  •  Les interfaces génériques qui contiennent elles-mêmes d’autres interfaces génériques peuvent être séparées en deux parties. Une interface de base qui contient tous les membres natifs et une interface générique qui hérite de l’interface de base. Ainsi les contrats peuvent garder leur simplicité en ne déclarant que l’interface de base.

Le système de transformation

Maintenant que vous connaissez l’intérêt de bien isoler vos couches avec des contrats, et comment construire ces contrats, nous allons passer à la partie intéressante : le système de transformation.


Comme je l’explique depuis le début de cet article, l’objectif est de copier des données issues d’un objet source dans un objet destination de type différent.


Malheureusement, les logiciels de gestion doivent interagir avec des bases de données, et la moindre des choses lorsque l’on travaille sur un projet conséquent qui peut contenir plusieurs centaines de tables consiste à utiliser un ORM pour pouvoir évoluer sereinement.


Or, la plupart des ORM fonctionnent de telle manière qu’ils fournissent des objets matérialisés issues d’une base de données, et pour permettre de  générer la requête correspondante aux modifications apportées à ces objets, des mécanismes de détection se basent directement sur les instances de ces objets maintenus par un contexte interne !


Notre problème ne consiste donc pas qu’à faire de la simple copie de données, mais à faire de la synchronisation de données et de grappe d’objets pour rester conforme aux ORM.


Vous commencez maintenant à comprendre pourquoi les architectes logiciels ne veulent pas se prendre la tête avec une vraie séparation des domaines J


Ainsi, l’outil de transformation que je vais vous présenter se nomme la MergeCopy, car le système permet non seulement de copier des objets qui respectent un contrat commun, mais aussi de synchroniser la destination avec les données source sans toucher à l’intégrité des instances d’objets qui les constituent.

Utilisation

La MergeCopy se présente sous la forme d’une simple méthode d’extension appelée « MergeCopy() ».


Pour pouvoir l’utiliser, ajoutez à votre contrat l’interface générique IMergeableCopy, T étant le type qui représentera l’identifiant unique de l’instance de l’objet qui sera accessible via la propriété « MergeId »


Cette propriété est un membre purement technique permettant à la MergeCopy de distinguer une instance d’objet d’une autre instance pour un même type afin de pouvoir effectuer la synchronisation.


La stratégie d’assignation de cette propriété reste à la discrétion du développeur. Mais en générale celle-ci est mappée à la clé primaire de l’objet issu du DAL.


Reprenons l’exemple expliqué dans la partie précédente.


Tout d’abords,  je rajoute l’interface IMergeableCopy à nos contrats en prenant le parti de dire que mes instances seront représentées par des Guid :

public interface IPerson : IMergeableCopy
{
    string FirstName { get; set; }
    string LastName { get; set; }
    DateTime BirthDay { get; set; }       
}

public interface IPerson : IPerson
    where TAddress : IAddress
    where TPhone : IPhone
    where TCollectionPhone : ICollection
{      
    TAddress Address { get; set; }
    TCollectionPhone Phones { get; set; }
} 

public interface IPhone : IMergeableCopy
{
    string Number { get; set; }
    string Type { get; set; }
}
public interface IAddress : IMergeableCopy
{
    string Street { get; set; }
    string City { get; set; }
    string Country { get; set; }
}

public interface IAddress : IAddress
    where TGpsLocation : IGpsLocation
{
    TGpsLocation GpsLocation { get; set; }
}

public interface IGpsLocation : IMergeableCopy
{
    double Latitude { get; set; }
    double Longitude { get; set; }
}

Maintenant que mes contrats sont parfaitement définis, je vais pouvoir passer à l’implémentation dans deux domaines à responsabilités différentes :
  • BusinessDomain aura la responsabilité de fournir des Api pour créer et manipuler des personnes
  • StorageDomain aura quant à lui la responsabilité de sérialiser et desérialiser ces objets dans un fichier texte possédant un format spécifique.
Je rappelle que physiquement, BusinessDomain et StorageDomain sont deux DLL bien distinctes qui référencent toutes les deux nos contrats qui eux-mêmes sont dans une troisième DLL.




Implémentation dans la partie Business :


En premier lieu, je vais créer une classe de base pour centraliser la gestion de notre MergeId :

public abstract class Business : IMergeableCopy
{
    protected Business()
    {
        MergeId = Guid.NewGuid();
    }
    public Guid MergeId { get; set; }
}

J’implémente ensuite les contrats qui ne nécessitent pas de types génériques :


public class BusinessPhone : Business, IPhone
{
    public string Number { get; set; }
    public string Type { get; set; }
}

public class BusinessGps : Business, IGpsLocation
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }
}

Une fois ces classes implémentées, je peux passer à celles qui les utilisent :


public class BusinessAddress : Business, IAddress
{
    public string Street { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public BusinessGps GpsLocation { get; set; }
}
public class BusinessPerson : Business, IPerson>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDay { get; set; }
    public BusinessAddress Address { get; set; }
    public List Phones { get; set; }
}

Très bien, apportons maintenant quelques comportements spécifiques à nos objets business

public class BusinessPerson : Business, IPerson>
{
    private string _lastName;
    public string FirstName { get; set; }
    public string LastName
    {
        get { return _lastName; }
        set
        {
            if( string.IsNullOrWhiteSpace(value) )
                throw new Exception("The lastname cannot be empty");
            _lastName = value.ToUpper();
        }
    }
    public DateTime BirthDay { get; set; }
    public BusinessAddress Address { get; set; }
    public List Phones { get; set; }

    public int Age { get { return DateTime.Now.Year - BirthDay.Year; } }
}

La propriété « LastName » ne peut pas être vide et est convertie en majuscule lorsqu’elle est définie. Aussi une nouvelle propriété « Age » a été ajoutée pour calculer l’âge de la personne en fonction de sa date de naissance.


Implémentons maintenant la partie Storage

public class StoragePerson : Storage, IPerson>
{        
    public string FirstName { get; set; }
    public string LastName{ get; set; }       
    public DateTime BirthDay { get; set; }
    public StorageAddress Address { get; set; }
    public List Phones { get; set; }

    public override string ToString()
    {
        var builder = new StringBuilder(FirstName + "^" + LastName + "^" + BirthDay + "^" + (Address != null ? Address.ToString() : ""));
        foreach (var storagePhone in Phones)
            builder.Append("|" + storagePhone);            
        return builder.ToString();
    }
}

public class StorageAddress : Storage, IAddress
{
    public string Street { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public StorageGps GpsLocation { get; set; }

    public override string ToString()
    {
        return Street + "^" + City + "^" + Country + "^" + (GpsLocation != null ? GpsLocation.ToString() : "");
    }
}

public class StoragePhone : Storage, IPhone
{
    public string Number { get; set; }
    public string Type { get; set; }
    public override string ToString()
    {
        return Number + "^" + Type;
    }
}

public class StorageGps : Storage, IGpsLocation
{
    public double Latitude { get; set; }
    public double Longitude { get; set; }

    public override string ToString()
    {
        return Latitude + "^" + Longitude;
    }
}

public abstract class Storage : IMergeableCopy
{
    protected Storage()
    {
        MergeId = Guid.NewGuid();
    }
    public Guid MergeId { get; set; }
}

Comme vous pouvez le constater, ici notre but est de générer une ligne par personne qui aura le format suivant : FirstName^Lastname^BirthDay^Street^City^Country^Latitude^Longitude|Number^Type|Number^Type|…


Mais une question se pose : comment la partie business va pouvoir envoyer l’ordre à la partie storage de procéder à l’enregistrement de son objet ?


Effectivement, cet article étant porté sur la copie de données, je me suis principalement axé sur la problématique de DTO (Data Transfert Object) qui se pose entre nos modules. La question de savoir comment envoyer les ordres entre les modules est plus une question d’architecture générale sur laquelle vous pourrez trouver d’excellentes réponses grâce à l’utilisation des « Services » et des « Repository »


Dans notre cas, la DLL qui contient les contrats possède aussi les interfaces qui définissent comment doivent être implémenté les repository. La différence fondamentale, c’est que seule la partie Storage implémentera le repository. La partie business pourra y accéder grâce à un mécanisme d’injection de dépendance, ce qui n’est pas du tout le sujet de cet article.

Pour notre exemple, voici à quoi ressemble l’interface de notre Repository :


public interface IStorageRepository
{
    void SavePerson(string filePath, IPerson person);
}

L’implémentation que nous en ferons dans la partie Storage sera celle-ci :

public class StorageRepository : IStorageRepository
{
    public void SavePerson(string filePath, IPerson person)
    {
        var personToSave = new StoragePerson();
        personToSave.MergeCopy(person);
        File.AppendText("\r\n" + personToSave);
    }
}

Ici, nous voyons opérer la magie de la transformation !


Quelques explications s’imposent :


Le repository a pour fonction d’enregistrer notre personne dans un fichier texte, mais comme vous pouvez le voir, l’argument est un IPerson, ce qui signifie que l’objet qui sera passé n’aura pas le comportement qui permet au storage de formater la chaine comme expliqué précédemment.


Nous devons donc transformer cette IPerson en un StoragePerson, c’est donc le rôle des deux lignes de code qui sont  :


var personToSave = new StoragePerson();
personToSave.MergeCopy(person);

Une fois que nous sommes en possession d’une StoragePerson, il devient alors facile de l’enregistrer dans le fichier demandé.


Pour obtenir l’effet inverse, c’est-à-dire de disposer d’une BusinessPerson à partir d’un StoragePerson désérialisé, il suffit de faire la même chose dans le service utilisé par la partie Business.


Par exemple si je fais évoluer mon Repository de la façon suivante :

public interface IStorageRepository
{
    IPerson Load(Guid personId);
    void SavePerson(string filePath, IPerson person);
}

En imaginant que la partie Storage implémente le Load, je vais créer dans la partie Business un service qui permettra de manipuler le Repository :



public interface IBusinessService
{
    BusinessPerson GetPerson(Guid id);
    void SavePerson(BusinessPerson person);
}

public class BusinessService : IBusinessService
{
    private readonly IStorageRepository _storageRepository;

    public BusinessService(IStorageRepository storageRepository)
    {
        if (storageRepository == null) throw new ArgumentNullException("storageRepository");
        _storageRepository = storageRepository;
    }

    public BusinessPerson GetPerson(Guid id)
    {
        var storageObject = _storageRepository.Load(id);
        BusinessPerson result = new BusinessPerson();
        result.MergeCopy(storageObject);
        return result;
    }

    public void SavePerson(BusinessPerson person)
    {
        _storageRepository.SavePerson("d:\\database.txt",person);
    }
}

Comme vous le voyez dans la méthode GetPerson(), nous récupérons un enregistrement issu du storage, puis nous le transformons en BusinessPerson. Avec à cette stratégie, notre service est en capacité de ne traiter plus que des BusinessObject.



Cet exemple nous a permis de mettre en place l’architecture suivante :



Grâce au découplage total que cela apporte, nous pouvons imaginer plusieurs sortes de substitution, comme par exemple :



Remplacer la partie storage par un objet simulacre afin de pouvoir tester la partie business



Ajouter en parallèle un deuxième repository qui permettrait d’enregistrer nos objets dans une base de données plutôt que dans un fichier texte





Ou tout simplement tester certaines logiques internes de la partie business en remplaçant le service par un objet simulacre.



Une telle malléabilité ouvre un grand champ d’éventualités pour les architectes. Pour peu que vous vous intéressiez au Behavior Driven Developpement, sachez que cela vous donne la possibilité de faire des applications business avec des scénarios de tests qui viendront alimenter certains enregistrements dans une base de données complétement virtuelle. De quoi garantir la robustesse et la non régression de votre logiciel à tout instant !


Mais cela est encore un nouveau sujet…


Pour revenir à nos moutons, voici quelques dernières choses à savoir sur la MergeCopy :
  • La bonne gestion du MergeId ne permet pas seulement de régler les cas de fusions d’instances, mais permet aussi de régler les problèmes de type enfant qui pointe vers son parent. Donc pas d’inquiétude en ce qui concerne les boucles infinies qui peuvent se produire dans d’autres systèmes de copie.
  •  En tant que système central de votre architecture, La MergeCopy possède plusieurs systèmes d’optimisation pour que la copie ne ralentisse pas trop votre application. Toutefois des objets trop gros ou trop complexes qui sont très souvent transformés peuvent avoir un impacte sur les performances. Soyez donc vigilent sur ce point.
Une dernière chose dont nous n’avons pas parlé concerne l’ordre de copie des propriétés. Car il arrive parfois qu’une propriété s’appuie sur une autre propriété pour faire des contrôles d’intégrités, par exemple :

public class BusinessAddress : Business, IAddress
{
    private string _city;
    private string _country;
    private BusinessGps _gpsLocation;

    public string Street { get; set; }
    public string City
    {
        get { return _city; }
        set
        {
            if(string.IsNullOrWhiteSpace(Street))
                throw new Exception("Define street before the city");
            _city = value;
        }
    }

    public string Country
    {
        get { return _country; }
        set
        {
            if (string.IsNullOrWhiteSpace(City))
                throw new Exception("Define city before the country");
            _country = value;
        }
    }

    public BusinessGps GpsLocation
    {
        get { return _gpsLocation; }
        set
        {
            if( string.IsNullOrWhiteSpace(Street) || string.IsNullOrWhiteSpace(Country) || string.IsNullOrWhiteSpace(City) )
                throw new Exception("define all the properties before the gps location");
            _gpsLocation = value;
        }
    }
}

Ici la règle métier veut que la rue soit renseignée avant la ville, que la ville soit renseignée avant le pays et que la localisation GPS ne soit renseignée que lorsque toutes les données sont préalablement définies.


Si la MergeCopy ne procède pas dans le bon ordre, celle-ci va échouer inévitablement.


Pour pallier ce problème, la MergeCopy propose un attribut nommé [MergeCopy] à placer sur les propriétés de vos classes.


Cet attribut peut être exploité de plusieurs manières :


En utilisant la propriété Order

Avec cette propriété, vous pouvez définir l’ordre exact dans lequel la copie doit s’effectuer. Plusieurs propriétés peuvent avoir le même « order ». Exemple :

public class BusinessAddress : Business, IAddress
{
    private string _city;
    private string _country;
    private BusinessGps _gpsLocation;

    [MergeCopy(Order = 1)]
    public string Street { get; set; }
    [MergeCopy(Order = 2)]
    public string City
    {
        get { return _city; }
        set
        {
            if(string.IsNullOrWhiteSpace(Street))
                throw new Exception("Define street before the city");
            _city = value;
        }
    }
    [MergeCopy(Order = 3)]
    public string Country
    {
        get { return _country; }
        set
        {
            if (string.IsNullOrWhiteSpace(City))
                throw new Exception("Define city before the country");
            _country = value;
        }
    }
    [MergeCopy(Order = 4)]
    public BusinessGps GpsLocation
    {
        get { return _gpsLocation; }
        set
        {
            if( string.IsNullOrWhiteSpace(Street) || string.IsNullOrWhiteSpace(Country) || string.IsNullOrWhiteSpace(City) )
                throw new Exception("define all the properties before the gps location");
            _gpsLocation = value;
        }
    }
}

En utilisant la propriété StackBottom 

Il arrive dans certains cas que l’on désire qu’une ou plusieurs propriétés soient copiées en premier ou en dernier quoi qu’il arrive.



La MergeCopy considère l’ensemble des propriétés d’un objet à copier comme les éléments d’une pile. En jouant avec StackBottom et order, vous pouvez dire si la propriété à copier se trouve sur le haut ou sur le bas de la pile.


Schéma :



Voilà, je pense que vous savez tout.

Conclusion

La méthode que je vous ai présentée ici a été mise en œuvre sur un logiciel de gestion dans le domaine de la santé que  plusieurs personnes sont amenées à faire évoluer en ajoutant de nouveaux modules au fil de l’eau.


Grâce au découplage total qu’apporte la transformation, des équipes différentes travaillent sur plusieurs aspects du logiciel qui s’avèrent être les composants d’une seule et même fonctionnalité métier. Chaque élément est ainsi testé indépendamment, et un système d’injection de dépendance assemble tous les morceaux pour la plus grande joie des utilisateurs.


Le système de contrat agit comme une sorte de spécification entre les développeurs, ce qui leur permet de se mettre d’accord sur les API qui doivent être fournies. Toute modification du contrat est ainsi détectée au moment de la compilation amenant plus de communication entre les développeurs si des évolutions sont apportées aux API.


Mais la MergeCopy seule ne fait pas tout. Pour en tirer pleinement parti vous devrez maitriser tous les patterns permettant de mettre en œuvre une bonne architecture SOLID.



Le sujet est vaste et plein d’embuche, mais ce n’est qu’à ce prix que vous pourrez mettre sur le marché des logiciels pour lesquelles les  utilisateurs apprécieront chaque mise à jour que vous leur fournirez.



Hazymail.NET, la résurrection du programme perdu.
18 août 2014
Il y a quelques semaines, j'ai dû mettre en place un script qui doit envoyer un email à la fin de son traitement. Rien de plus normal me direz-vous ?

Le truc fun de l'histoire c'est qu'en cherchant sur le net, je suis tombé sur un très vieux soft que j'avais écrit en C++ en l'an de grâce 2006 nommé HazyMail.

Cet outil permettait d'envoyer des mails sans avoir besoin d'un serveur SMTP, ce qui est quand même bien pratique.

Je me suis donc empressé de l'utiliser, et j'ai pu constater quelques disfonctionnements, notamment sur la partie pièce jointe. Je me suis alors dis qu'il serait pas mal de lui donner un petit coup de jeune en le redéveloppant en .NET 2.0 histoire que celui-ci soit Cross plateforme !

Je vous invite donc à vous connecter sur http://hazymail.boudoux.fr pour découvrir ce joujou qui risque d'évoluer au rythme de mes congés et de mes temps libres.

N'hésitez pas à m'envoyer vos feedback !
Changement de nom de domaine pour MyTCPMON.
19 mai 2014
Le nom de domaine pour MyTCPMON est arrivé à expiration, et malheureusement je suis arrivé trop tard pour le renouveler. 

Comme personne ne l’a commandé entre temps, tout aurait pu aller pour le mieux, mais mon hébergeur semble me prendre pour un pigeon en demandant pas moins de 10 euros pour des « frais administratif de renouvellement tardif » !






Comme je trouve cela profondément scandaleux, j’ai décidé de mutualiser en mettant le site à disposition sur http://mytcpmon.boudoux.fr


Qu’est-ce que MyTCPMON ?

C’est un outil que j’ai développé permettant de voir et d’agir sur le flux réseau qui transite entre deux entités.


Mon équipe et moi l’utilisons régulièrement pour simuler des réseaux lents, et comprendre les échanges qui s’opèrent entre nos Framework SQL et le serveur afin d’optimiser au maximum le trafic.

C’est un outil indispensable pour tous les développeurs soucieux d’optimiser les échanges réseaux de leurs logiciels.

Va-t-il y avoir des évolutions ?

Pour la petite histoire, MyTCPMON est un outil que j’ai développé pour le fun. J’étais sur le point de sortir la release 1.3 lorsque je me suis fait voler mon ordinateur dans un bar. Bien sûr, aucune sauvegarde ne m’a permis de remettre la main sur le code source, ce qui implique qu’aucune évolution n’est prévue au programme.


Mais tout n’est pas perdu, car MyTCPMON a été écrit en .NET, et beaucoup d’outils permettent aujourd’hui de reconstituer un projet à partir d’une version compilée. Cela demande un peu de travail, mais il n’est pas exclu qu’à un jour je me remette sur le projet, dans quel cas mon premier réflexe sera bien sûr de passer le projet en OPEN SOURCE.

Mes anciens articles de HACK
29 juin 2013
Dans les années 2000, alors que je n'avais qu'une vingtaine d'années, j'étais passionné par le hacking.

Si cette discipline m'a obligé à posséder de fortes compétences en réseaux, il est arrivé un moment où ma seule façon de progresser fût d'apprendre les arcanes de l'informatique, comprendre comment fonctionne précisément les choses pour entrer dans le cercle très fermé des "EL8" (comprendre élite).

Il faut savoir qu'en hacking, les personnes les plus respectées ne sont pas celles qui ont pénétré les plus gros serveurs, mis à genoux les plus gros systèmes ou volé les documents les mieux gardés de la planète, mais se sont celles qui trouvent et mettent en oeuvre les procédés et programmes qui permettent ces exploits.

Être un hacker consiste donc à étudier précisément un système afin de pouvoir détourner son utilisation originale pour le faire fonctionner à son avantage.

C'est dans ce sens que jadis j'avais écrit plusieurs articles techniques que je vais vous présenter ici.

Introduction

Lorsque l'on évolue dans le monde du hack, nul besoin de vous préciser que l'on ne signe jamais ses écrits avec son vrai nom, la bonne pratique consiste à utiliser un pseudonyme au cas où nos procédés violeraient les lois du copyright. Comme mes articles ciblent des systèmes qui sont aujourd'hui obsolètes, je pense qu'il n'y a plus de danger. Seule subsiste la manière et l'esprit qui permettrons à chacun d'apprécier la logique à avoir pour évoluer dans ce monde passionnément obscure.

C'est pour cela que tous les "papers" que je vais vous présenter serons signés "ThreaT" qui était mon pseudonyme. C'était l'époque du folklore, et vous verrez dans mes articles que certains pseudonymes étaient encore plus ridicules que le mien :)

Les articles

Attention : les liens ci-dessous vont vous mener vers des documents comportant énormément de fautes d'orthographe. Si j'essaye aujourd'hui de faire des efforts, ce n'était pas le cas à l'époque. Merci de votre compréhension. 

WSPing PRO Pack Cracking tutorial : Ce premier article explique la façon dont j'ai procédé pour déverrouiller la protection d'un logiciel  permettant de faire des tests réseaux. Je devais avoir 18 ou 19 ans lorsque je l'ai écrit. C'était l'époque où je commençais à maîtriser l'ASM et où je me passionnais pour l'ingénierie inverse (reverse engineering) dont le but est de désassembler un produit pour en comprendre les concepts de fabrication. Cet article est très technique mais on peut y voir comment s'y prennent les crackers pour casser les protections de vos logiciels préférés.

Virus Linux Shell Script Infection : Dans cet article il est question d'un des premiers virus pour Linux dont l'idée m'est venue par hasard. D'après la date de l'article je venais tout juste d'avoir 20 ans et le monde de la micro commençait à intéresser le grand public. Cet article a eu beaucoup de succès à l'époque de sa sortie, et pas mal de retour m'ont été faits sur les forums ou les canaux IRC.

Shell Script Infection part II : Après les nombreux retours sur le premier article traitant du virus Linux, j'ai écrit un deuxième article pour améliorer et optimiser le concept. Celui-ci a eu encore plus de succès que le premier et fût cité en référence dans pas mal d'autre "papers" de la communauté. Je me souviens d'ailleurs qu'au cours d'une conversation IRC avec un autre passionné, celui-ci m'avait expliqué que cet article était vraiment trop connu et qu'il était blasé de le voir cité à tous les coins de rue. Je m'en souviens car je ne savais pas vraiment comment prendre sa remarque...

Remote NT Shell Obtention : Ici arrive l'un de mes premiers articles permettant d'accéder au Graal : le contrôle à distance d'une machine à travers un "shell" (interpréteur de commande). Aujourd'hui avec la panoplie d'outils de contrôle à distance comme teamviewer, l'accès bureau à distance, VNC, ... cela peut paraître dérisoire, mais à l'époque où les serveurs étaient des machines sous Windows NT 4 avec 32Mo de RAM, ou les connexions internet étaient du RNIS  (hein ? quoi ?) ne dépassant pas les quelques Ko/s de débit, et où à peine 15% de la population française devait être connectée à internet, obtenir un moyen de contrôler une machine à distance relevait du défi technique.

Explorer.exe Buffer Overflow : Cet article est grosso-modo ma première tentative d'exploitation d'un buffer overflow. Pour ceux qui ne connaissent pas cette technique, sachez que les hackers usent de plusieurs procédés pour pénétrer les systèmes, et l'un d'entre eux consiste à exploiter une vulnérabilité d'un programme informatique appelée le dépassement de tampon. Le concept est simple : un ordinateur exécute une suite d'instructions comme un cuisinier suit une recette de cuisine. La technique consiste à placer en plein milieu de la recette un "continuez en suivant les instructions de la page x" (page soigneusement insérée par une technique appelée l'injection de shellcode). Lorsque le cuisinier arrive sur la page x, les instructions demandent à ouvrir la porte pour laisser entrer les voleurs, chose que le cuisinier fait sans poser de question pensant que cela fait partie de la recette de cuisine.

ThreaT Backdoor : Après avoir goûté au contrôle à distance (cf : Remote NT Shell Obtention), je me suis mis à créer une véritable backdoor permettant d'avoir un accès total par ligne de commande à une station distante. A cette époque je vivais sur Paris où la scène underground était très active. J'ai présenté ce projet à  un meeting de hack privé organisé dans un appartement regroupant pas mal de tête du milieu. C'est d’ailleurs au cours de cette présentation que j'ai rencontré des gars talentueux avec qui j'ai partagé plusieurs nuits blanches à coder, reverser et m’intéresser au kernel NT (Le noyaux du système Windows)

Make It Overrun : Dans la lignée des failles type buffer overflow, cet article présente l'exploitation d'un programme fourni par Microsoft écrit en langage WIL. Si celui ne présente pas grand intérêt sur le plan de sa possibilité d'exploitation, la technique reste quant à elle très intéressante pour présenter un cas concret aux novices passionnés.

Nethide : A partir d'ici, on commence à parler de choses sérieuses : les rootkits.
Dans cet article, j'explique de manière didactique comment modifier le programme netstat.exe (outil en ligne de commande permettant de lister toutes les connexions actives sur un ordinateur) dans le but de modifier son comportement afin que certaines connexions soient cachées à l'administrateur système. L'article demande un bon niveau en programmation bas niveau, en débogage système et quelques notions réseaux fondamentales. Je devais avoir 22 ans lorsque je l'ai écrit et je crois que c'est à partir de ce moment-là que mon niveau en informatique a commencé à être acceptable.

Cracking the WFP : Après Nethide, cet article monte le niveau d'un cran en s'attaquant à la WFP (Windows File Protection) qui est un procédé permettant à Windows de protéger vos fichiers systèmes sensibles. Mis au point en collaboration avec Crazylord, un hacker rencontré lors de la gamma OH (le meeting où j'ai présenté ThreaT Backdoor) nous expliquons comment détourner la protection pour injecter un rootkit maison du processus TASKMGR. Truc normal quoi...

Hardcode 1 : Pour cet article, j'avais pour projet d'écrire un e-zine (magazine électronique) dans lequel il serait question de patcher tout et n'importe quoi. Ce premier numéro présente une évolution de la commande "NET SHARE" pour lui donner la possibilité de faire un partage sur une machine distante. Malheureusement, je n'ai jamais continué ce projet qui aurait pu être très fun. L'article reste cependant très intéressant pour les passionnés de programmation bas niveau.

Regedit buffer overflow : Vous connaissez Regedit ? C'est l'outil Microsoft permettant d'éditer la base de registre. Figurez-vous que j'avais trouvé une faille dans cet outil présent sur tous les ordinateurs équipés du système Microsoft Windows® toute version qui permettait d’exécuter du code à l'insu d'un utilisateur. A la suite de cet article, j'ai publié une autre faille sur Regedit qui elle permettait d’exécuter du code en local ou à distance ! Le code source de l'exploit (le programme permettant d'exploiter la faille) a été diffusé sur NT bug track ou plus de 200 000 personnes l'on consultés. Encore plus fort, à l'époque j'avais proposé un patch de Regedit pour corriger la vulnérabilité, et celui-ci était disponible AVANT celui de Microsoft. Un pur délire encore consultable aujourd'hui sur des sites de sécurité tel que SecurityFocus, qui m'a même valu plusieurs publications dans la presse comme sur "Réseau-Telecom.net"

Real Win32 Generic Shellcode : Mon dernier article signé ThreaT, peut être l'apothéose d'une vie de hacker : Comment créer un Shellcode générique pour Windows. Le sujet est très technique, mais les possibilités sont immenses. Vous vous rappelez de l'histoire de la recette de cuisine dans laquelle on insère une page d'instruction ? (voir le résumé d'Explorer.exe Buffer Overflow). Le shellcode est cette page d'instruction, c'est donc l’élément crucial de l'attaque par détournement de pointeur (buffer overflow, format string, shatter attack, ...). Le problème c'est que chaque livre de cuisine à son propre format, nous devons à chaque fois créer une page d'instruction "spécifique" ce qui est très compliqué. Dans cet article j'explique comment créer une page "générique" afin de pouvoir l’insérer dans n'importe quel livre de cuisine. Je vous laisse imaginer la suite...

Sur ce...

L’écriture de ce billet a fait remonter pas mal de souvenir. Pour rester dans l’ambiance du hacker rebelle, je terminerai donc sur ce paragraphe nostalgique tiré du fameux "Hacker Manifesto" qui a bercé toute ma crise d’adolescence :

C'est notre monde maintenant... Le monde de l'électron et de l'interrupteur, la beauté du baud. Nous utilisons un service déjà existant, Sans payer ce qui pourrait être bon marché si ce n'était pas la propriété de gloutons profiteurs, et vous nous appelez criminels. Nous explorons... et vous nous appelez criminels. Nous recherchons la connaissance... et vous nous appelez criminels. Nous existons sans couleur de peau, sans nationalité, sans dogme religieux... et vous nous appelez criminels. Vous construisez des bombes atomiques, vous financez les guerres, Vous ne punissez pas les patrons de la mafia aux riches avocats, Vous assassinez et trichez, vous manipulez et nous mentez en essayant de nous faire croire que c'est pour notre propre bien être, et nous sommes encore des criminels.
Oui, je suis un criminel. Mon crime est celui de la curiosité. Mon crime est celui de juger les gens par ce qu'ils pensent et dise, pas selon leur apparence. Mon crime est de vous surpasser, quelque chose que vous ne me pardonnerez jamais.
Je suis un hacker, et ceci est mon manifeste.Vous pouvez arrêter cet individu, mais vous ne pouvez pas tous nous arrêter... Après tout, nous sommes tous les mêmes.

SortedSplitList - Un algorithme d'indexation d'objet en C#
06 juin 2013
En informatique, lorsque la spécification demande à traiter un très grand volume de données avec des performances proches du temps réel, le programmeur doit passer par des mécanismes d'indexation pour trier de manière pertinente les informations à manipuler. Or, si pour la plupart des logiciels, un grand nombre de données se compte en centaines de milliers d'objets, il y en a pour lesquels il est nécessaire d'en manipuler plusieurs dizaines de millions, nécessitant énormément de ressources mémoire.

En général ces gros logiciels sont installés sur des stations dédiées dimensionnées spécifiquement pour tenir la charge, c'est pourquoi les algorithmes privilégies les performances plutôt que l'optimisation de consommation mémoire. Sauf que lorsque un serveur nécessite de maintenir plusieurs dizaines d’indexes sur plusieurs dizaines de millions d'objets, la mémoire vive peut se retrouver rapidement saturée engendrant un écroulement des performances globales.

Cet article va traiter d'un algorithme d'indexation que j'ai mis au point pour pallier à ce problème en alliant performance et consommation mémoire lorsque l'on doit travailler avec plusieurs millions d'objets.

La bonne compréhension de cet article nécessite un bon niveau en programmation générale avec des connaissances en langage C#.

Get the English version of this article on CodeProject

Introduction

Il y a quelques années, j'ai développé le système de stockage d'une technologie SGBD NoSQL.
Le serveur devait enregistrer les données sous la forme clé/valeur sur le disque, et être capable de gérer divers aspects comme le système transactionnel, la réplication, le verrouillage, la réparation des segments corrompus, le compactage ainsi que des compteurs sur le nombre de données présentes dans les bases, la place prise, la place perdue, ...

Pour arriver à bout de toutes ces fonctionnalités, les données devaient être enregistrées avec un entête de bloc, et l'ensemble de ces blocs devaient être rangés dans plusieurs indexes à multiples critères.

Au départ, j'ai utilisé les outils classiques offerts par le .NET framework : SortedList, Dictionary, SortedDictionary, mais très vite, des dysfonctionnements se sont révélés lors des tests de charges.

Il a donc été nécessaire de créer ma propre liste pour permettre de réaliser un serveur qui puisse gérer un très grand nombre de données, rapide, peu gourmand en mémoire et capable d'indexer des données en triant les ensembles selon plusieurs clés.

Etat de l'art

Avant de commencer à présenter ma solution, je vais déjà présenter le problème.

Pour cela, nous allons observer le comportement des différents algorithmes offert par le .NET framework en essayant de leurs injecter 10 millions d'objets avec des données aléatoires. 
Au cours de ces tests, je vais recueillir la quantité de mémoire utilisée ainsi que le temps passé pour chacun des outils testés.

Voici l'objet qui sera utilisé pour remplir nos indexes :

public class DataObject
{
    private static readonly Random Random = new Random();

    private readonly Guid _id = Guid.NewGuid();
    private readonly DateTime _date = new DateTime(Random.Next(1980, 2020),Random.Next(1,12), Random.Next(1,27));
    private readonly int _number = Random.Next();

    public  Guid ID
    {
        get { return _id; }
    }
    public DateTime Date
    {
        get { return _date; }
    }
    public int Number
    {
        get { return _number; }
    }
}

Une factory permettra de générer autant d'instance que voulu pour notre test :

public static class DataFactory
{
    public static List GetDataObjects(int number)
    {
        var result = new List();
        for(int i = 0; i < number; i++)
            result.Add(new DataObject());
        return result;
    }
}

Vous remarquerez que la factory renvoie directement une liste au lieu d'une énumération. Ceci est fait exprès pour que les calculs de temps ne soient pas faussés par le temps de génération de chaque objet.

Nous mettrons en place aussi un compteur de performance pour collecter les données sur le temps et la quantité de mémoire utilisée par l'algorithme :

public class PerfCounter : IDisposable
{
    private readonly Stopwatch _stopwatch = new Stopwatch();
    private readonly long _currentMemoryUsage = Process.GetCurrentProcess().WorkingSet64;
    private readonly string _filePath;

    public PerfCounter(string filePath)
    {
        if( string.IsNullOrEmpty(filePath) )
            throw new FileNotFoundException();

        string directoryPath = Path.GetDirectoryName(filePath);
        if( string.IsNullOrEmpty(directoryPath) )
            throw new FileNotFoundException();

        Directory.CreateDirectory(directoryPath);
        if (!File.Exists(filePath))
            File.AppendAllText(filePath, "Memory\tTime\r\n");

        _filePath = filePath;
        _stopwatch.Start();            
    }

    public void Dispose()
    {
        _stopwatch.Stop();
        GC.Collect();
        long memory = Process.GetCurrentProcess().WorkingSet64 - _currentMemoryUsage;     
        File.AppendAllText(_filePath, _stopwatch.Elapsed.ToString() + "\t" + memory + "\r\n");
    }
}

Maintenant que nous avons tous ce qu'il faut pour commencer notre analyse, enchaînons sur le premier outil disponible. 

SortedList

Une SortedList représente une collection de paires clé/valeur triées par la clés qui est accessible par clé et par index.

Nous allons injecter 1 million d'objets aléatoires selon la méthode suivante :

private static void TestSortedList()
{
    var sortedList = new SortedList();

    for (int i = 0; i < 10; i++) {
        List datas = DataFactory.GetDataObjects(100000);
        using (var counter = new PerfCounter("c:\\Indexation\\SortedList.txt")) {
            foreach (DataObject dataObject in datas) {
                sortedList.Add(dataObject.ID, dataObject);
            }
        }
    }
}

voici le résultat de ce petit programme :



Temps total d'insertion pour 1 million d’éléments : 785 secondes
Mémoire total pour indexer 1 million d’éléments : 54 Mo

Comme vous pouvez le constater, l'insertion dans une SortedList est extrêmement lente car il nous a fallu 13 minutes pour insérer 1 million d'objets. Je rappelle que notre objectif à l'origine est de pouvoir gérer des dizaines de millions d'objets, il est donc inenvisageable d'utiliser cette méthode.

Pourquoi la SortedList est elle si lente ?

Pour une raison très simple : une liste est un tableau d'éléments rangés de façon contigus.






Lorsque l'on ajoute un élément à la fin de la liste, il suffit de l'empiler comme ceci :



Cette opération n'apporte aucun sur coût à l'algorithme. c'est d'ailleurs pour ça que les tests de performances sur des données non aléatoires peuvent faire croire que l'insertion dans une liste est extrêmement rapide du fait que le système ne fait qu'ajouter les données dans la liste.

Par contre, lorsque l'on veut insérer un élément au milieu de la liste (c'est le cas d'une liste triée), nous n'avons pas d'autre choix que de décaler tous le bloc d’élément supérieur pour faire une place à la nouvelle donnée, puis de recoller cette suite à la fin de la liste. 


Évidemment, cette opération est fortement coûteuse en terme de temps car plus il y a d’éléments dans la liste, et plus l'opération de copier/coller sera longue, surtout lorsque il faudra insérer des éléments au début de la liste.

Ici, l'équation pour la courbe de tendance sur le temps d'insertion est  

Ainsi, importer 10 millions d’éléments aléatoires dans une SortedList prend théoriquement 36 heures !

Dictionary

Un dictionnaire C# est une collection d'objets génériques de type Clé/Valeur qui se base sur l'algorithme HashTable.

Comme précédemment, nous allons obtenir le temps d’exécution et la quantité de mémoire utilisée par l'algorithme pour 10 millions d'objets.

Voici le résultat :



Temps total d'insertion pour 10 millions d’éléments : 2 secondes
Mémoire total pour indexer 10 millions d’éléments : 638 Mo

Le temps d'insertion est beaucoup mieux, mais la mémoire utilisée est trop importante.

Imaginez si nous devions créer 5 indexes pour des objets pesant 20 octets chacun, l'ensemble des éléments pèserait 200 Mo tandis que l'algorithme prendrait 3 Go et 190 Mo en RAM !

Pourquoi la hashtable prend tant de place ?

Lorsque l'on ajoute un élément dans une hashtable, une clé est utilisée pour calculer un numéro relativement unique (appelé hashcode). Je dis "relativement" car il se peut que deux clés différentes est le même hashcode, ce phénomène est appelé une collision.

Pour palier à ce problème, une hashtable possède en interne une table à deux dimensions (appelé buckets) qui contient des entrées sous formes de structures à plusieurs paramètres. ce sont ces entrées qui prennent beaucoup de place.
En général une entrée est composée du hashcode, de la clé, de la valeur et d'un pointeur sur l’élément suivant , car les buckets sont en fait des tableaux de listes chaînées.




L’intérêt de toute cette structure est de pouvoir ranger intelligemment les données indexées par le hashcode 

En gros, le hashcode est soumis à un modulo sur la longueur du bucket ce qui donne l'index ou son entrée est stockée. Une fois cet index connu, une boucle parcours l'ensemble des entrées correspondantes pour comparer toutes les clés et leurs hashcodes avec l'élément à insérer.
Si une clé identique est trouvée, le système renvoi une erreur.
Si un hashcode identique est trouvé alors que la clé est différente, une nouvelle entrée est est créée dans la liste chaînée.

Si vous regardez dans le graphique, vous pouvez observez qu'a partir de 6 millions d'objets, la mémoire fait un bon exceptionnel de 100%. Je suppose que ceci est dût à une pré-allocation au niveau du bucket suite à un nombre important de collision.

Ainsi, plus il y a d'objet à indexer, plus la probabilité de collision est importante, ce qui force la hashtable à allouer beaucoup de mémoire pour maintenir son niveau de performance. Cette technique est donc à proscrire lorsque l'on veut traiter un très grand nombre d'objets !

SortedDictionary & SortedDictionary2

D'après le MSDN, la classe SortedDictionnay a été conçue pour palier à la problématique de vitesse d'insertion d'une SortedList. Voyons ce que cela donne avec notre système de test :  



Temps total d'insertion pour 10 millions d’éléments : 5 secondes
Mémoire total pour indexer 10 millions d’éléments : 612 Mo




Temps total d'insertion pour 10 millions d’éléments : 5 secondes
Mémoire total pour indexer 10 millions d’éléments : 649 Mo

A la vue des résultats, vous allez vous demander pourquoi utiliser un SortedDictionnary plutôt qu'un Dictionnary simple ? effectivement à consommation de mémoire équivalente, le Dictionnary est deux fois plus rapide !

Nous arrivons ici à une deuxième dimension de notre problème, l'indexation à plusieurs profondeurs.

Tous nos tests sont basés sur la recherche à une seule clé, mais il y existe des cas ou nous désirons par exemple trier les objets par date, puis pour chaque date les ranger par taille, puis par identifiant. Si nous voulions faire cela avec un simple Dictionnary, nous serions obligés de l'implémenter de la façon suivante :

Dictionary>> index = new Dictionary>>();

Comme vous l'aurez compris, nous imbriquons trois dictionnaires. Non seulement cette méthode apporte de la complexité au niveau de l'implémentation, mais surtout la quantité de mémoire utilisée sera multipliée par 3, nécessitant 2Go de RAM au lieu des 650Mo du SortedDictionnary.

La SortedDictionnary permet de s’affranchir du problème grâce à l'implémentation obligatoire d'un "comparer" qui lui permettra de faire de la recherche dichotomique sur une liste de dictionnaires internes.

Ma solution : la SortedSplitList

Sur le même modèle que les tests précédents, voici ce que donne l'insertion de 10 millions d'objets dans une SortedSplitList



Temps total d'insertion pour 10 millions d’éléments : 5 secondes
Mémoire total pour indexer 10 millions d’éléments : 235 Mo

L'algorithme met autant de temps qu'un SortedDictionnary pour une consommation mémoire presque 3 fois inférieure au Dictionnary ou SortedDictionnary !

Comment fonctionne la SortedSplitList ?

Pour mettre au point cet algorithme, je suis parti de la SortedList et j'ai cherché à améliorer sa vitesse d'insertion.

Comme je vous l'ai expliqué lors de l'analyse de performance de la SortedList, sa lenteur viens du fait que pour insérer un élément au début d'une liste, le système doit tout d’abord retirer tous les éléments supérieurs à l’élément à insérer, puis les ré-empiler par la suite.

Pour que vous puissiez vous rendre compte du temps perdu à faire cette opération, empilez soigneusement une centaine d'assiettes les unes sur les autres, puis insérez en une à la dix-huitième position.

La méthode consiste donc à ne pas travailler sur une seule liste qui peut potentiellement être énorme, mais de travailler sur plusieurs petites listes rangées de manière intelligentes :

Structure interne de la SortedSplitList


Imaginons que nous voulons faire l'insertion de l’élément 27. Dans un premier temps, nous faisons une recherche dichotomique sur l'ensemble des listes afin de trouver celle qui devra contenir l’élément;

Recherche de la sous liste devant accueillir le nouvel élément


Une fois la liste obtenue, il suffit de faire une deuxième recherche dichotomique à l'intérieur de celle ci, et d'y insérer l’élément comme dans une liste triée classique :

Recherche de l'emplacement d'insertion dans la sous-liste


Insertion du nouvel élément.


Grâce à cette méthode simple, l'impacte sur le temps d'insertion est fortement diminué tout en conservant l'avantage d'une consommation mémoire optimale !

Utilisation du code

Pour utiliser une SortedSplitList, il faut déclarer en premier lieu un "comparer" par défaut de l'objet que l'on désire indexer :

Objet à indexer :


public class TestObject
{
    public int Id { get; set; }
    public int Id2 { get; set; }
    public DateTime Date { get; set; }
    public string Data { get; set; }
}


Comparer par défaut :

public class CompareById : IComparer
{
    public int Compare(TestObject x, TestObject y)
    {
        if (x.Id == y.Id)
            return 0;
        if (x.Id < y.Id)
            return -1;
        return 1;
    }
}

Créez ensuite une nouvelle instance de SortedSplitList avec ce comparer :

SortedSplitList sortedSplitList = new SortedSplitList(new CompareById());

Une fois ceci fait, vous pouvez ajouter, retrouver ou supprimer des éléments de la liste à l'aide des méthodes Add(), Retrieve() ou BinarySearch() et Remove(), RemoveAll() ou Clear().

En ce qui concerne la méthode Retrieve, le premier argument doit être une instance d'objet configurée avec les paramètres sur lesquelles on désire effectuer la recherche.

Par exemple, si je veux retrouver un TestObject dont l'identifiant est 78 pour connaître la valeur de son champ Data, je dois m'y prendre de la façon suivante :

var searchObject = new TestObject {Id = 78};
var foundObject = sortedSplitList.Retrieve(searchObject);
Console.WriteLine(foundObject != null ? foundObject.Data : "Data not found.");

La méthode BinarySearch() permet quant à elle de retrouver l'index de l’élément que l'on recherche, donnant la possibilité de parcourir la SortedSplitList en fonction de vos besoins.

Pour savoir comment travailler avec l'index retourné par la méthode BinarySearch(), referez vous à la page MSDN traitant de la méthode BinarySearch() sur les listes génériques

Travailler avec des enregistrements rangés selon des critères multiples

Imaginons que nos TestObject aient une clé composée de Date + Id.
Comment faire pour ranger et retrouver un tel élément dans un SortedSplitList ?

Pour ranger les éléments, rien de plus facile. Il suffit de définir un comparer par défaut comme suit :

public class CompareByDateId : IComparer
{
    public int Compare(TestObject x, TestObject y)
    {
        int dateResult = x.Date.CompareTo(y.Date);
        if (dateResult == 0) {
            if (x.Id == y.Id)
                return 0;
            if (x.Id < y.Id)
                return -1;
            return 1;
        }
        return dateResult;
    }
}

Ainsi, les méthodes Add() et Retrieve() vous permettrons de travailler avec vos éléments à clé composée.

Maintenant, comment s'y prendre si nous voulons connaitre tous les éléments d'une date donnée ?

Voyant que la SortedSplitList permet donne accès à un enumerator, un débutant en programmation serait tenté d'utiliser la méthode suivante :

foreach (var testObject in sortedSplitListSortedById.Where(a => a.Date == DateTime.Parse("2003/01/01")))
           Console.WriteLine(testObject.Data);


Cela peut parfaitement fonctionner si votre liste contient peu d’élément, mais les performances de cette ligne de code peuvent s'avérer catastrophique ci la liste contient plusieurs millions d'objets.

La méthode adéquate dans ce cas consiste à faire une recherche binaire selon un comparer spécialisé pour la recherche de date, puis de parcourir les éléments de la liste en sens inverse jusqu’à trouver le premier. Une fois cet élément trouvé, il suffit de reparcourir la liste dans le bon sens en ne renvoyant que les éléments qui correspondent avec les critères de recherche.

Si tous cela vous semble compliqué à mettre en oeuvre, la méthode PartiallyEnumerate() risque de vous être fort utile pour mener à bien cette tache. Voici comment l'utiliser.

Tous d’abord, nous définissions un comparer spéciale pour la recherche par date, et uniquement par date :

public class CompareByDate : IComparer
{
    public int Compare(TestObject x, TestObject y)
    {
        return x.Date.CompareTo(y.Date);
    }
}   

Ensuite, nous pouvons appeler la méthode PartallyEnumerate() en lui passant un objet de comparaison comportant la date que nous désirons, et le comparer ci dessus :

foreach (var testObject in sortedSplitList.PartiallyEnumerate(new TestObject() { Date = DateTime.Parse("01/01/2003") }, new CompareByDate()) )
           Console.WriteLine(testObject.Id);                           

Avec cette méthode, le foreach affichera tous les id des elements datés du 01/01/2003 sans prendre plusieurs minutes si votre liste contient des millions d'objets.

Conclusion

Bien que les outils d'indexations fournis par le .NET framework soient largement suffisant pour la plupart des besoins logiciel, la SortedSplitList peut s'avérer très utile pour traiter un très grand volume d'objets dans votre application, celle-ci allie l'optimisation mémoire d'une SortedList, la vitesse d'insertion d'un SortedDictionary tout en donnant la possibilité de travailler sur plusieurs clés.

Ainsi, vous pourrez facilement indexer vos données selon plusieurs critères sans craindre de voir trop rapidement apparaître une OutOfMemoryException.






Programmation multithread, kesako ?
27 avr. 2013
La loi de Moore prévoit que la puissance des ordinateurs double tous les deux ans. Or, depuis 2004, la fréquence des processeurs tend à stagner en raison de difficultés rencontrées par la miniaturisation des composants.
Pour gagner en puissance, les constructeurs de microprocesseurs ont dû avoir recours au traitement parallèle afin de maintenir les prédictions de Gordon. Pour ce faire, ils ont privilégié le nombre de cœur tout en prônant la programmation multitâche (appelé multithread dans le jargon).

Dans cet article de vulgarisation, je vais essayer de décrire de façon simple en quoi consiste la programmation multitâche et pourquoi ce concept rend la vie des développeurs qui veulent s'y atteler plus difficile. Nous verrons aussi que malgré les promesses de puissance, toutes les applications ne gagnent pas à être écrite en multithread.


Concept de base

Pour que tout le monde comprenne bien le principe, je vais prendre l'exemple d'une femme de ménage qui doit ranger une maison.


Son programme de rangement est le suivant :
- ranger la salle de bain
- faire les sols
- ranger la cuisine
- ranger le salon
- faire les vitres
- ranger la chambre

Maintenant, comment faire pour que la maison soit rangée le plus vite possible ?

La première idée consiste à optimiser le programme. Par exemple si nous observons que notre femme de ménage utilise les mêmes produits pour la chambre et pour la salle de bain, nous pouvons placer ces deux tâches de manière consécutive. Ensuite, nous pouvons dire que faire les vitres et les sols sont des tâches à faire à la fin. Et si en plus ces deux tâches utilisent les mêmes produits ménagers que pour la salle de bain, on peut les placer à la suite, ce qui donnerais :

- ranger la chambre
- ranger le salon
- ranger la cuisine
- ranger la salle de bain
- faire les sols
- faire les vitres


Après quelques essais, on se rend compte que notre programme est optimisé, mais on ne gagne pas énormément en performance.

Nous décidons alors d'augmenter la fréquence d’exécution de notre femme de ménage :
La maison se range un peut plus vite, mais vous conviendrez que la femme de ménage à une limite physique que même la cocaïne et la promesse d'un gros salaire ne peuvent dépasser.

Pour aller plus vite, il faut donc faire du multitâche, et c'est la que les choses se compliquent.

Déjà, première chose, comme vous l'aurez compris dans mon analogie, la femme de ménage représente le microprocesseur de l'ordinateur, c'est à dire l'unité d’exécution du programme.

Si je demande à cette même femme de ménage de ranger en même temps la chambre, le salon, la cuisine, la salle de bain, etc... je lui fait faire du multitâche ! mais que va-t-il se passer ?



Tout d'abord, elle vas commencer par la chambre. En plein milieu de son activité, elle va tout arrêter, noter sur un petit tableau ce qu'elle est entrain de faire, ranger son matériel dans son petit chariot pour passer dans le salon. Là, elle va redéployer tout son matériel afin de commencer une nouvelle tâche comme passer l'aspirateur. Au bout de quelques minutes, elle arrête, note là où elle en est, range son matériel et passe dans la cuisine, tout cela en boucle jusqu’à ce que la maison soit toute rangée.

Comme vous le remarquerez, le passage d'une tâche à une autre fait perdre beaucoup de temps. C'est ce que l'on appelle en informatique le "Context Switch". Ainsi, faire un programme qui exécute plus de tâches parallèles qu'il n'y a d'unité d’exécution fait perdre énormément de temps ! Voici un premier point de vigilance à prendre en compte lorsque l'on est débutant en programmation multithread.

La solution semble donc toute trouvée ! Il suffit d'avoir autant de femme de ménage qu'il y a de tâches parallélisables.

Une pour ranger la chambre,
Une pour ranger le salon,
Une pour ranger la cuisine,
Une pour ranger la salle de bain
Une pour les sols et une pour les vitres. 

Ce qui fait 6 femmes de ménages qui peuvent travailler en même temps, ainsi la maison sera rangée 6 fois plus vite !

Voici la réflexion que se sont faits les constructeurs de microprocesseur. Le seul moyen d'aller plus vite est de mettre à disposition du programmeur un maximum d'unités de traitement. C'est ce qu'on appelle des cœurs (core en anglais).

Ainsi lorsque vous lisez dans les caractéristiques techniques d'un microprocesseur "2 coeurs, 4 Threads" cela signifie que vous disposez de deux unités de traitement, qui chacune est capable de faire deux choses à la fois sans faire de "Context Switch" (par un mécanisme appelé l'hyperthreading) ce qui virtuellement correspond à 4 unités de calcul.




En gros on vous explique que vous avez 2 femmes de ménages, mais que l'une peut ranger la chambre tout en repassant des chemises, et que l'autre peut faire la vaisselle tout en rangeant la cuisine, alors c'est comme si vous aviez 4 femmes de ménages. Oui je sais, c'est un peut de l'arnaque, et c'est pour ça qu'il vaut mieux privilégier le nombre de cœurs au nombre de threads.

Quelle est l'impacte en terme de programmation ?


Le programmeur qui désire tirer parti de toutes les unités de calcul mises à sa disposition dans son programme n'est pas au bout de ses peines, car celui ci doit gérer plusieurs mécanismes de synchronisation qui non seulement s'avèrent très difficiles à mettre en oeuvre, mais qui plus est, complexifie énormément la relecture de code ainsi que la recherche de bug.

La synchronisation des ressources

Vous avez 6 femmes de ménages qui travaillent en parallèles. Mais comment ça se passe si elles n'ont à leur disposition qu'un seul aspirateur et une seule serpillière ? C'est ce que l'on appelle en informatique la synchronisation de ressources.

Prenons un exemple simple : 
La femme de ménage qui range le salon a pour programme :

  • Passer l'aspirateur
  • Si une tache est visible sur la moquette
    • Prendre l'éponge sur le meuble de la cuisine
    • Laver la tache avec l'éponge
    • Reposer l'éponge sur le meuble de la cuisine
  • Continuer à passer l'aspirateur.

La femme de ménage qui range la cuisine a quant à elle le programme suivant :

  • Ranger la vaisselle issue du lave vaisselle.
  • Si une assiette n'est pas propre
    • Prendre l'éponge sur le meuble de la cuisine
    • Laver l'assiette avec l'éponge
    • Reposer l'éponge sur le meuble de la cuisine
  • Continuer à ranger la vaisselle

Vous remarquez qu'entre ces deux programmes, il y a une ressource partagée qui est l'éponge.




Prendre l'éponge sur le meuble de la cuisine et Reposer l'éponge sur le meuble de la cuisine sont des actions de synchronisation de ressource qu'on appelle en informatique des "sections critiques" qui permettent le "verrouillage par exclusion mutuelle"

Effectivement, lorsque les deux femmes de ménage vont travailler à leurs tâches respectives, si les deux désirent prendre l'éponge en même temps, c'est la première qui aura mis la main dessus qui l' obtiendra, la seconde devra attendre que l'éponge soit remise sur le meuble de la  cuisine pour la prendre à son tour.

Comme vous vous en doutez, celle qui c'est fait piquer l'éponge doit attendre que l'autre veuille bien la rapporter pour continuer son travail, ce qui fait perdre du temps, et peut engendrer de nombreux bugs.

Par exemple, si dans le programme de celle qui range le salon, nous avions oublié de dire Reposer l'éponge sur le meuble de la cuisine, que se passait-il ?

Tant que la femme de ménage qui range le salon ne trouve pas de tache sur la moquette, tout se passe bien. D'ailleurs, tout se passe aussi bien si elle trouve une tache sur la moquette alors que celle qui fait la cuisine a fini de ranger le lave vaisselle. Par contre, si celle du salon prend l'éponge et que celle qui range la cuisine en a besoin car elle a trouvée une assiette sale dans le lave vaisselle, elle attendra indéfiniment l'éponge et ne pourra pas terminer son travail du fait que celle qui range le salon ne la ramènera jamais !

Ce bug de synchronisation de ressource tout bête est déjà bien difficile à analyser alors que seulement une ressource est en concurrence avec deux tâches parallèles. Le moindre oubli en programmation multithread ne pardonne pas !

Un autre type de bugs similaire appelé le "dead-lock" fait le cauchemar des programmeurs.
Imaginons que les programmes de nos deux femmes de ménages soient modifiés de la façon suivante :

La femme de ménage qui range le salon a pour nouveau programme :
  • Faire la poussière
  • Faire d'autres trucs.
  • Prendre l'aspirateur dans le placard.
  • Passer l'aspirateur
  • Si une tache est visible sur la moquette
    • Prendre l'éponge sur le meuble de la cuisine
    • Laver la tache avec l'éponge
    • Reposer l'éponge sur le meuble de la cuisine
  • Continuer à passer l'aspirateur.
  • Remettre l'aspirateur dans le placard
La femme de ménage qui range la cuisine a quant à elle le programme suivant :
  • Ranger les placard de la cuisine
  • Faire d'autre trucs
  • Prendre l'éponge sur le meuble de la cuisine
  • Nettoyer la table de la cuisine
  • Si des miettes tombes par terre
    • Prendre l'aspirateur dans le placard.
    • Aspirer les miettes avec l'aspirateur
    • Remettre l'aspirateur dans le placard
  • Continuer à nettoyer la table.
  • Reposer l'éponge sur le meuble de la cuisine

Pouvez vous voir le bug qui se cache entre ces deux programmes ?
Les deux pris individuellement ne semblent pas poser de problèmes, pourtant lorsqu'ils sont exécutés ensemble, il peuvent conduire à ce que l'on appelle un "interblocage"

Effectivement, nous sommes en présence de deux ressources partagées qui sont utilisées dans des ordres différents. Cette pratique conduit au fameux bug de DEAD-LOCK qui fait trembler tous les programmeurs qui l'on déjà rencontrés.

Comment est-ce possible ?

La femme de ménage du salon fait des trucs et prend l'aspirateur, elle s'approprie donc cette ressource pour continuer son travail. Dans le même temps celle qui range la cuisine prend l'éponge.

Tout va bien jusqu'a ce que celle qui fait le salon rencontre une tache sur la moquette, la elle va aller chercher l'éponge, et va attendre que celle qui fait la cuisine la restitue, car elle est en train de l'utiliser pour ranger la table. Manque de bol, en nettoyant la table, celle qui fait la cuisine fait tomber des miettes, elle va donc aller chercher l'aspirateur dans le placard qui n'y est pas, car il est dans le salon. Sans le savoir, les deux femmes de ménages s'attendent mutuellement, et l'ensemble du programme s'arrête !

Mais comment éviter ça ?!?

De mon expérience  la meilleur façon d'éviter un deadlock lorsque l'on écrit un programme multitâche est de faire en sorte que l’acquisition de plusieurs ressources partagées soient toujours faite dans le même ordre.
Si cela engendre une pénalisation trop forte des performances, il faut faire en sorte de synchroniser les tâches entre elles pour qu'elles prennent un autre chemin de code si une ressource principale est déjà prise.

Mais la meilleur façon d'éviter les dead-lock tout en gardant un bon niveau de performance est bien sûr de tout faire pour avoir un minimum de ressources partagées entre plusieurs tâches. Chose facile à dire :p

La synchronisation des tâches

Vous avez 6 femmes de ménages qui travaillent en parallèles. Mais que se passe-t-il si celle qui fait les sols arrive dans une pièce alors que quelqu'un d'autre est en train de passer l'aspirateur ?

Nous sommes ici devant un cas de synchronisation de tâches, qui s'il est mal mené peut conduire à un type de bug extrêmement difficile à trouver, j'ai bien sûr nommé le terrifiant "Race Condition" ou "Situation de compétition" pour les français.

Qui quoi donc ?

Une situation de compétition se produit lorsque un défaut de synchronisation apparaît dans un système multitâche.

Sortons du monde des femmes de ménages et imaginons plusieurs personnes qui construisent un mur.


Bob place les briques, c'est sa spécialité. Il ne réfléchi pas, il met la brique sur la couche de ciment de façon parfaite.

Paul lui met le ciment, il en a toujours d'avance et le prépare à une vitesse folle. Il place la bonne dose où il faut et quand il faut.









Le programme est très simple :

Faire
Placer ciment.
Placer brique.
jusqu'à ce que le mur soit fini

Je demande à bob d’exécuter ce programme, mais celui ci me dit qu'il ne sais placer que des briques. Il lui faut l'aide de Paul pour aller plus vite.

La modification du programme est trivial :


Faire
Paul, tu places le ciment.
Bob, tu places la brique.
Jusqu'à ce que le mur soit fini

Je lance le programme, et la, c'est la catastrophe !


Mais que s'est-il passé ?

Je demande à Paul et à Bob de travailler de façon décomposée pour que je comprenne leur erreur. au bout de 3 heures d'observation à regarder Paul placer le ciment puis bob placer la brique, j'arrive à un résultat parfait.


Quelqu'un va-t-il m'expliquer ce qui se passe !?!?!

Nous sommes en présence de ce qu'on appelle en informatique un "Heisenbug"
Ce terme fait référence à Werner Heisenberg, un physicien quantique qui a démontré que dans cette discipline  le simple fait d'observer une expérience peut modifier le résultat de cette expérience. Et c'est exactement ce qui nous est arrivés avec nos deux personnages !

Comment est-ce possible ?

Lorsque je demande à bob de placer une brique, il ne réfléchi pas et place la brique. Il n'attend pas que Paul est mis préalablement le ciment, et inversement.
Ainsi, lorsque je dis de façon unitaire à Paul : "Place le ciment", puis une fois que je me suis assuré qu'il l'a bien fait je dis à bob : "Place la brique", j'agis comme un agent de synchronisation externe sans le savoir !

Par contre, quand je lance le programme, je donne le GO à Paul, puis un peut plus tard je    donne un GO à Bob. Ce décalage de temps peut donner l'illusion que tout va bien, mais il suffit   que Paul prenne un peut de retard pour préparer sa mixture, et Bob va se mettre à empiler des briques l'une sur l'autre sans ciment !!

Mais alors que faire ??

Il faut modifier le programme pour synchroniser les tâches :


Faire
Paul, tu places le ciment, et tu attends que bob est placé une brique
Bob, tu places la brique et tu attends que bob est mis du ciment.
Jusqu'à ce que le mur soit fini

Dorénavant, Paul et Bob sont synchronisés.

Conclusion

J'espère que cet article de vulgarisation très sommaire aura permis à quelques uns d'avoir une meilleure vision de ce que représente le multitâche en programmation. 

Pour ma part, la plus grande difficulté lorsque l'on inspecte un programme multitâche est de réussir à penser en plusieurs dimensions temporelle. Un peut comme si vous deviez lire une partition de musique pour laquelle chaque note peut être jouée par plusieurs instruments en même temps, mais avec des temps décalés.

L'exercice est difficile, mais ce n'est qu'à ce prix qu'un programmeur sera capable d'exploiter toute la puissance offerte par les nouveaux microprocesseurs, et avec l'explosion des nouveaux types d'applications (reconnaissance vocale, réalité augmentée, intelligence artificielle, ...) il faut avouer que ce ne sera pas du luxe !


Et c'est ainsi que démarre mon blog.
05 avr. 2013
Certains veulent devenir pompier, d'autres vétérinaires, moi j'ai toujours voulu devenir programmeur.

"Quoi ? tu fais ce job de looser de ton plein grès ?"

Il est vrai qu'aujourd'hui, c'est un métier qui d'un point de vu extérieur parait bien barban. Rester des heures sur un ordinateur pour taper des lignes et des lignes dans une langue inconnue du commun des mortels, tout ça pour afficher un bouton qui fait pouêt pouêt, ça semble difficile d'intéresser la moindre personne avec ce concept lors d'une conversation.

Et pourtant, beaucoup de gens pratiquent cette discipline avec passion.

Je suis programmeur, entrez dans mon monde...


Celui-ci commence avec l'école, les choses que l'on essaient de m'apprendre me lassent. Non pas parce que je suis un génie, mais parce que je ne comprends pas toujours bien l'utilité pratique.

- Moi : "Mais à quoi ça sert madame de savoir résoudre une dérivée ou une primitive ?"
- Prof : "ça sert à avoir ton bac."
- Moi : "Ce que je voulais dire, c'est dans quelle application pratique ?"
- Prof : "C'est dans l'application du programme du bac"
- Moi : "..." (moment de solitude)

Et un jour, je découvre un ordinateur.

Au début, comme tout ado qui se respecte, je pense que c'est une console de jeux ! youpi, on s’éclate ! Et un jour, la conscience s'éclaire.

Je joue avec un pote à un vieux jeux "cracké" , et je m’aperçois que toutes les phrases du jeu ont été remplacées par des équivalents humoristiques, du genre :"Attention, vous entrez dans un champs de pines". Mon pote me fait remarquer la faute de frappe mise en oeuvre par le gars qu'il connait, et qui a cracké le jeu.

- Moi : "Comment ? tu veux dire qu'on peut modifier le jeu ? mais comment c'est possible ?"
- Pote : "Il suffit de parler la langue des machines !"

Et c'est partie. Comme un accro qui vient de s'injecter son shoot d’héroïne,  je prends conscience d'un nouveau monde autour de moi. L’ordinateur n'est pas une bête comprise uniquement par des gens en chemise blanche à grosse lunette qui détiennent les plans secrets de cette machine, mais n'importe qui peut lui dire quoi faire, il suffit de parler sa langue !

Quelques jours plus tard, un mec entre en cours de technologie.

"Monsieur machin est informaticien, il va travailler sur l'ordinateur pour le programmer afin de gérer les bulletins de notes. Veuillez ne pas prêter attention à ce qu'il fait et restez attentif s'il vous plait"

Pardon ? Tous les profs du bahut seront guidés par cette machine qui elle même est guidée par ce gars pas très net ? mais c'est génial !

Je cherche sur mon ordinateur et découvre le langage BATCH. Un premier contact avec l'ordinateur ce fait.

- Moi : "echo bonjour"
- Machine : "bonjour"

La machine me parle, j'ai l'impression d'être un astronaute qui communique avec un extraterrestre pour la première fois.

- Moi "echo Bonjour maitre"
- Machine "Bonjour maitre"

Elle semble se soumettre facilement

- Moi "echo "
- Machine "La syntaxe de la commande n'est pas correcte."

Mais que se passe t'il ? en cherchant plusieurs heures, je découvre que le caractère > agit comment un indicateur de sortie.

- Moi "Echo bonjour maitre > LPT1"

La machine imprime sur mon imprimante "Bonjour maitre" ! je lui ai donc commandé de contrôler un autre appareil !

Des possibilités infinies se bousculent dans ma tête, je réalise que maîtriser cette bécane, c'est maîtriser le monde de demain, le monde du future, de mon future ! Grâce à cette machine, des hommes ont réussi à aller sur la lune, d'autres prévoient le temps qu'il fera demain. Nous ne sommes qu'au début des prouesses offerte par cette nouvelle ère, et il faut absolument que j'en sois l'un des acteurs.

17 ans plus tard, tout le monde à dans sa poche un appareil lui permettant d'avoir accès à l'ensemble des connaissances de l'humanité en un instant, d'être informé instantanément des sauts d'humeur de leurs amis et de faire leurs courses pour passer le weekend avec leurs enfants plutôt que dans les magasins.

Tous ces délires ont été mis en oeuvre par des développeurs. Chacun de notre coté nous avons élaborés des algorithmes, créés des logiciels, sites web, automates, outils de gestion, jeux vidéos ... de la fonctionnalité la plus élémentaire jusqu'au site marchant qui génère des millions de chiffre d'affaire,  nous œuvrons à construire un monde qui rapproche de plus en plus, comme des bâtisseurs de cathédrales qui veulent ériger le plus majestueux des monuments afin que tous ceux qui ont la foie puissent profiter de leur savoir faire.

A travers ce blog, je vais essayer de transmettre ma passion pour ce métier mal mené, du fait qu'à mon sens, un trop grand nombre de jeunes sortant des écoles se retrouvent propulsés à ce job qui ne les inspirent pas.

Pour ce faire, j'alternerai entre des articles de vulgarisation de certains concepts de l'informatique et des articles techniques un peut plus pointus. Car il en faut pour tout le monde !

Allez, j'ai encore des bugs à corriger... saleté de métier !