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"

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.

Si tu as déjà jasé développement avec moi, il y a de fortes chances que PowerShell soit venu sur le sujet. Avant l’arrivée de PowerShell sur Windows en 2006, les développeurs Windows qui voulaient tirer profit de la ligne de commande étaient pris avec le bon vieux BATCH (cmd.exe). Bien qu’on puisse se débrouiller et arriver à un certain niveau avec le terminal classique, c’est très loin d’être optimal.

PowerShell nous amène beaucoup plus loin et adresse plusieurs irritants pour les développeurs qui veulent utiliser la ligne de commandes dans leur routine quotidienne. Notamment, le support de script de profil qui permet de mettre à notre main nos sessions PowerShell.

Ce petit guide propose une façon simple d’avoir un profil PowerShell propre, bien structué et réutilisable, en quelques étapes facile.

Créer un répertoire Git pour les scripts de profile

Le meilleur moyen de stocker et éventuellement partager notre profil PowerShell est de le versionner dans un répertoire Git. De cette façon, notre boîte à outil est accessible de partout. J’ai personnellement choisi GitHub, mais plusieurs options s’offrent à toi. Gitlab et Bitbucket offrent gratuitement des répertoires privés, si c’est important pour toi.

La structure du répertoire

powershell-dev-environment/
├── src/
│   ├── profile.ps1
│   ├── _imports.ps1
│   ├── _prompt.ps1
│   └── _ui.ps1
└── install.ps1

Le point d’entrée

Le fichier profile.ps1 est le point d’entrée de notre profil.

# profile.ps1

$env:devProfileDir = $PSScriptRoot

. "$env:devProfileDir\_imports.ps1";
. "$env:devProfileDir\_ui.ps1";
. "$env:devProfileDir\_prompt.ps1";

Noter qu’on stocke le chemin vers notre profil dans une variable d’environnement ` $env:devProfileDir`. Ça peut être utile pour y revenir rapidement.

Le fichier _imports.ps1 sert à définir tous les modules externes qu’on souhaite charger dans chaque session PowerShell.

# _imports.ps1

Import-Module posh-git
# et ainsi de suite...

ATTENTION, certains modules peuvent prendre beaucoup de temps à s’initialiser (oui c’est toi que je regarde Carbon), alors il est important de bien réfléchir à ce qu’on veut exécuter à chaque démarrage de session.

Les couleurs

Les différentes couleurs d’affichage sont configurées dans le fichier _ui.ps1

# _ui.ps1

$console = $host.UI.RawUI

$console.ForegroundColor = "white"
$console.BackgroundColor = "black"

$colors = $host.PrivateData
$colors.VerboseForegroundColor = "white"
$colors.VerboseBackgroundColor = "blue"
$colors.WarningForegroundColor = "yellow"
$colors.WarningBackgroundColor = "darkgreen"
$colors.ErrorForegroundColor = "white"
$colors.ErrorBackgroundColor = "red"

Clear-Host

La fonction Prompt

Prompt est une fonction spéciale de PowerShell qui est exécutée pour générer la zone de saisie de commande. Lorsqu’elle est définie dans le script de profil, elle nous donne entièrement le contrôle sur le rendu de cette zone. Voici ma fonction Prompt :

# _prompt.ps1

function Prompt {    
    $realLASTEXITCODE = $LASTEXITCODE

    $Host.UI.RawUI.ForegroundColor = $GitPromptSettings.DefaultForegroundColor

    Write-Host
    Write-Host "$ENV:USERNAME@" -NoNewline -ForegroundColor DarkYellow
    Write-Host "$ENV:COMPUTERNAME" -ForegroundColor Magenta -NoNewline
    Write-Host (Get-Date -Format " | MM/dd/yyyy | HH:MM:ss") -ForegroundColor DarkGray

    Write-Host $($(Get-Location) -replace ($env:USERPROFILE).Replace('\','\\'), "~") -NoNewline -ForegroundColor Gray

    Write-VcsStatus

    Write-Host

    $global:LASTEXITCODE = $realLASTEXITCODE

    return ""
}

Et voici un aperçu du résultat dans le terminal :

Aperçu de la zone de saisie du terminal

Exécuter les scripts de profil au démarrage des sessions

Si tu es comme moi, tu aimes avoir tout ton code à la même place sur ton poste. Donc en principe ce répertoire Git nouvellement créé se trouvera à un endroit comme c:\users\moi\dev\powershell-dev-environment

Ce qu’on veut, c’est que PowerShell exécute notre script au démarrage des sessions. Par défaut, une variable $profile est définie et elle a pour valeur le chemin vers le script de profil de la session uilisateur en cours. Sa valeur est typiquement quelque chose comme C:\Users\fcote\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1.

Ce qu’on doit faire, c’est créer ce fichier s’il n’existe pas encore, et y ajouter la ligne suivante.

. "c:\users\moi\dev\powershell-dev-environment\src\profile.ps1"

À partir de maintenant, PowerShell exécutera notre script au début de chaque session.

La touche finale : le script d’installation

L’étape précédente peut facilement être automatisée avec un petit script d’installation tout simple.

# install.ps1

$installedHint = "#https://github.com/thatfrankdev/powershell-dev-environment"
$installed = $false

if(-not (Test-Path $profile)){
    New-Item $profile
}
else{    
    $installed = (Select-String -Path $profile -pattern $installedHint | Measure-Object).Count -gt 0
}

if(-not $installed){
    Add-Content $profile ""
    Add-Content $profile ". `"$PSScriptRoot\src\profile.ps1`" $installedHint"
    powershell.exe
}

Noter l’utilisation de la variable $installedHint. L’insertion d’un commentaire au bout de la ligne permet à ce script d’être roulé plusieurs fois sans multiplier l’initialisation de notre profil.

La prochaine étape

On a maintenant une structure de base très intéressante pour construire un profil PowerShell réutilisable.

Dans un prochain article, quelques fonctions sur mesure très intéressantes à placer dans ton nouveau profil Powershell!

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;
    }
}