Qu'est-ce qu'un anti-pattern ?
Section intitulée « Qu'est-ce qu'un anti-pattern ? »Un anti-pattern, c'est une solution qui semble bonne sur le moment mais qui crée des problèmes à long terme. Le gain est immédiat et visible, le coût est différé et diffus : c'est cette asymétrie qui le rend piégeant. On l'adopte pour livrer plus vite aujourd'hui, on le paie en temps de maintenance et en incidents pendant des mois.
Dans le contexte des pipelines CI/CD (Continuous Integration / Continuous Delivery, le système automatisé qui teste et déploie votre code), les anti-patterns sont des pratiques qui :
- Ralentissent le feedback aux développeurs
- Rendent la maintenance difficile
- Créent des bugs difficiles à reproduire
- Accumulent de la dette technique (des problèmes qu'on repousse à plus tard)
La pipeline monolithe
Section intitulée « La pipeline monolithe »Une pipeline monolithe enchaîne toutes les étapes du cycle dans une seule définition, du lint au déploiement en production. Chaque exécution paie le coût de l'ensemble, même quand la modification ne concerne qu'une étape, et une défaillance sur n'importe quel maillon bloque tout le reste.
Symptôme
Section intitulée « Symptôme »Une seule pipeline fait tout : vérification du code (lint), tests, compilation (build), analyse de sécurité (scan), déploiement sur tous les environnements. Elle dure 45 minutes. Personne n'ose y toucher.
Pourquoi c'est problématique
Section intitulée « Pourquoi c'est problématique »Les quatre lignes du tableau découlent d'une même cause : l'absence de découpage. Concentrez-vous sur la première, le feedback lent, car c'est elle qui dégrade le plus le travail au quotidien. Un développeur qui attend quarante-sept minutes pour apprendre qu'une virgule casse le build finit par ne plus attendre : il enchaîne autre chose, perd le contexte, et revient à froid sur son erreur. Les trois autres lignes sont les conséquences structurelles de ce même monolithe.
| Problème | Impact concret |
|---|---|
| Feedback lent | 47 minutes pour savoir si une faute de frappe casse le build |
| Fragilité | Un changement dans une étape peut casser toutes les autres |
| Pas de parallélisation | Les étapes s'exécutent une par une, même si elles pourraient tourner en parallèle |
| Debugging difficile | Quand ça échoue, il faut chercher dans 500 lignes de configuration |
Comment corriger
Section intitulée « Comment corriger »Découper en pipelines indépendantes avec des responsabilités claires. Chaque pipeline a un seul objectif et peut échouer sans bloquer les autres :
Pipeline CI (5 min) ← Vérifie que le code est correct├── lint ← Vérifie le style du code├── tests unitaires ← Teste les fonctions individuellement└── build ← Compile l'application
Pipeline CD-staging (3 min) ← Déploie pour les tests├── deploy staging ← Met en ligne sur l'environnement de test└── tests e2e ← Exerce l'application via son interface
Pipeline CD-prod (2 min) ← Déploie pour les vrais utilisateurs├── deploy production└── smoke tests ← Vérifie que les fonctions essentielles marchentBénéfice immédiat : si le lint échoue (2 minutes), le développeur le sait tout de suite au lieu d'attendre 47 minutes.
La pipeline non-déterministe
Section intitulée « La pipeline non-déterministe »Une pipeline déterministe donne toujours le même résultat avec les mêmes entrées. Une pipeline non-déterministe introduit de l'aléatoire, et donc de l'incertitude.
Symptôme
Section intitulée « Symptôme »La même pipeline sur le même code donne des résultats différents. La phrase révélatrice : « Relance, ça va passer. »
Run 1: ✓ succèsRun 2: ✗ échec (test timeout) ← Pourquoi maintenant ?Run 3: ✓ succèsRun 4: ✗ échec (dépendance introuvable)Run 5: ✓ succès ← Rien n'a changé pourtant...Causes fréquentes
Section intitulée « Causes fréquentes »Ces quatre causes ont un point commun : elles introduisent une entrée non maîtrisée dans un processus censé être reproductible. Les dépendances non épinglées et les tests flaky représentent à eux deux la majorité des cas rencontrés, et ce sont aussi les plus faciles à corriger. Repérez d'abord laquelle vous frappe en lisant le message d'échec : un « module not found » pointe vers les dépendances, un « timeout » ou un « connection refused » intermittent vers les tests instables ou les ressources partagées.
| Cause | Explication simple |
|---|---|
| Dépendances non épinglées | npm install télécharge la dernière version, qui peut avoir changé depuis hier |
| Tests flaky | Tests instables qui dépendent de l'ordre d'exécution, du timing, ou de données externes |
| Ressources partagées | Plusieurs tests utilisent la même base de données et se marchent dessus |
| Rate limiting | Les API externes (GitHub, npm) bloquent parfois les requêtes trop fréquentes |
Comment corriger
Section intitulée « Comment corriger »Chaque action ci-dessous supprime une source d'aléatoire identifiée juste avant.
La première, épingler les dépendances, est celle qui rapporte le plus vite
et se résume à un seul changement de commande, illustré sous le tableau : passer
de npm install à npm ci. Ne cédez jamais sur la dernière ligne, corriger
les tests flaky : un test instable qu'on relance en boucle n'est pas réparé, il
est seulement caché, et il finira par masquer un vrai défaut.
| Action | Pourquoi ça aide |
|---|---|
| Épingler toutes les dépendances | Le lockfile garantit les mêmes versions à chaque exécution |
| Isoler les tests | Chaque test crée ses propres données, pas de conflit possible |
| Mocker les API externes | Simuler les réponses au lieu d'appeler les vrais services |
| Corriger les tests flaky | Les ignorer ne fait que repousser le problème |
# ❌ Non-déterministe- run: npm install# Problème : télécharge les dernières versions, qui peuvent avoir changé
# ✓ Déterministe- run: npm ci# npm ci utilise exactement les versions du fichier package-lock.jsonLe copier-coller sans gouvernance
Section intitulée « Le copier-coller sans gouvernance »Une pipeline qui fonctionne se duplique de projet en projet, puis diverge au gré des ajustements locaux. Chaque copie devient un exemplaire unique, sans source commune : une correction ne se propage plus, et un défaut recopié se répand avec le modèle.
Symptôme
Section intitulée « Symptôme »Chaque projet a sa propre pipeline, copiée depuis un autre projet, légèrement modifiée. 50 projets = 50 variations incompatibles.
projet-a/.github/workflows/ci.yml (version originale)projet-b/.github/workflows/ci.yml (copié, modifié)projet-c/.github/workflows/ci.yml (copié de B, re-modifié)projet-d/.github/workflows/ci.yml (copié de C, avec les bugs de C !)...Pourquoi c'est problématique
Section intitulée « Pourquoi c'est problématique »La ligne qui fait vraiment mal est la maintenance impossible. Tant que tout va bien, cinquante copies divergentes ne dérangent personne. Le jour où une faille est découverte dans le modèle d'origine, il faut la corriger à la main dans cinquante fichiers, en espérant n'en oublier aucun et ne rien casser au passage. Le drift est le mécanisme silencieux qui rend cette correction si risquée : chaque copie a dérivé de son point de départ, si bien qu'aucun correctif ne s'applique proprement partout.
| Problème | Conséquence concrète |
|---|---|
| Incohérence | Un projet scanne les vulnérabilités, un autre non, sans raison valable |
| Maintenance impossible | Une faille de sécurité dans la pipeline = 50 fichiers à corriger manuellement |
| Drift | Les copies divergent au fil du temps, personne ne sait plus quelle version est « la bonne » |
| Pas de capitalisation | Une équipe améliore sa pipeline, les autres n'en bénéficient pas |
Comment corriger
Section intitulée « Comment corriger »Centraliser la logique dans un workflow réutilisable (workflow_call
sur GitHub Actions) ou un template partagé. Le principe : une seule définition
fait autorité, et chaque projet l'appelle en la référençant par une version. Une
correction apportée à cette définition se propage à tous les projets qui la
référencent, sans copier-coller manuel.
# Avant : 200 lignes de configuration copiées-collées# Après : 5 lignes qui appellent le template centralisé
jobs: ci: uses: org/shared-workflows/.github/workflows/ci.yml@v1 with: language: python python-version: "3.11"Résultat :
- Corriger un bug = modifier un seul fichier
- Ajouter une vérification de sécurité = tous les projets en bénéficient
- Les nouveaux projets démarrent avec les bonnes pratiques
Le rebuild à chaque environnement
Section intitulée « Le rebuild à chaque environnement »Un artefact, c'est le produit fini de votre pipeline : une image Docker, un fichier exécutable, un package. C'est ce qui sera déployé.
Symptôme
Section intitulée « Symptôme »L'artefact est reconstruit pour chaque environnement. La phrase révélatrice : « Ça marchait en staging ! »
staging: npm run build ← Compilation n°1 docker push staging
production: npm run build ← Compilation n°2 (potentiellement différente !) docker push productionPourquoi c'est problématique
Section intitulée « Pourquoi c'est problématique »Tout part de la non-reproductibilité. Reconstruire signifie retélécharger des dépendances, réutiliser un cache différent, tourner sur un runner à l'état distinct : rien ne garantit que la deuxième compilation produise le même binaire que la première. Les bugs fantômes en sont la conséquence directe, et ce sont les plus coûteux à traquer, puisque le « ça marchait en staging » est littéralement vrai. Vous avez testé un artefact, vous en déployez un autre.
| Problème | Exemple concret |
|---|---|
| Non-reproductibilité | L'artefact testé en staging ≠ l'artefact déployé en production |
| Bugs fantômes | Un bug apparaît en production alors que « ça marchait en staging » |
| Temps perdu | Chaque déploiement recompile tout, même si rien n'a changé |
Comment corriger
Section intitulée « Comment corriger »Appliquer le principe « Build once, deploy many » (construire une fois, déployer partout) : un artefact unique, promu d'environnement en environnement.
build: npm run build docker push app:abc123 ← Artefact unique, identifié par son hash
staging: docker pull app:abc123 ← Exactement le même artefact ENV=staging deploy ← Seule la configuration change
production: docker pull app:abc123 ← Toujours le même artefact ENV=production deploy ← Seule la configuration changeGarantie : ce qui a été testé en staging est exactement ce qui tourne en production. Les seules différences sont les variables d'environnement (URLs, clés API, etc.).
Pour aller plus loin : Bonnes pratiques CI/CD
Les tests en fin de chaîne
Section intitulée « Les tests en fin de chaîne »Placer les tests les plus coûteux à la toute fin retarde le signal d'échec jusqu'après le build et le déploiement. Le développeur apprend la régression longtemps après avoir changé de sujet, et la remise en contexte coûte souvent plus cher que la correction elle-même.
Symptôme
Section intitulée « Symptôme »Les tests lourds (tests e2e, « end-to-end », qui exercent l'application de bout en bout à travers son interface) s'exécutent après le build et le déploiement. Le feedback arrive trop tard.
lint (1s) → build (3min) → deploy (2min) → tests e2e (15min) │ └── Échec détecté après 20 minutes !Pourquoi c'est problématique
Section intitulée « Pourquoi c'est problématique »Le fil rouge est le coût de l'attente. Placer les tests lents en dernier n'est pas gênant quand ils passent ; ça le devient quand ils échouent, car tout le travail en amont (build, déploiement) a été consommé pour rien. Le gaspillage de ressources et la perte de contexte se cumulent : vous brûlez des minutes de runner ET vous forcez le développeur à se replonger dans un code qu'il a quitté vingt minutes plus tôt. Un test qui aurait pu échouer en trente secondes échoue après vingt minutes.
| Problème | Impact sur le développeur |
|---|---|
| Feedback tardif | 20 minutes pour découvrir qu'un simple bug casse tout |
| Gaspillage de ressources | Build et deploy exécutés pour rien si les tests échouent |
| Perte de contexte | Le développeur a commencé autre chose, il doit se replonger dans le code |
Comment corriger
Section intitulée « Comment corriger »Appliquer la pyramide des tests : exécuter les tests rapides en premier, les tests lents à la fin.
lint (1s) ──┬── tests unitaires (30s) ──┬── build (3min) │ │ └── Échec rapide ! └── tests e2e (seulement si tout passe)La pyramide des tests visualise cette stratégie :
/\ Tests e2e (lents, après merge) / \ → Peu nombreux, vérifient les parcours critiques /────\ / \ Tests d'intégration (sur PR) /────────\ → Vérifient que les composants fonctionnent ensemble / \ /────────────\ Tests unitaires (à chaque commit) → Nombreux, rapides, vérifient chaque fonctionLogique : les tests du bas (unitaires) sont rapides et nombreux. Si l'un échoue, on le sait en 30 secondes. Pas besoin de lancer les tests lourds.
La dépendance à l'environnement
Section intitulée « La dépendance à l'environnement »Un runner, c'est la machine (physique ou virtuelle) qui exécute votre pipeline. Différents runners peuvent avoir des configurations différentes.
Symptôme
Section intitulée « Symptôme »La pipeline fonctionne sur un runner spécifique mais échoue ailleurs. C'est le fameux « Ça marche sur ma machine », version CI.
Runner A (ubuntu-20.04): ✓ succèsRunner B (ubuntu-22.04): ✗ échec ← Même code, résultat différentRunner C (macos): ✗ échecCauses fréquentes
Section intitulée « Causes fréquentes »Ces quatre causes partagent une racine : la pipeline suppose quelque chose de
l'environnement au lieu de le déclarer. Les versions implicites sont les
plus sournoises, parce qu'elles ne produisent aucune erreur franche : python
lance simplement une version différente, et votre code s'exécute jusqu'au moment
où une incompatibilité subtile fait dérailler un test. Le réflexe à prendre en
lisant ce tableau : chaque ligne correspond à une chose que vous croyiez
acquise et qui varie d'un runner à l'autre.
| Cause | Exemple concret |
|---|---|
| Outils non installés | La pipeline utilise jq qui est installé sur un runner mais pas les autres |
| Versions implicites | python appelle Python 3.8 sur un runner, Python 3.11 sur un autre |
| Chemins en dur | /home/runner/specific/path n'existe pas sur tous les runners |
| Variables d'environnement | Une variable est configurée sur un runner, absente sur les autres |
Comment corriger
Section intitulée « Comment corriger »| Solution | Pourquoi ça fonctionne |
|---|---|
| Conteneuriser | L'image Docker embarque tous les outils nécessaires, identiques partout |
| Expliciter les versions | python-version: "3.11" garantit la même version sur tous les runners |
| Éviter les chemins absolus | $GITHUB_WORKSPACE s'adapte automatiquement au runner |
| Installer les prérequis | Si un outil est nécessaire, l'installer explicitement dans la pipeline |
# ✓ Explicite et portablepermissions: {} # ← Aucun droit par défaut au niveau workflowjobs: test: runs-on: ubuntu-24.04 permissions: contents: read # ← Le strict nécessaire pour ce job container: python:3.11-slim # ← Environnement contrôlé steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: pip install -r requirements.txtRègle d'or : si quelque chose n'est pas explicitement défini dans la pipeline, ça peut changer sans prévenir.
Les exceptions permanentes
Section intitulée « Les exceptions permanentes »Une dérogation accordée pour débloquer une livraison n'a presque jamais de date de fin. Faute d'échéance et de réexamen, elle survit à l'urgence qui l'a justifiée, et le périmètre réellement contrôlé se réduit sans que personne ne l'ait décidé.
Symptôme
Section intitulée « Symptôme »Des projets sont exemptés des contrôles de sécurité « temporairement ». Deux ans plus tard, les exceptions sont toujours là, et personne ne sait si elles sont encore justifiées.
Exceptions au scan de sécurité:- projet-legacy (temporaire, ajouté en 2022) ← Toujours là- projet-urgence (temporaire, ajouté en 2023) ← Toujours là- projet-client-X (temporaire, ajouté en 2023) ← Toujours là- ...Pourquoi c'est problématique
Section intitulée « Pourquoi c'est problématique »Le danger le plus perfide est la fausse sécurité. Un tableau de bord tout vert donne l'illusion que la chaîne est saine, alors que les projets exemptés, invisibles dans ces indicateurs, sont justement ceux qui échappent au contrôle. La dette invisible aggrave le tout : sans inventaire des exceptions ni de leur justification, plus personne ne sait quel est le périmètre réellement protégé. Vous croyez couvrir cent pour cent du parc, vous en couvrez peut-être soixante.
| Problème | Risque concret |
|---|---|
| Fausse sécurité | Les tableaux de bord sont verts, mais des projets vulnérables passent sous le radar |
| Précédent dangereux | « Eux ont une exception, pourquoi pas nous ? », les exceptions se multiplient |
| Dette invisible | Personne ne sait combien de projets sont exemptés ni pourquoi |
Comment corriger
Section intitulée « Comment corriger »Le principe directeur : une exception doit avoir une fin programmée, sinon elle est permanente par défaut. La date d'expiration automatique est la mesure la plus efficace, parce qu'elle inverse la charge de la preuve. Au lieu d'exiger que quelqu'un pense à supprimer l'exception, on force son détenteur à la renouveler explicitement, justification à l'appui. Les trois autres mesures organisent la traçabilité autour de ce mécanisme et évitent le bypass total quand un simple filtrage ciblé suffit.
| Mesure | Comment l'appliquer |
|---|---|
| Date d'expiration | Chaque exception expire automatiquement après 90 jours |
| Justification documentée | « Pourquoi cette exception ? Quel est le plan pour la supprimer ? » |
| Revue régulière | Audit trimestriel : les exceptions non renouvelées sont supprimées |
| Alternative au bypass total | Plutôt qu'ignorer le scan complet, ignorer uniquement la vulnérabilité spécifique |
La pipeline SPOF
Section intitulée « La pipeline SPOF »SPOF signifie « Single Point of Failure » (point de défaillance unique). C'est un élément dont la panne bloque tout le système.
Symptôme
Section intitulée « Symptôme »Une seule pipeline fait tout, sur un seul runner, avec un seul maintainer. Si l'un de ces éléments tombe, tout s'arrête.
Comment corriger
Section intitulée « Comment corriger »Un SPOF n'est pas seulement technique. Le tableau distingue volontairement trois natures de point unique, et la plus souvent oubliée est le maintainer unique : une pipeline que personne d'autre ne comprend tombe en panne dès que son auteur part en congé ou change d'équipe. La parade est symétrique pour les trois lignes, introduire de la redondance, qu'il s'agisse de machines, de personnes formées ou de canaux d'alerte.
| SPOF identifié | Solution |
|---|---|
| Runner unique | Créer un pool de runners (au moins 2-3) |
| Maintainer unique | Documenter la pipeline, former au moins une autre personne |
| Pas d'alertes | Configurer des notifications quand le runner est indisponible |
Checklist d'audit
Section intitulée « Checklist d'audit »Utilisez cette liste pour évaluer la santé de vos pipelines. Elle reprend un anti-pattern par ligne, dans l'ordre du guide. Ne cherchez pas à tout cocher du premier coup : traitez d'abord le feedback rapide et le déterminisme, ce sont les deux points qui débloquent le plus de temps pour l'équipe. Les autres se corrigent ensuite, une fois que les développeurs ont retrouvé un cycle de retour court et fiable.
- Feedback rapide : Durée < 15 min pour le premier retour
- Déterminisme : Aucun « relance, ça va passer »
- Gouvernance : Templates centralisés (pas de copier-coller)
- Build once : Artefact construit une seule fois
- Pyramide des tests : Tests rapides exécutés en premier
- Exceptions traçables : Pas d'exception sans date d'expiration
- Pas de SPOF : Plus d'une personne connaît la pipeline
À retenir
Section intitulée « À retenir »- Un anti-pattern est une solution qui crée plus de problèmes qu'elle n'en résout
- La pipeline monolithe ralentit le feedback, découpez en pipelines indépendantes
- Une pipeline non-déterministe érode la confiance, épinglez les dépendances
- Le copier-coller multiplie la maintenance, centralisez dans des templates
- Build once, deploy many, un artefact unique pour tous les environnements
- Les tests rapides d'abord, échec en 30 secondes plutôt qu'en 20 minutes
- Les exceptions temporaires deviennent permanentes, ajoutez des dates d'expiration
- Éliminez les SPOF, redondance des runners et des connaissances
Liens utiles
Section intitulée « Liens utiles »- Bonnes pratiques CI/CD, Les invariants qui marchent
- Pourquoi les pipelines échouent-elles ?, Causes profondes organisationnelles
- Anatomie d'une pipeline, Comprendre pour mieux corriger