Refactorisation de mon premier programme
Au moment d'écrire cette article, cela fait 10 ans déjà que j'ai écrit du code pour la première fois.
Je me rappelle encore de la joie que j'ai ressentie lorsque, après des heures de recherche, le petit programme que je venais d'écrire fonctionnait enfin comme je l'avais imaginé.
Que ce soit après la mise en ligne d'un site web, le développement d'un script ou la mise en place d'une architecture cloud, j'ai l'impression de redécouvrir à chaque fois cette sensation de satisfaction immense lorsque mon travaille fonctionne.
Passé cette phase, il vient un moment où le premier jet d'une solution commence à présenter ses faiblesses. Des bugs apparaissent, l'ajout de morceaux de code les uns après les autres dégradent la maintenabilité, il est temps de refactoriser le programme.
Après avoir retrouvé le code source du programme évoqué précédemment, c'est avec un brin de nostalgie que je vais me livrer à cet exercice aujourd'hui.
colors.bat
Lorsque je me suis intéressé au code, il n'était pas question d'installer quoique ce soit sur l'ordinateur familial. Je m'étais donc dirigé vers la programmation d'applications en ligne de commande que j'écrivais alors avec le bloc-notes de Window XP.
Le programme qui en résulte s'appelle colors.bat. Il n'a qu'une seule et unique fonction : demander à l'utilisateur de choisir une couleur, puis changer la couleur de la console pour refléter ce choix.
Alors, prêts à voir cette abomination ? 🙃
@echo off | |
:debut | |
cls | |
set /p lol=Quel est ta couleur prefere ? | |
if "%lol%"=="noir" goto noir | |
if "%lol%"=="bleu" goto bleu | |
if "%lol%"=="vert" goto vert | |
if "%lol%"=="marron" goto marron | |
if "%lol%"=="pourpre" goto pourpre | |
if "%lol%"=="kaki" goto kaki | |
if "%lol%"=="gris" goto gris | |
if "%lol%"=="rouge" goto rouge | |
if "%lol%"=="rose" goto rose | |
if "%lol%"=="jaune" goto jaune | |
if "%lol%"=="blanc" goto blanc | |
:noir | |
color 0f | |
pause | |
goto fin | |
:bleu | |
set /p choi=Quel bleu ?(fonce,gris,clair ou cyan) | |
if "%choi%"=="fonce" goto fonce | |
if "%choi%"=="gris" goto gris2 | |
if "%choi%"=="clair" goto clair | |
if "%choi%"=="cyan" goto cyan | |
:vert | |
set /p lki=Quel vert ?(vert ou clair) | |
if "%lki%"=="vert" goto vertvert | |
if "%lki%"=="clair" goto clair2 | |
:marron | |
color 40 | |
pause | |
goto fin | |
:pourpre | |
color 50 | |
pause | |
goto fin | |
:kaki | |
color 60 | |
pause | |
goto fin | |
:gris | |
set /p lkj=Quel gris ?(gris ou gris clair) | |
if "%lkj%"=="gris" goto gris3 | |
if "%lkj%"=="clair" goto clair3 | |
:rouge | |
color c0 | |
pause | |
goto fin | |
:rose | |
color d0 | |
pause | |
goto fin | |
:jaune | |
color e0 | |
pause | |
goto fin | |
:blanc | |
color f0 | |
pause | |
goto fin | |
:fonce | |
color 10 | |
pause | |
goto fin | |
:gris2 | |
color 30 | |
pause | |
goto fin | |
:clair | |
color 90 | |
pause | |
goto fin | |
:cyan | |
color b0 | |
pause | |
goto fin | |
:vertvert | |
color 20 | |
pause | |
goto fin | |
:clair2 | |
color a0 | |
pause | |
goto fin | |
:gris3 | |
color 80 | |
pause | |
goto fin | |
:clair3 | |
color 70 | |
pause | |
goto fin | |
:fin | |
cls | |
echo FIN !!! | |
pause | |
set /p encore=Une autre couleur ?(oui ou non) | |
if "%encore%"=="oui" goto debut | |
if "%encore%"=="non" goto dommage | |
:dommage | |
echo Dommage ! Une prochaine fois peut etre ... | |
pause |
Analyse
Par où commencer ?
Ignorons les fautes d'orthographe pour un instant et penchons-nous ligne par ligne sur ce que fait ce morceau de code.
@echo off | |
:debut | |
cls | |
set /p lol=Quel est ta couleur prefere ? |
Ces 4 lignes réalisent chacune une tâche bien distincte :
- La première ligne
@echo off
est une particularité des scripts Windows. Elle signale simplement à la console de ne pas afficher à l'utilisateur les commandes exécutées mais seulement leur résultat. - La seconde instruction
:debut
définie une région du programme qui sera accessible plus tard avec l'instructiongoto debut
, modifiant ainsi le flux d'éxécution du programme de haut vers le bas. - La troisième instruction permet d'effacer le contenu de la console.
- La 4ème instruction surlignée définie une variable
lol
qui contient le résultat d'une entrée utilisateur après avoir affiché la phrase « Quel est ta couleur prefere ? ». Si seulement j'avais su que je me relirais un jour...
Sans parler du nommage hasardeux des variables, nous voyons que plusieurs éléments posent problème.
En effet, d'un point de vue expérience utilisateur, aucune indication n'est donnée quant au format de l'entrée utilisateur attendu. Sans indication aucune, l'utilisateur à toute les raisons de penser qu'il pourrait aussi bien entrer bleu que bleu très clair virant un petit peu sur le vert ou encore #0000ff par exemple.
if "%lol%"=="noir" goto noir | |
if "%lol%"=="bleu" goto bleu | |
if "%lol%"=="vert" goto vert | |
if "%lol%"=="marron" goto marron | |
if "%lol%"=="pourpre" goto pourpre | |
if "%lol%"=="kaki" goto kaki | |
if "%lol%"=="gris" goto gris | |
if "%lol%"=="rouge" goto rouge | |
if "%lol%"=="rose" goto rose | |
if "%lol%"=="jaune" goto jaune | |
if "%lol%"=="blanc" goto blanc |
Une fois la variable lol
définie, ces quelques instructions se chargent de changer le flow d'éxécution du programme afin d'accéder à la bonne section selon l'entrée utilisateur fournie.
:bleu | |
set /p choi=Quel bleu ?(fonce,gris,clair ou cyan) | |
if "%choi%"=="fonce" goto fonce | |
if "%choi%"=="gris" goto gris2 | |
if "%choi%"=="clair" goto clair | |
if "%choi%"=="cyan" goto cyan | |
:vert | |
set /p lki=Quel vert ?(vert ou clair) | |
if "%lki%"=="vert" goto vertvert | |
if "%lki%"=="clair" goto clair2 |
Pour certaines couleurs, le programme demande des précisions sur la teinte de la couleur puis l'assigne à une nouvelle variable choi
pour le bleu et lki
pour le vert dans cet exemple.
Cette fois encore, nous discuterons du nommage de ces variables dans la section suivante de cet article.
:blanc | |
color f0 | |
pause | |
goto fin | |
:fonce | |
color 10 | |
pause | |
goto fin | |
:gris2 | |
color 30 | |
pause | |
goto fin | |
:clair | |
color 90 | |
pause | |
goto fin |
Chaque section définie par un label (ex: :blanc
) fait 3 choses :
- changer la couleur de l'affichage an accord avec le choix de l'utilisateur
- mettre le programme sur pause, c'est à dire attendre que l'utilisateur appuie sur une touche de son clavier avant de poursuivre l'éxecution du programme
- se rendre au bloc
fin
.
:fin | |
cls | |
echo FIN !!! | |
pause | |
set /p encore=Une autre couleur ?(oui ou non) | |
if "%encore%"=="oui" goto debut | |
if "%encore%"=="non" goto dommage | |
:dommage | |
echo Dommage ! Une prochaine fois peut etre ... | |
pause |
Une fois le contenu de l'écran effacé et le message de fin affiché, une nouvelle variable encore
permet de rediriger l'utilisateur au début du programme, ou d'en interrompre l'éxecution selon que celui-ci ait entré oui ou non.
Axes d'amélioration
Nous l'avons vu, même si ce programme fonctionne, il présente de nombreux axes d'amélioration.
Expérience utilisateur
Comme évoqué précédemment, les instructions données à l'utilisateur ne sont pas claires.
Dans la nouvelle version, je propose ainsi d'aiguiller l'utilisateur en affichant clairement le type de donnée qu'il doit renseigner.
En lieu et place de l'actuel Quel est ta couleur prefere ?
, j'ai donc fait le choix d'afficher un tableau contenant toutes les options acceptées par ce programme :
ECHO What's your favorite color in this list? | |
ECHO: | |
ECHO - Black - Gray | |
ECHO - Blue - Light Blue | |
ECHO - Green - Light Green | |
ECHO - Aqua - Light Aqua | |
ECHO - Red - Light Red | |
ECHO - Purple - Light Purple | |
ECHO - Yellow - Light Yellow | |
ECHO - White - Bright White |
Lisibilité du code
La première chose qui me saute aux yeux en lisant le code, c'est le manque de distinction entre les instructions système, les variables et les chaînes de caractères.
Même si le code embarqué dans cet article est proprement formaté, il faut garder à l'esprit qu'un développeur pourrait être amené à travailler sur ce fichier en utilisant un éditeur de texte de type vim ou nano ne présentant pas forcément de coloration syntaxique appropriée à ce format de fichier.
Ma proposition est donc la suivante : toute instruction système doit être écrite en majuscules, toutes les variables en minuscules.
J'ai également établi arbitrairement que les variables seraient écrites en camel case dans un soucis d'uniformité.
Le script étant découpé en plusieurs étapes (choix de la couleur, affichage de celle-ci et écran de fin), il convient de refléter cette organisation dans le code en sautant des lignes quand nécessaire. Ainsi, d'un simple coup d'œil nous savons que chaque bloc réalise une opération distincte :
IF /I "%color%"=="bright white" SET colorCode=f0 | |
) | |
IF "%colorCode%"=="" ( | |
ECHO This program can't handle this color. | |
) ELSE ( | |
COLOR %colorCode% | |
ECHO Here's a nice %color% for you! | |
) | |
ECHO: | |
ECHO Do you want to try another one? (y/N) |
Du bon nommage des variables
Faisons la liste des variables utilisées dans cette première version : lol
, choi
, lki
, lkj
et encore
.
Si je vous demandais d'essayer de deviner ce qu'elles contiennent simplement d'après leur nom, je suis sûr que personne ne m'aurait dit que lol
contient le nom d'une couleur ou que lki
contient une teinte par exemple.
Dans ma version finale, j'ai réduit au strict minimum leur nombre avec pour chacune un nom explicite quant à leur contenu :
Nom de la variable | Description |
---|---|
color | La couleur entrée par l'utilisateur |
colorCode | Le code couleur déterminé par la couleur entrée par l'utilisateur |
retry | La réponse à la question "Do you want to try another one?" |
DRY - Dont Repeat Yourself
Le second problème qui saute aux yeux vient des lignes suivantes :
:rouge | |
color c0 | |
pause | |
goto fin | |
:rose | |
color d0 | |
pause | |
goto fin | |
:jaune | |
color e0 | |
pause | |
goto fin | |
:blanc | |
color f0 | |
pause | |
goto fin | |
:fonce | |
color 10 | |
pause | |
goto fin |
Comme décrit précédemment, celles-ci font peu ou prou la même chose à l'exception de l'instruction color
qui change selon le choix de l'utilisateur.
Une meilleure solution consiste à effectuer cette logique plus bas dans le programme après avoir renseigné le code couleur dans une variable un peu plus haut :
IF /I "%color%"=="black" SET colorCode=0f | |
IF /I "%color%"=="blue" SET colorCode=1f | |
IF /I "%color%"=="green" SET colorCode=2f | |
IF /I "%color%"=="aqua" SET colorCode=3f | |
IF /I "%color%"=="red" SET colorCode=4f |
IF "%colorCode%"=="" ( | |
ECHO This program can't handle this color. | |
) ELSE ( | |
COLOR %colorCode% | |
ECHO Here's a nice %color% for you! | |
) |
De 20 lignes dans l'exemple précédent, nous passons désormais à 6 lignes une fois la répétition de logique évitée. La lisibilité du programme s'en trouve également fortement améliorée et un développeur n'aura aucun mal à ajouter une couleur à l'avenir sans avoir à toucher au cœur de la logique du programme.
Version finale
Une fois ces quelques petites améliorations effectuées, le programme s'en retrouve significativement plus facile à lire, à comprendre et à utiliser.
Si un développeur souhaite ajouter une couleur, il n'a qu'à l'ajouter à liste des possibilités puis ajouter son code à la suite de ceux déjà en place.
@ECHO OFF | |
:defineColor | |
CLS | |
COLOR 0f | |
ECHO What's your favorite color in this list? | |
ECHO: | |
ECHO - Black - Gray | |
ECHO - Blue - Light Blue | |
ECHO - Green - Light Green | |
ECHO - Aqua - Light Aqua | |
ECHO - Red - Light Red | |
ECHO - Purple - Light Purple | |
ECHO - Yellow - Light Yellow | |
ECHO - White - Bright White | |
ECHO: | |
SET /P color=^> | |
IF "%color%"=="" ( | |
GOTO defineColor | |
) ELSE ( | |
IF /I "%color%"=="black" SET colorCode=0f | |
IF /I "%color%"=="blue" SET colorCode=1f | |
IF /I "%color%"=="green" SET colorCode=2f | |
IF /I "%color%"=="aqua" SET colorCode=3f | |
IF /I "%color%"=="red" SET colorCode=4f | |
IF /I "%color%"=="purple" SET colorCode=5f | |
IF /I "%color%"=="yellow" SET colorCode=60 | |
IF /I "%color%"=="white" SET colorCode=70 | |
IF /I "%color%"=="gray" SET colorCode=80 | |
IF /I "%color%"=="light blue" SET colorCode=9f | |
IF /I "%color%"=="light green" SET colorCode=a0 | |
IF /I "%color%"=="light aqua" SET colorCode=b0 | |
IF /I "%color%"=="light red" SET colorCode=cf | |
IF /I "%color%"=="light purple" SET colorCode=d0 | |
IF /I "%color%"=="light yellow" SET colorCode=e0 | |
IF /I "%color%"=="bright white" SET colorCode=f0 | |
) | |
IF "%colorCode%"=="" ( | |
ECHO This program can't handle this color. | |
) ELSE ( | |
COLOR %colorCode% | |
ECHO Here's a nice %color% for you! | |
) | |
ECHO: | |
ECHO Do you want to try another one? (y/N) | |
SET /P retry=^> | |
IF /I "%retry%"=="y" GOTO defineColor | |
IF /I "%retry%"=="yes" GOTO defineColor | |
EXIT /B %ERRORLEVEL% |