Ces temps-ci, je me retrouve à faire beaucoup de développement mobile. Évidemment, étant sous l’excellente plateforme Xamarin, je dois supporter plusieurs systèmes d’opérations: iOS, Android et UWP. Avant chaque déploiement, je dois m’assurer d’incrémenter les versions en suivant la gestion sémantique de version. C’est une tâche plutôt monotone pouvant être facilement automatisée: c’est ici qu’entre en compte xavtool.

C’est quoi, xavtool?

xavtool est un outil en ligne de commande disponible sous Windows via choco, scoop et sous macOS via brew. Il permet, en une seule commande, d’aller modifier les versions dans les fichiers Info.plist, AndroidManifest.xml et Package.appxmanifest.

La détection des applications n’est pas associée à un processus précis de Xamarin, ce qui fait en sorte qu’un développeur iOS qui travaille avec swift pourrait utiliser l’utilitaire sans problème.

Je vous invite sur la page officielle pour en connaître davantage: https://github.com/gabrielrobert/xavtool/

Pourquoi Go?

J’ai profité de l’occasion pour me faire une idée sur Go, un langage de programmation qui m’inspire beaucoup depuis quelques années.

Xamarin est une technologie maintenue par Microsoft, il aurait été normal d’implémenter xavtool en C# ou en F# (qu’on ne me parle pas de VB!). Je gagne déjà ma vie à écrire du C#, découvrir de nouvel écosystème n’a jamais tué personne.

De plus, j’ai vraiment été séduit par la philosophie du langage:

  • Multi plateforme
  • Gestion des erreurs forcée en dehors du classique “happy path”
  • Communauté très riche
  • Performance mise de l’avant

J’ai utilisé quelques outils qui m’ont permis de livrer quelque chose très rapidement:

  • cli - construire des programmes en mode ligne de commande
  • semver - facilite la gestion sémantique
  • mxj - permettre d’encoder et de décoder du xml
  • go-plist - permettre de travailler des fichiers plist facilement
  • go-multierror - permettre de travailler avec des listes d’erreurs
  • testify - plusieurs utilitaires pour agrémenter la librairie de test unitaire standard de Go
  • évidemment, les librairies précompilées offertes par Go

Utilisation typique

$ xavtool current
1.0.1 - androidApp (...\test\AndroidManifest.xml)
1.0.1 - iOSApp (...\test\Info.plist)
1.0.1 - uwpApp (...\test\Package.appxmanifest)

$ git flow release start '1.1.0'

$ xavtool i
1.0.1: New version: 1.1.0 (...\test\AndroidManifest.xml)
1.0.1: New version: 1.1.0 (...\test\Info.plist)
1.0.1: New version: 1.1.0 (...\test\Package.appxmanifest)

$ git commit -am "Version bump to 1.1.0"
$ git flow release finish -p

À venir

J’ai en tête quelques petites idées pour améliorer la gestion des versions d’une application mobile:

  • Gestion du nouveau format des .csproj
  • Gestion des AssemblyInfo
  • Intégration poussée dans les plateformes de CI: VSTS, AppVeyor, Travis, etc..

Voici une liste plutôt personelle de différentes commandes pouvant être utiles et qui permettent d’améliorer la productivité dans la vie de tout les jours. C’est plus une cheat sheet qu’un cas typique d’article.

Supprimer les dossiers /bin et /obj d’une solution

Attention, cela va aussi affecter les dossiers node_modules, veuillez donc effectuer cette commande avec précaution.

Get-ChildItem .\ -include bin,obj -Recurse | foreach ($_) { remove-item $_.fullname -Force -Recurse }

Voir les sources

Lire un fichier en continue (tail)

Get-Content "<path>" -Wait

Voir les sources

Supprimer des fichiers existants suite à la mise en place d’un .gitignore

git rm -r --cached . 
git add .
git commit -am "Remove ignored files"

Voir les sources

Supprimer tous les packages d’un projet (nuget / Package manager)

Get-Package -ProjectName "PROJECT_NAME" | Uninstall-Package -ProjectName "PROJECT_NAME" -RemoveDependencies

Voir les sources

Supprimer un packages de tous les projets (nuget / Package manager)

Get-Project -All | Uninstall-Package "PACKAGE_NAME" -RemoveDependencies

Voir les sources

Supprimer les fichiers d’un projet Xamarin pour charger les dépendences de nouveau

rimraf "C:\Users\YOUR_USER\AppData\Local\Xamarin"

Place l’environnement courrant en mode “Development” lors de l’utilisation d’une application ASP .NET Core.

$Env:ASPNETCORE_ENVIRONMENT = "Development"

Voir les sources

Supprimer les branches locales et remote d’un répertoire git

#!/bin/sh
# Deletes all local and remote tags.
# `git delete-tag` comes from git-extras:
# https://github.com/visionmedia/git-extras
# Is very slow but will get there in the end.
git ls-remote --tags \
| awk '!/\^{}/' \
| awk -F tags\/ '{ print $2}' \
| xargs -L 1 git delete-tag

Dans un contexte traditionnel agile, la réécriture de code doit systématiquement être pratiquée en parallèle d’une fonctionnalité. Le refactoring n’apporte aucune plus-value aux utilisateurs, mais bien à nos collègues et aux prochains individus qui auront à implémenter de nouvelles fonctionnalités. Dans un tel contexte, cela peut devenir plutôt difficile pour un responsable de projet, qui parfois n’a aucune connaissance du code, d’approuver ce genre de travaux.

Ça ne devrait pas être le cas.

Le développeur jugeant qu’une pièce de code devrait être réécrite ne doit jamais être écarté sans effectuer le calcul du retour sur investissement.

Les points positifs du refactoring

Si on vous demande quels sont les bien faits de la réécriture de code, les points à soulever à ces gens peuvent être les suivants:

  • Amélioration de la maintenabilité du programme.
  • Augmentation de la vélocité sur le moyen / long terme.
  • Introduction des nouveaux développeurs grandement facilitée
  • Un code retravaillé est beaucoup plus simple à améliorer ensuite en cas de problème de performance. Le fait que les méthodes soient courtes et bien exprimées rend le travail de profilage beaucoup plus simple.

Si le responsable est beaucoup plus axé sur la qualité du produit

Les responsables de produit visant la qualité sur le long terme du produit seront ceux qui auront le plus facilement conscience de la nécessité du refactoring. Ces gens savent qu’un produit de qualité doit être capable d’itérer et se renouveler rapidement et convenablement, quelles que soient les contraintes du marché. Un produit de qualité est composé de modules correctement pensé, et implanté de la meilleure manière possible. Cependant, la meilleure implantation est rarement la première, d’où l’importance du refactoring.

Si le responsable est axé sur le temps et l’argent

Il ne faut pas leur en parler. Vraiment. Si vous tenez à conserver votre efficacité et vous tenez à augmenter votre productivité en livrant plus rapidement, gonflez vos estimations afin de permettre de corriger les erreurs du passé. Vous serez gagnant sur le long terme. Il faut cependant faire preuve de jugement, très important!

En mode service, ça donne quoi?

Si votre travail est de desservir un client en mode forfaitaire ou au taux horaire:

  • Le client n’a pas à savoir qu’un refactoring a été effectué dans son application. Je crois cependant qu’il faut être honnête et partager le bénéfice d’une telle pratique, même si c’est plus compliqué à expliquer à quelqu’un en dehors du domaine.
  • Il faut toujours faire du refactoring en ayant une plus-value en tête et non pas parce que ça nous tente.
  • Des bouts de code qui sont en production depuis un bon moment et qui ne requièrent aucune modification, peu importe la laideur de leur implémentation, ne méritent pas d’être réécrit.

Si l’on vous dit que le refactoring ne devrait pas être facturé au client, je crois que c’est également faux. Prenons un contexte moins précis et dirigeons-nous vers l’automobile: lorsque vous achetez une voiture, vous l’utilisez pendant un certain moment. Vous roulez et profitez pleinement de celle-ci, cependant il vient un jour où l’huile sera usée et vous devrez débloquer un effort monétaire pour le faire. Le mécanicien est en droit de vous facturer ses services et le droit est identique dans un contexte logiciel. Nous sommes des professionnels oeuvrant sur des solutions qui évolues, il ne faut pas être surpris si à un moment ou un autre il faut faire de la maintenance.

Chez Spektrum Media, nous avons près d’une dizaine de serveurs ayant chacune d’elles plusieurs bases de données. Au début, gérer et maintenir une ou deux machines est un jeu d’enfant. Mais plus le chiffre gonfle, et plus on se rend compte d’un manque: un outil qui permet en un coup d’oeil d’avoir la santé de l’ensemble de notre flotte de serveurs.

C’est à ce moment qu’on a découvert Opserver de Stack Exchange, un outil OSS qui permet ce genre de choses. Sachant que les gens chez Stack Exchange ont aussi une stack fondamentalement basée sur .NET, pourquoi ne pas l’essayer?

Installation du projet sur un poste de travail

On va d’abord faire fonctionner le tout en local. Ensuite, on va passer pour des systèmes en production.

1) Faire la commande suivante dans votre console: git clone https://github.com/opserver/Opserver

2) Ouverture du projet dans Visual Studio et compilation.

3) Ajouter un site dans IIS pontant vers Opserver (et non Opserver.Core). Personnellement, je vais le faire pointer vers opserver.local.

4) Ajouter l’entrée dans le fichier host pour faire pointer 127.0.0.1 vers opserver.local.

les configurations de sécurité

problème 1

La cause du problème vient du fait que le fichier pointé sur l’image ci-haut n’existe pas. Heureusement, nous avons un fichier exemple qui se situe juste ici: /Config/SecuritySettings.config.example.

Optionnel: On peut ajouter certains networks dans ce fichier afin de pouvoir accéder à Opserver sans être authentifié.

Pour le moment, je vais laisser la configuration à <SecuritySettings provider="alladmin" />, puis supprimer le .example de l’extension du fichier. Une fois fait, c’est le moment de rafraichir notre page.

Page de connexion

Woot!

À cause de nos configurations précédentes, nous n’avons pas besoin d’identifiants pour se connecter, il donc suffit de cliquer sur Log in pour accéder au tableau de bord du logiciel.

Les configurations du matériel

Aucune configuration

Il fallait bien s’en douter, nous n’avons configuré aucune machine externe à notre instance de Opserver. Dans notre cas, ce qui nous intéresse le plus, c’est la capacité de surveiller nos instances de SQL Server. Pour ce faire, il faut aller dans le dossier Config et supprimer l’extension .example du fichier `SQLSettings.json.

Ce fichier-là est très intéressant. En voici son contenu:

{
    "defaultConnectionString": "Data Source=$ServerName$;Initial Catalog=master;Integrated Security=SSPI;",
    "clusters": [
        {
            "name": "NY-SQLCL03",
            "refreshIntervalSeconds": 20,
            "nodes": [
                { "name": "NY-SQL01" },
                { "name": "NY-SQL02" },
                { "name": "OR-SQL01" },
            ]
        },
        {
            "name": "NY-SQLCL04",
            "refreshIntervalSeconds": 20,
            "nodes": [
                { "name": "NY-SQL03" },
                { "name": "NY-SQL04" },
                { "name": "OR-SQL02" },
            ]
        }
    ],
    "instances": [
        { 
            "name": "NY-DB05",
            "connectionString": "Data Source=NY-DB05;Initial Catalog=bob;Integrated Security=SSPI;", 
        },
        { "name": "NY-DESQL01" },
        { "name": "NY-RESTORESQL01" },
        { "name": "NY-UTILSQL01" },
        { "name": "OR-DESQL01" },
        { "name": "OR-HALOG01" }
    ]
}

Concrètement, on peut y voir une série de clusters et une série d’instances accompagnées de chaînes de connexion.

On va donc commencer à tester notre système en pointant sur notre poste de travail. Évidemment, notre environnement local ne possède pas le concept de clusters, on peut donc supprimer cette section et ajouter nos informations comme suit.

{
  "defaultConnectionString": "Data Source=.\\sqlexpress;Initial Catalog=master;User Id=sa;Password=sa;",
  "instances": [
    {
      "name": "\\SQLEXPRESS",
      "connectionString": "Data Source=.\\sqlexpress;Initial Catalog=master;User Id=sa;Password=sa;"
    }
  ]
}

Les choses importantes à noter:

  • Le Data Source pointant vers mon environnement local.
  • Le Initial Catalog pointant vers master.
  • J’ai remplacé le Integrated Security par un utilisateur de ma base de données qui a les droits sur l’ensemble des tables. MSSQL propose l’utilisateur sa out-of-the-box, c’est celui-là que j’ai pris en changeant pour un mot de passe. Ne faites pas ça en production.

On y est pour l’environnement local

Local monitoring

On y est pour notre preuve de concept. C’est maintenant le moment de pousser ça en production et d’en faire un outil indipensable.

Déploiement

Étant un fan d’Azure et du cloud, je vais faire passer le tout directement dans le nuage. Pour cette étape-ci, vous aurez besoin du azure-cli.

On ne va pas se casser la tête et créer une webapp.

# Connexion
az login -u {VOTRE_USERNAME} -u {VOTRE_PASSWORD}


# Création de l'application. Notez qu'il faut changer `opserver` pour un nom unique.
az webapp create --name opserver --resource-group {resource-group-name} --plan {plan-name}


# Créer un user pour le deployment
az appservice web deployment user set --user-name <username> --password <password>


# Configurer l'application pour utiliser le git local. Utilisez un repo distinct de celui de github/opserver.
# Sauvegardez le résultat sous le format suivant: https://<username>@<app-name>.scm.azurewebsites.net:443/<app-name>.git
az appservice web source-control config-local-git --name <app-name> --resource-group {resource-group-name} --query url --output tsv


# Pousser le répertoire local vers l'environnement
git remote add azure https://username@<app-name>.scm.azurewebsites.net/<app-name>.git
git push azure master

Voilà, c’est bon!

Il vous suffit maintenant d’aller mettre vos fichiers manuellement dans votre application sous le dossier Config et vous pourrez avoir votre solution de monitoring de serveurs prêt à faire feu.

Assurez-vous de ne divulguer aucune chaînes de connexion de production dans votre répertoire. Il serait tellement facile pour quiconque mal intentionné de pouvoir s’amuser, car n’oublions surtout pas que ce projet contiendra littéralement une porte d’entrée vers l’ensemble de vos servers SQL.

Le state pattern permet d’implémenter un bout de code d’une façon orientée objet. Il permet de résoudre des problèmes d’architecture qui deviennent lourds à maintenir et se traduit en dette technique.

Mon cas préféré d’un state pattern typique adapté à la réalité est une méthode qui exécute une logique basée sur des instructions “switch” et qui est souvent reliée à un état précis (un “enum” par exemple). Voyons voir, avec une problématique de la vie réelle, comment on peut améliorer un peu nos implémentations du code.

Je désire obtenir le prix d’un instrument de musique.

On se retrouve souvent avec ce genre de classes, qui à première vue ne semble pas causer de problème, mais qui à long terme peuvent poser des problèmes de maintenabilité. Considérant que j’ai une solide suite de tests couvrant cette fonctionnalité, imaginons le code suivant:

public class Instrument
{
    public InstrumentType Type;

    public Instrument(InstrumentType type)
    {
        Type = type;
    }

    public int GetPrice()
    {
        switch (Type)
        {
            case InstrumentType.Guitar:
                return 200;
            case InstrumentType.Piano:
                return 435;
            case InstrumentType.Drum:
                return 320;
            default:
                throw new Exception("Incorrect type");
        }
    }
}

Étape 1

Je vais abstraire le prix de sorte que son calcul soit effectué dans la classe appropriée et créer quatre nouvelles entités. Je vais modifier la classe “Instrument” en lui déclarant une méthode abstraite nommée “GetPrice”, obligeant mes sous-classes à implémenter celle-ci:

public abstract class InstrumentPrice
{
    public abstract InstrumentType GetType();
}

public class GuitarPrice : InstrumentPrice
{
    public override InstrumentType GetType()
    {
        return InstrumentType.Guitar;
    }
}

public class PianoPrice : InstrumentPrice
{
    public override InstrumentType GetType()
    {
        return InstrumentType.Piano;
    }
}

public class DrumPrice : InstrumentPrice
{
    public override InstrumentType GetType()
    {
        return InstrumentType.Drum;
    }
}

Étape 2

Je dois faire en sorte d’utiliser le bon prix dans la classe “Instrument”. Il faut donc créer une nouvelle propriété qui contiendra ce prix, ainsi qu’une méthode pour l’instancier.

public class Instrument
{
    private InstrumentPrice _price;
    public InstrumentType Type;

    public Instrument(InstrumentType type)
    {
        Type = type;
        SetPrice(type);
    }

    public void SetPrice(InstrumentType type)
    {
        switch (type)
        {
            case InstrumentType.Guitar:
                _price = new GuitarPrice();
                break;
            case InstrumentType.Piano:
                _price = new PianoPrice();
                break;
            case InstrumentType.Drum:
                _price = new DrumPrice();
                break;
            default:
                throw new Exception("Invalid");
        }
    }
}

Étape 3

Il faut maintenant déplacer la logique de calcul des prix dans la classe appropriée, soit “InstrumentPrice”.

public abstract class InstrumentPrice
{
    public abstract InstrumentType GetType();

    public int GetPrice()
    {
        switch (GetType())
        {
            case InstrumentType.Guitar:
                return 200;
            case InstrumentType.Piano:
                return 435;
            case InstrumentType.Drum:
                return 320;
            default:
                throw new Exception("Invalid");
        }
    }
}

public class Instrument {
    private InstrumentPrice _price;

    // ...

    public int GetPrice() {
        return _price.GetPrice();
    }
}

Étape 4

Ensuite, c’est le moment pour mettre en place le polymorphisme. Chaque sous-classe doit implémenter sa propre logique pour avoir le bon prix. À noter qu’il faut mettre la méthode “GetPrice” de la classe “InstrumentPrice” à abstraite.

public class GuitarPrice : InstrumentPrice {
    // ...
    public override int GetPrice() {
        return 200;
    }
}

public class PianoPrice : InstrumentPrice {
    // ...
    public override int GetPrice() {
        return 435;
    }
}

public class DrumPrice : InstrumentPrice {
    // ...
    public override int GetPrice() {
        return 320;
    }
}

Conclusion

Voici maintenant notre architecture finale. C’est plus de code qu’au départ me direz-vous? Certes, notamment parce que notre contexte est de base. La complexité de notre programme n’est pas assez grande pour que ce pattern s’applique à la perfection, mais lorsque notre programme va prendre de l’expansion, et qu’il faudra notamment:

  • Attribuer des logiques de prix différentes pour chaque instrument
  • Gérer les accessoires pour chaque instrument
  • Gérer les frais d’entretien

Nous pourrons alors nous dire satisfaits d’une telle modification! Je crois qu’il faut savoir prendre la décision en tant que développeur de prendre  la décision d’inclure le state pattern dès le départ ou bien de l’omettre. Si on sait qu’aucune fonctionnalité n’est prévue nécessitant cet effort, on peut se permettre de faire la modification lorsque le temps viendra. Cependant, si nous sommes en train de monter un système d’inventaire, il serait peut-être bon de prévoir le coup l’avance.

public class Instrument
{
    public InstrumentType Type;
    private InstrumentPrice _price;

    public Instrument(InstrumentType type)
    {
        Type = type;
        SetPrice(type);
    }

    public int GetPrice()
    {
        return _price.GetPrice();
    }

    public void SetPrice(InstrumentType type)
    {
        switch (type)
        {
            case InstrumentType.Guitar:
                _price = new GuitarPrice();
                break;
            case InstrumentType.Piano:
                _price = new PianoPrice();
                break;
            case InstrumentType.Drum:
                _price = new DrumPrice();
                break;
            default:
                throw new Exception("Invalid");
        }
    }
}

public abstract class InstrumentPrice
{
    public abstract InstrumentType GetType();

    public abstract int GetPrice();
}

public class GuitarPrice : InstrumentPrice
{
    public override InstrumentType GetType()
    {
        return InstrumentType.Guitar;
    }

    public override int GetPrice()
    {
        return 200;
    }
}

public class PianoPrice : InstrumentPrice
{
    public override InstrumentType GetType()
    {
        return InstrumentType.Piano;
    }

    public override int GetPrice()
    {
        return 435;
    }
}

public class DrumPrice : InstrumentPrice
{
    public override InstrumentType GetType()
    {
        return InstrumentType.Drum;
    }

    public override int GetPrice()
    {
        return 320;
    }
}