Cet article est le premier d'une série de plusieurs articles à propos de la qualité logicielle dans un projet. Pour entamer cette série, on va parler du choix de l'interpréteur et de la gestion des dépendances.
Alors voilà, ça redevient un peu le foutoir ici. Une zone dans laquelle je publie sans discernement des articles tantôt sur la musique, tantôt sur le développement logiciel. Ça promet. J'espère que vous arriverez à suivre.
Plus un bug est découvert tard, plus il est coûteux
un mec, un jour
Cet article est le premier d'une série de 9 ou 10 articles traitant de la qualité logiciel. J'essaie de mettre au propre tout ce que j'ai appris à faire, tout au long de mes déjà 15 ans d'expérience dans le développement web, et tout ce que je mets en pratique quand je démarre un nouveau projet.
J'ai initié cette série via une présentation en interne, pour le poste de responsable qualité (QA / testeur / tmtc) que j'occupe actuellement.
On y parlera bien entendu de tests unitaires, de code styling, de versionning. Mais je vous garde la surprise.
Comme je suis surtout un développeur Python / JavaScript, je vais me baser là-dessus pour chacun des articles. Mais c'est évidemment applicable sur d'autres langages (PHP par exemple), bien que certaines parties soient vraiment orientées langages interprétés.
Allez, entrons dans le vif du sujet.
Le premier truc, quand on commence un projet, c'est de choisir son interpréteur. Ça parait évident, mais en vérité, sur un petit projet où on est tout seul, on a souvent tendance à ne pas faire attention à ça.
J'ai python installé sur ma machine, pof, c'est bon. Go.
Sauf qu'il est prudent de fixer l'interpréteur dès ce moment-là : CPython ou autre chose ? Node ou deno ? Quelle version ?
Ça serait dommage de coder sur une version trop récente de l'interpréteur si votre environnement de production propose une version plus ancienne ne prenant pas en charge certaines méthodes que vous utilisez. De plus, si un nouveau développeur embarque sur le projet, il faut lui indiquer avec quelle version travailler.
En général, on se contente de fixer la version dans le fichier readme du projet. Au passage, ça permet de commencer à faire un fichier readme (on en reparlera dans la partie sur la documentation).
En javascript, on peut spécifier la version de node (et de npm) dans le fichier package.json
(voir ici). C'est purement informatif, mais c'est un très bon endroit pour fixer les choses. De plus, nvm est une solution permettant d'installer et d'utiliser plusieurs versions de node en parallèle sur la même machine.
Dans le monde python, pyenv permet à peu près la même chose que nvm. Il est possible d'ajouter à votre projet un fichier .python-version
avec la version exacte de python à utiliser.
Poetry permet de fixer les versions prises en charge dans un fichier pyproject.toml
. Il y a aussi pipenv qui permet de faire ça et même plus. Mais je trouve que ces outils font trop de choses et je ne suis pas raccord avec leurs philosophies.
De ce fait, je préfère utiliser simplement le fichier readme pour indiquer la dépendance système.
# Requirements
* Python (>=3.10.6)
Votre projet va peut-être avoir besoin de dépendances systèmes, et de configurations particulières : bases de données, applications pour la gestion d'images, etc.
En gros, tout ce qui ne se gère pas avec le gestionnaire de dépendances de votre langage.
Pour faire simple, à partir du moment où, pour faire fonctionner votre programme, vous avez besoin de faire un apt install
ou de modifier un fichier de configuration, cela doit-être reflété dans le projet.
Pareil, ici, le plus naturel sera d'ajouter quelques lignes dans le fichier readme :
# Requirements
* Python (>=3.10.6)
* postgreSQL (>12.3)
En cas de fichiers de configuration modifiés, une section peut être présente dans le readme. Je mets parfois également des fichiers de configuration pré-configurés dans mon dépôt. Ça ne mange pas de pain, mais ça signifie qu'il faut les maintenir, en cas de montée de version de la dépendance.
Une autre solution va pouvoir nous aider ici : Docker.
En effet, sans préjuger du fait qu'on l'utilisera plus tard ou non, un dockerfile, et/ou l'utilisation de docker-compose, peuvent être une bonne source de documentation pour les pré-requis systèmes pour le projet. De plus, pour peu qu'on l'utilise dans la CI (une section que l'on verra plus tard), on aura un contrôle automatisé de la bonne installation de l'application avec les différentes dépendances impliquées.
Pour les néophytes, et pour essayer de rester simple, Docker est une application qui permet de faire de la conteneurisation : une sorte d'environnement isolé, pas vraiment virtuel, au sein duquel on va pouvoir installer nos dépendances et notre application. Docker-compose permet de faire fonctionner ensemble plusieurs de ces environnements (pour isoler des services principalement).
Ces environnements sont définis par des fichiers textes, en particulier ceux qu'on appelle les Dockerfile (en fait, c'est vraiment le nom du fichier sur le disque). On indique à partir de quelle image on travaille, puis on indique toute une série d'opérations à effectuer lorsque l'on va créer l'image. On peut donc utiliser ce fichier pour expliciter les dépendances systèmes :
Pour faire ma popotte, installe moi postgreSQL > 12.3, puis aussi imagemagick, la version la plus à jour s'il-te-plait.
Voici un exemple rapide de ce à quoi ça peut ressembler :
FROM python:3.10
WORKDIR /usr/src/app
ENV PATH=/usr/src/app/bin:$PATH
ENV TZ Europe/Paris
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update && apt-get install -y \
imagemagick \
libmagickwand-dev
COPY ./config/imageMagick-policy.xml /etc/ImageMagick-6/policy.xml
RUN pip install requirements.txt
On y retrouve la version de python, les dépendances à installer, et un copier/coller d'une configuration spécifique.
La majeure partie des langages de programmation modernes fournissent un gestionnaire de paquet permettant l'installation (mais aussi la publication) de dépendances. Apprenez à les utiliser si besoin.
Sur un projet python, utilisez pip pour installer les dépendances. Mais faites-le au travers d'un virtualenv. En effet, dans le monde python, les dépendances sont installées au niveau système. Comme on n'a pas envie de mélanger tous ses projets, on crée des environnements virtuels, un par projet, de façons à avoir des environnements isolés.
Pip, c'est très bien. Ça va chercher (par défaut) des paquets sur le Python Package Index (le fameux PyPI), et on peut lui dire d'installer des dépendances directement à partir de github si besoin.
Pensez à créer un fichier de dépendances. La plupart du temps, on crée un fichier requirements.txt
à la racine du projet, et on l'utilise comme ça :
pip install -r requirements.txt
Quand on parlera de la stratégie d'environnements, je remettrai ça en cause. Mais pour débuter c'est très bien.
Dans le monde javascript, npm
et yarn
sont les deux acteurs majeurs. Dans les deux cas, ils s'appuient sur un fichier package.json, plus complet qu'un simple fichier texte, vu qu'il permet de faire plus de choses comme lancer des commandes. Est-ce que c'est vraiment ce qu'on attend d'un package manager, je ne sais pas, mais en attendant ça fonctionne très bien et ça permet aussi de gérer un fichier .lockfile
.
Ça permet de figer les versions des dépendances à utiliser sur le projet, pour que toute l'équipe ait la même chose (ça détaille l'arbre de dépendance entièrement résolu).
Dans le monde javascript, les dépendances sont simplement téléchargées dans un dossier node_dependancies
situé dans le répertoire où vous avez fait votre npm install
. Il est possible de faire une installation globale, mais on le fait généralement pour des outils qu'on veut utiliser en dehors d'un projet.
Il pourrait exister un débat sur le fait d'utiliser npm pour les applications front. Le N de npm fait référence à node. À la base, c'est un package manager pour faire de l'applicatif backend. Il a depuis longtemps été adopté pour faire du front-end, pour vous éviter d'aller télécharger à la main jQuery et de le copier/coller dans un répertoire static
, comme on faisait dans le temps (quand on n'utilisait pas des cdn).
La plupart des frameworks front venant avec des outils permettant de packager l'application pour la production, utiliser npm pour son front est tout à fait valable. Plus complexe, mais valable. Donc il n'y a pas de débat (à ma connaissance).
Si vous travaillez sans framework, ça peut cependant nécessiter d'aller vous renseigner sur des outils comme webpack. C'est relativement complexe. Dans le sens où c'est plus complexe que de ne pas s'occuper de ça.
Et parfois, donc, ça sera plus rapide de gérer vos dépendances à la main, en allant télécharger vos fichiers (bootstrap par exemple) directement sur le site de la dépendance, et en la copiant dans un répertoire de fichiers statiques. Par contre, ça signifie qu'il vous faudra gérer vos versions à la main, et donc bien penser à vos mises à jour régulières. Ça vaudra donc le coup de le documenter dans le readme 😉
L'objectif de tout ça, c'est évidemment de mettre régulièrement à jour ses dépendances. Vous pouvez le faire manuellement, ou bien utiliser des outils :
npm outdated
Mais l'important, c'est d'adopter une stratégie vous permettant facilement de maintenir à jour les versions de vos dépendances.
Cette stratégie, en vérité, sera complète à partir du moment où on mettra en place de l'intégration continue. Mais nous reviendrons là-dessus.
Dans la prochaine partie, on va parler de stratégie d'environnement. Ou "comment penser dès le début au fait que mon application va tourner sur différentes machines, pas que la mienne"'.
2022 - tominardi.fr