Madgnome's lair

Adapted by Julien Hoarau

Étant assez actif sur le tag MSBuild de StackOverflow, je me suis dit qu’il était peut-être temps que je partage un peu.

Comme son nom l’indique (ou pas…) MSBuild est l’outil de build made in Microsoft. On peut le voir comme un équivalent à Ant pour la plateforme .Net (Il y a NAnt pour ça me direz vous, certes). J’en parle comme la dernière nouveauté, digne d’une iphone 4, mais non… MSBuild est livré et utilisé dans le framework .NET depuis la version 2.0 (2005).

Il est utilisé un peu partout au sein de la plateforme .NET : Visual Studio (Les fichiers projets, *.csproj, *.vbproj…, sont des fichiers MSBuild), Team Foundation, Xna, Expression Blend…
C’est un outil auquel tout développeur .NET sera confronté et qu’il se doit de connaître un minimum.

Tout comme pour Ant, MSBuild utilisent une syntaxe XML et se base sur des targets et des tasks. Les targets, étant des regroupements de tâches, semblables à des méthodes, permettent d’exécuter des actions particulières. Par exemple la target Build d’un fichier *.csproj construira le projet.

Je n’ai fait qu’effleurer le sujet dans ce billet, pour approfondir il vous faudra vous reporter au lien ci-dessous. Dans les prochains billets je mettrais en avant certains points particuliers, pas forcément très clair sur MSDN.

Lorsque l’on développe une application console, on se retrouve rapidement confronté au problème de la gestion des arguments passés au démarrage. Et étonnement il n’y a rien dans le framework .NET pour gérer cela (une rumeur prévoyait cela pour .NET 4, mais il n’en est rien au final). On a donc le choix entre partir vers une solution faite maison : “Après tout, je n’ai besoin que de deux paramètres… trois… quatre grands maximums” et l’on se retrouve à fouiner dans le code au bout de deux semaines car l’on avait jugé inutile d’inclure l’aide (“pour deux paramètres à quoi bon!”).

L’autre solution est bien évidemment d’utiliser une librairie, parmi les nombreuses disponibles. Je vais dans ce billet en présenter trois :

Mono.GetOptions :

Il faut d’abord récupérer Mono, et l’installer. Vous pourrez alors référencer la librairie Mono.GetOptions.dll dans votre programme et commencer à l’utiliser.

Déclaration des arguments (par le biais d’attributs):

using Mono.GetOptions;

namespace CommandLineParserTest.Mono.GetOptions
{
  public class CommandLineArgumentOptions : Options
  {
    // Option(ShortDescription, ShortForm)
    // Long option is the variable name ("--logFile"), short option is -l
    [Option("Write logFile to FILE", ShortForm = 'l')] 
    public string logFile;

    [Option("Toggle verbose mode", 'v')] 
    public bool verbose;

    public CommandLineArgumentOptions()
    {
      ParsingMode = OptionsParsingMode.Both;
    }
  }
}

Parsing des arguments :

namespace CommandLineParserTest.Mono.GetOptions
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      CommandLineArgumentOptions options = new CommandLineArgumentOptions();
      options.ProcessArgs(args);

      Console.WriteLine("Specified Program Options:");
      Console.WriteLine("logFile: {0}", options.logFile);
      Console.WriteLine("verbose: {0}", options.verbose);
    }
  }
}

NDesk.Options :

Actuellement, il n’y a pas encore de livrable prêt à l’emploi pour NDesk.Options, il faut donc télécharger les sources et le construire (d’après le site de NDesk il y a un binaire quelque part, mais je n’ai pas réussi à mettre la main dessus). Vous pouvez sinon ajouter directement le fichier NDesk.Options/Options.cs à votre projet.

Déclaration des arguments à la C# 3.0 (lambda, mais il est possible d’utiliser des delegates pour une compatibilité C# 2.0):

string logFile;
bool verbose;
bool help = false;

var commandLineArgumentOptions =
  new OptionSet()
    {
      {
        "l|logFile=",
        "Write logFile to FILE",
        v => logFile = v
      },
      {
        "v|verbose",
        "Toggle verbose mode",
        v => verbose = (v != null)
      }, 
      {
        "h|help", 
        "Show this message and exit", 
        v => help = (v != null)
      }
    };

Parsing des arguments :

try
{
  commandLineArgumentOptions.Parse(args);
}
catch (OptionException e)
{
  Console.WriteLine(e.Message);
  Console.WriteLine("Try 'program.exe --help' for more information.");
  return;
}

if (help)
{
  commandLineArgumentOptions.WriteOptionDescriptions(Console.Out);
}

Command Line Parser Library :

Télécharger. Dézipper. Référencer la librairie CommandLine.dll, on a là notre gagnant en terme de rapidité et de simplicité d’installation.

Déclaration des arguments (par le biais d’attributs) :

private sealed class CommandLineArgumentOptions
{
  [Option("l", "logFile", Required = true, HelpText = "Write logFile to FILE")] 
  public string LogFile;

  [Option("v", "verbose", Required = false, HelpText = "Toggle verbose mode")] 
  public bool Verbose;

  [HelpOption(HelpText = "Show this message and exit")]
  public string GetUsage()
  {
    HelpText help = new HelpText(Program.headingInfo);
    help.Copyright = new CopyrightInfo("Julien Hoarau", 2009, 2014);

    help.AddOptions(this);

    return help;
  }
}

Parsing des arguments :

private static void Main(string[] args)
{
  CommandLineArgumentOptions commandLineArguments = 
    new CommandLineArgumentOptions();
  ICommandLineParser parser = 
    new CommandLine.CommandLineParser();

  if(parser.ParseArguments(args, commandLineArguments, Console.Error))
  {
    Console.WriteLine("Specified Program Options:");
    Console.WriteLine("logFile: {0}", commandLineArguments.LogFile);
    Console.WriteLine("verbose: {0}", commandLineArguments.Verbose);
  }
}

Bilan

On retrouve les mêmes fonctionnalités principales dans les 3, il s’agit après d’une question de goût.
Mono.GetOptions et Command Line Parsing Library partagent un mode de déclaration par attributs, plus adapté pour cette situation, à mon sens, que la méthode de NDesk.Options à base de lambdas.

A choisir entre Mono.GetOptions et Command Line Parsing Library, mon choix se porterait sur Command Line Parsing Library, qui a l’avantage de ne pas être marqué comme obsolète et qui est plus simple à récupérer.

... même si tu penses pouvoir l'être. Apparemment, l'utilisation intensive d'IHM ne confère aucune aptitude à la création de celle-ci. C'est la dure leçon de vie qui m’a été inculquée aujourd'hui.

La dernière livraison que l’on a effectuée sur notre application s’est accompagnée de la mise en place d’une version de démonstration et d’une version d’évaluation. Le cheminement prévu pour un prospect étant :

  • L’utilisateur visite le site relatif au produit.
  • Si il est un minimum intéressé, il va sur le mode de démonstration , s'y inscrit et le teste.
  • Si la démonstration lui plaît, il remplit un formulaire pour demander une évaluation gratuite.

Au bout d’une semaine, on s’est rendu compte que les utilisateurs sautaient l’étape démonstration est allé directement à la demande d’évaluation. Après enquête, il s’agirait d’un problème d’ergonomie, les utilisateurs essayent d’aller sur le mode de démonstration, mais n’arrive pas à s’inscrire, le formulaire d’authentification est pris pour un formulaire d’inscription.

Voici une maquette de l'IHM fautive (réalisé avec Balsamiq Mockup, que je conseille vivement):

Le bouton d’inscription qui me semblait clair et suffisamment visible ne l’était en réalité pas du tout, même pour nos clients versés dans l’outil informatique (développeurs).

Il m'a donc fallu retoucher à cette page pour rendre l'information et les actions possibles plus claires à l'utilisateur.
Et voici la nouvelle version (n'a pas l'air aussi surchargé en réalité) :

Une interface qui peut vous sembler claire à vous et à votre équipe ne l’est peut-être pas. Il est donc conseillé d’effectuer des tests d’ergonomie auprès d’utilisateur lambda ne connaissant pas le produit, afin d’étudier leurs comportements.

Je clôture ce billet sur une citation de Jeff Atwood : "If you let your developers create your UI, hilarity ensues"

About....

12:00 AM

Je suis Julien Hoarau (aucun lien de parenté avec un quelconque footballeur) et je suis ingénieur R&D, sur Lyon, dans le domaine de la qualité logicielle.

Vous pouvez également me suivre sur twitter.