Un projet qu'on peut reprendre
Ce que ce chapitre apporte
- Organiser un projet pour qu'un nouvel arrivant s'y retrouve sans explication orale.
- Écrire un README qui répond aux questions qu'on se pose réellement.
- Distinguer une dépendance déclarée d'une dépendance installée.
- Expliquer à quoi sert un environnement isolé et ce qu'il évite.
- Lire une contrainte de version et dire quelles versions elle accepte.
- Justifier l'existence d'un fichier de verrouillage.
- Sortir la configuration du code sans exposer de secret.
La structure minimale
Il n'y a pas de disposition universelle, mais il y a des attentes. Un projet qui les respecte se lit sans mode d'emploi.
projet/
├── README.md # à quoi ça sert, comment on l'installe, comment on le lance
├── .gitignore # ce qui ne se versionne pas
├── requirements.txt # les dépendances déclarées
├── .env.example # les variables attendues, sans les valeurs
├── src/ # le code
│ ├── lecture.py
│ └── rapport.py
├── tests/ # les tests, séparés du code
│ └── test_rapport.py
└── data/ # les données d'exemple, petites
Deux idées derrière cette disposition. Le code applicatif est séparé des tests, ce qui permet de lancer les uns sans embarquer les autres. Et tout ce qui décrit le projet, dépendances comprises, se trouve à la racine, là où on le cherche.
node_modules, .venv), les produits de compilation, les fichiers de configuration personnels, les gros fichiers binaires, et bien sûr les secrets.Le critère : si le fichier peut être régénéré à partir de ce qui est versionné, il ne se versionne pas. Un dépôt doit rester petit, parce que son historique conserve tout, à jamais.
Le README
C'est le fichier le plus lu du projet et le plus souvent négligé. Il répond à cinq questions, dans cet ordre.
- À quoi sert ce projet ? Deux phrases, en français, sans jargon interne.
- Comment l'installer ? Les commandes exactes, copiables, y compris la version de l'interpréteur attendue.
- Comment le lancer ? Un exemple complet qui produit une sortie visible.
- Comment lancer les tests ? Une commande.
- Où est le reste ? Documentation, suivi des anomalies, contacts.
Ce test se refait à chaque fois qu'un nouvel arrivant rejoint le projet, et il attrape toujours quelque chose.
Déclarer plutôt qu'installer
Une dépendance déclarée est inscrite dans un fichier versionné du projet : requirements.txt ou pyproject.toml en Python, package.json en JavaScript, pom.xml en Java.
Une dépendance installée est le code réellement présent sur la machine, dans un dossier qui ne se versionne pas.
La déclaration est la seule chose qui voyage avec le projet. Installer une bibliothèque sans l'ajouter au fichier de dépendances est la première cause de « ça marche chez moi » : le programme fonctionne sur la machine où la bibliothèque a été installée à la main, et nulle part ailleurs.
Pourquoi isoler
Sans environnement isolé, toutes les bibliothèques s'installent au même endroit, pour tous les projets. Deux projets qui ont besoin de deux versions différentes de la même bibliothèque deviennent alors incompatibles, et le second installé casse le premier.
Un environnement isolé donne à chaque projet son propre dossier de bibliothèques. Il est jetable : on le supprime et on le reconstruit à partir des dépendances déclarées, ce qui est aussi une bonne façon de vérifier que la déclaration est complète.
Les versions
La version sémantique s'écrit MAJEUR.MINEUR.CORRECTIF. On incrémente le majeur en cas de rupture de compatibilité, le mineur pour un ajout compatible, le correctif pour une correction sans changement d'interface.
Cette convention permet d'exprimer ce qu'on accepte comme mise à jour.
| Contrainte | Accepte | Refuse |
|---|---|---|
==2.1.3 | exactement cette version | tout le reste |
~=2.1.3 | les correctifs : 2.1.4, 2.1.9 | 2.2.0 |
^2.1.3 | tout le majeur 2 : 2.4.0 | 3.0.0 |
>=2.1.3 | tout ce qui suit, y compris 3.0.0 | les versions antérieures |
Regardez la dernière colonne. La même déclaration, installée à trois mois d'écart, ne donne pas la même version installée, sauf pour ==. Une contrainte souple est pourtant souhaitable : elle laisse arriver les correctifs de sécurité. La contradiction se résout par un second fichier.
Un fichier de verrouillage (package-lock.json, poetry.lock, pnpm-lock.yaml) enregistre la version exacte de chaque dépendance, directe ou indirecte, telle qu'elle a été résolue. Il se versionne.
Les deux fichiers ont donc deux rôles distincts. Le fichier de dépendances dit ce que le projet accepte, le fichier de verrouillage dit ce qui a été installé. Le premier se modifie à la main, le second est produit par l'outil et ne se retouche pas.
Deux conséquences pratiques : regarder le nombre de dépendances indirectes avant d'ajouter une bibliothèque pour une fonction de dix lignes, et mettre à jour régulièrement plutôt que par grands sauts, une montée de version après deux ans d'immobilité étant nettement plus douloureuse que douze petites.
La configuration hors du code
Une adresse de serveur, un identifiant, un chemin de fichier changent d'un environnement à l'autre. Écrits dans le code, ils obligent à modifier le programme pour le déployer ailleurs, et à commiter un secret pour le faire fonctionner.
# .env.example : versionné, sans aucune valeur réelle
API_URL=https://api.exemple.fr
API_TOKEN=
BASE_CHEMIN=./data
# .env : jamais versionné, présent sur chaque machine
Le code lit ces valeurs dans l'environnement, avec une valeur par défaut raisonnable quand c'est possible, et une erreur claire au démarrage quand la variable est obligatoire et absente. Échouer immédiatement au lancement vaut mieux qu'échouer trois heures plus tard, au milieu d'un traitement.
Exercices type
Pourquoi ne pas versionner le dossier des dépendances installées ?
Parce qu'il est régénérable à partir du fichier de dépendances : le versionner duplique une information qui existe déjà.
Parce qu'il est volumineux, souvent des dizaines de milliers de fichiers, et que l'historique Git conserve tout, définitivement.
Parce qu'il contient parfois du code compilé pour un système donné, inutilisable ailleurs.
Ce qui se versionne, c'est la déclaration et le fichier de verrouillage.
Un collègue obtient une erreur que vous n'avez pas, sur le même commit. Par où chercher ?
Par les versions installées. Comparez les sorties de pip freeze ou npm ls : si le projet n'a pas de fichier de verrouillage, vous n'avez probablement pas les mêmes versions.
Puis par l'environnement : variables absentes chez lui, fichiers de données différents, version de l'interpréteur, système d'exploitation.
Puis par ce qui traîne chez vous : une bibliothèque installée à la main et jamais déclarée fonctionne chez vous seul. Le test décisif est de reconstruire votre environnement à partir de zéro.
Quelle différence entre requirements.txt et un fichier de verrouillage ?
Le premier exprime des contraintes : ce que le projet accepte, souvent sous forme d'intervalles, et il est écrit par un humain.
Le second enregistre une résolution : la version exacte de chaque dépendance, directe et indirecte, telle qu'installée à un moment donné. Il est produit par l'outil.
Sans le second, deux installations à des dates différentes ne donnent pas le même environnement, alors que le dépôt est identique.
Pourquoi une contrainte trop stricte pose-t-elle aussi problème ?
Parce qu'elle bloque les correctifs de sécurité et les corrections d'anomalies, qui arrivent justement par les versions de correctif.
Parce qu'elle rend les mises à jour rares, donc énormes, donc risquées.
Et parce qu'avec plusieurs dépendances épinglées trop strictement, aucune combinaison ne satisfait tout le monde et l'installation devient impossible.
Le bon usage est une contrainte souple dans la déclaration, et un fichier de verrouillage pour la reproductibilité.
Pourquoi versionner un .env.example vide ?
Parce qu'il documente les variables attendues. Sans lui, un nouvel arrivant découvre les variables une par une, au fil des erreurs.
Parce qu'il donne la liste exacte à remplir, ce qui permet de vérifier au démarrage qu'aucune ne manque.
Et parce qu'il rend explicite que le vrai .env, lui, ne se versionne pas : le dépôt contient la forme, jamais les valeurs.
La méthode
- Crée un environnement isolé dès le premier
install, jamais d'installation globale pour un projet. - Déclare la dépendance dans le même geste que tu l'installes.
- Versionne le fichier de verrouillage, ne le modifie jamais à la main.
- Écris le README au fur et à mesure, et fais-le tester par un nouvel arrivant.
- Sors la configuration du code, et versionne un exemple sans valeurs.
- Reconstruis ton environnement de zéro de temps en temps : c'est le seul test de la déclaration.
- Mets à jour souvent et par petits pas plutôt qu'une fois tous les deux ans.
En résumé
- Un projet reprenable se clone, s'installe et se lance en suivant une page.
- Ce qui est régénérable ne se versionne pas.
- Une dépendance déclarée voyage avec le projet, une dépendance installée reste sur la machine.
- Un environnement isolé évite que deux projets se disputent la même version.
- La version sémantique distingue rupture, ajout et correction.
- Une contrainte dit ce que le projet accepte ; un fichier de verrouillage dit ce qui est installé.
- Les dépendances indirectes s'ajoutent au projet sans avoir été choisies.
- La configuration vit dans l'environnement, jamais dans le code, jamais dans le dépôt.
Et ensuite ? Un environnement isolé règle le problème des bibliothèques, pas celui du système : version de l'interpréteur, paquets système, base de données. Le chapitre suivant élargit l'isolement à toute la machine, avec les conteneurs.