En 2017, j'étais en mission sur une plateforme de gestion de commandes B2B. Vendredi soir, 18h, un bug critique en production : des lignes de commande se dupliquaient silencieusement depuis deux semaines. Le client avait facturé en double sans s'en apercevoir. La cause ? Un async void dans un event handler — une exception avalée sans trace, un comportement non déterministe que nos tests d'intégration ne couvraient pas parce que nous testions les cas nominaux, pas les chemins d'erreur asynchrones.

Nous avons corrigé le bug en 40 minutes. Puis j'ai fait quelque chose que je n'avais pas encore fait systématiquement : j'ai ouvert un fichier texte et j'ai écrit deux lignes. "Ce qui a merdé : async void dans un event handler. Ce qui l'aurait prévenu : une règle Roslyn + un test d'intégration sur les chemins d'erreur." Ce fichier est devenu, au fil des années, ce que j'appelle ma kill-list. C'est le point de départ de tout ce que je vais vous expliquer ici.

Ce que le kaizen signifie vraiment — et ce qu'il ne signifie pas

Le terme kaizen est souvent traduit par "amélioration continue", ce qui est tellement vague que ça ne veut rien dire. Tout le monde "cherche à s'améliorer". Personne ne se présente en disant l'inverse. La plupart des personnes qui utilisent ce mot pensent à une posture mentale — une bonne volonté de progresser. Ce n'est pas suffisant.

Le kaizen tel que je le pratique est une procédure, pas une intention. Elle tient en trois étapes, à exécuter après chaque sprint, chaque feature significative, chaque incident :

  1. Identifier un problème concret qui s'est produit — pas une liste, pas une analyse exhaustive. Un.
  2. Identifier sa cause racine — pas le symptôme, pas "le développeur a fait une erreur". La contrainte structurelle qui a permis à cette erreur d'exister.
  3. Définir une action préventive mesurable — une règle d'analyse statique, un test de convention, un ADR, une clause dans CLAUDE.md. Quelque chose de vérifiable.

La clé du troisième point est là : l'action préventive doit être structurelle. Elle ne doit pas dépendre de la vigilance humaine au prochain cycle. Si la prévention repose sur "je ferai attention la prochaine fois", c'est de l'espoir, pas du kaizen.

Trois exemples tirés de la production

Le N+1 EF Core qui est devenu un test de convention

Sur un projet de portail RH, une requête de liste de collaborateurs chargeait 200 lignes avec leurs contrats et leurs affectations. En développement, sur la base de recette avec 50 enregistrements, tout allait bien. En production, avec 8 000 collaborateurs actifs, la page mettait 12 secondes. La cause : EF Core chargeait les Contracts et Assignments en navigation lazy, générant 401 requêtes SQL pour une seule page.

La correction était triviale — ajouter .Include(). Mais la leçon, elle, a été transformée en test de convention :

// Convention test — lancé à chaque CI
[Fact]
public void AllQueryHandlers_ShouldNotHaveLazyNavigationAccess()
{
    var handlerTypes = typeof(ApplicationAssembly).Assembly
        .GetTypes()
        .Where(t => t.IsAssignableTo(typeof(IQueryHandler)));

    foreach(var handler in handlerTypes)
    {
        // Vérifie qu'aucun handler ne déclare de propriété
        // de navigation sans projection explicite via un DTO
        handler.Should().NotHaveVirtualNavigationProperties(
            because: "lazy loading is banned — use explicit .Include() or projections");
    }
}

Ce test ne teste pas le comportement d'EF Core. Il teste que nous n'introduisons pas de navigation lazy par inadvertance. Aujourd'hui, trois projets différents héritent de ce test. Le bug ne peut plus se reproduire sans que le CI le signale immédiatement.

L'async void qui est devenu un hook Roslyn

Le bug du vendredi soir de 2017. La correction immédiate était simple. L'action kaizen, elle, a pris deux formes. D'abord, une règle dans l'.editorconfig qui active l'analyseur AsyncFixer01 en erreur (pas en warning — en erreur, le build refuse de passer). Ensuite, une ligne dans le CLAUDE.md de tous mes projets depuis :

## Async — règles absolues
- async void = interdit, ZERO exception
- Event handlers async : utiliser async Task + fire-and-forget explicite
- Toute exception dans un Task non-awaited doit être loggée

Depuis 2018, je n'ai plus rencontré un seul bug lié à async void en production. Non pas parce que je suis plus vigilant — parce que la structure ne le permet plus.

La collision de nommage qui a généré un ADR

Sur une architecture microservices, deux équipes avaient nommé OrderCreatedEvent des events avec des sémantiques complètement différentes : l'une signifiait "une commande a été créée dans le système", l'autre "une commande a été créée et validée, prête à être traitée". Quand les services ont été intégrés, les consommateurs recevaient des events qu'ils ne savaient pas interpréter. Le débogage a pris deux jours.

La leçon est devenue un ADR (Architectural Decision Record) : tous les domain events doivent encoder leur état terminal dans leur nom (OrderDraftCreated vs OrderConfirmed) et tout nouveau event doit être validé en peer review contre le glossaire de domaine. Ce n'est pas une règle de code — c'est une décision d'équipe documentée, versionnée, consultable. Le problème ne peut plus surgir sans que quelqu'un signale l'écart.

La mathématique de la capitalisation

Voici le calcul que je montre parfois à des CTOs sceptiques sur l'investissement en qualité et en processus. Si chaque itération — sprint, feature, incident — vous rend 0,5% meilleur sur un axe précis (détection d'erreurs, vélocité de déploiement, fiabilité), qu'est-ce que ça donne sur un an de sprints bi-hebdomadaires ?

// 104 sprints de 2 semaines / an, gain de 0.5% par sprint
(1 + 0.005)^104 ≈ 1.68    // +68% en un an

// Sur 3 ans (156 sprints de 3 semaines) :
(1 + 0.005)^208 ≈ 2.84    // presque 3x

Ce n'est pas de la motivation — c'est de l'arithmétique. L'effet est réel parce que chaque amélioration s'applique à toutes les itérations suivantes, pas seulement à la prochaine. Une suite de tests qui attrape 15% des régressions supplémentaires aujourd'hui protège toutes les features des 3 prochaines années. Un CLAUDE.md qui empêche un pattern d'erreur agit sur toutes les sessions de travail futures.

En pratique, je le vois dans mes métriques de projet : la densité de bugs par feature livrée baisse d'année en année, pas à cause de la vigilance — à cause de l'accumulation structurelle de garde-fous. Les suites de tests attrapent plus de régressions qu'il y a trois ans, non pas parce qu'il y en a plus, mais parce que les tests ont appris des bugs passés. La kill-list grossit. Le CLAUDE.md s'affine. Les ADRs empêchent les décisions déjà prises d'être reposées.

"L'expérience ne se capitalise pas automatiquement. Elle se capitalise seulement quand vous la rendez structurelle — quand une leçon apprise ne peut plus être oubliée parce qu'elle est encodée quelque part que le système vérifie."

La hiérarchie des feedback loops

Le second pilier de ma philosophie est la tightening des boucles de feedback — le raccourcissement systématique du délai entre "je fais quelque chose" et "je sais si c'était correct". Voici la hiérarchie telle que je la conçois :

┌─────────────────────────────────────────────────────────────┐
│              HIÉRARCHIE DES FEEDBACK LOOPS                  │
│                                                             │
│  Compilateur          < 1 seconde       coût de fix : 1x   │
│  Test unitaire        < 30 secondes     coût de fix : 3x   │
│  Test d'intégration   < 5 minutes       coût de fix : 10x  │
│  Pipeline CI          < 15 minutes      coût de fix : 30x  │
│  Revue de code        < 24 heures       coût de fix : 100x │
│  Production           semaines-mois     coût de fix : 500x │
│                                                             │
│  Règle : chaque niveau vers le haut = 3-10x plus cher.     │
│  Objectif : déplacer les détections vers le bas.           │
└─────────────────────────────────────────────────────────────┘

Ces chiffres ne sont pas des études académiques — ils représentent ce que j'observe en mission depuis 15 ans. Un bug trouvé par le compilateur se corrige en recompilant. Le même bug trouvé en revue de code implique un commentaire, une correction, une nouvelle PR, un second passage de revue. En production, il implique un hotfix, un déploiement d'urgence, une communication client, un post-mortem.

Ce que cela signifie concrètement : toute la philosophie TDD, Clean Architecture, CI/CD n'est pas une idéologie — c'est une réponse rationnelle à ce tableau. TDD déplace la détection vers les tests unitaires plutôt que la revue de code. Les tests de convention déplacent la détection des violations architecturales vers le CI plutôt que la production. L'analyse statique déplace les erreurs courantes vers le compilateur. Chaque déplacement vers le bas de cette hiérarchie est une décision économique, pas esthétique.

Claude Code comme outil de déplacement vers la gauche

Quand j'explique pourquoi j'utilise Claude Code à des développeurs ou des CTOs, je ne dis pas "ça écrit du code plus vite". La plupart des développeurs seniors n'ont pas de problème de vitesse d'écriture — ils ont un problème de surface à couvrir. Ce que Claude Code fait réellement, dans mon workflow, c'est déplacer des catégories d'erreurs vers le bas de la hiérarchie des feedback loops.

Exemple concret : avant de soumettre une PR, je soumets mes modifications à Claude Code avec une instruction précise. Il identifie les violations SOLID que j'aurais peut-être laissé passer sous la pression du sprint, les cas de test manquants, les patterns à risque (double write sans transaction, appel HTTP sans timeout, absence de circuit breaker). Ce travail serait normalement fait en revue de code — 24 heures plus tard, avec un collègue qui a moins de contexte que moi sur le code que je viens d'écrire.

Ce déplacement de "revue de code" à "avant la PR" n'est pas un détail. Sur la hiérarchie ci-dessus, c'est passer de 100x à 30x. Et parce que le feedback est immédiat et contextuel, les corrections se font naturellement — pas dans un commentaire de PR de 48h que vous devez réinterpréter à froid.

Le vrai bénéfice n'est pas la vitesse — c'est la compression de la boucle de rétroaction sur la qualité architecturale. Et cette compression, c'est du kaizen appliqué à l'outillage.

Ce qui ne s'améliore pas automatiquement avec le temps

Je veux être précis sur ce point parce que c'est l'endroit où la plupart des développeurs se trompent. Il n'est pas vrai que 15 ans d'expérience équivalent automatiquement à 15 ans de capitalisation. J'ai rencontré des développeurs avec 15 ans de carrière qui réintroduisaient les mêmes patterns d'erreur chaque année, parce qu'ils n'avaient aucun système pour capturer ce qu'ils avaient appris.

L'expérience non capturée a une durée de vie de 48 à 72 heures. Après un incident, l'analyse est fraîche, les causes sont claires, les solutions sont évidentes. Une semaine plus tard, sous la pression des tickets, le souvenir s'est compressé en "on a eu un bug weird avec l'async". Six mois plus tard, il n'en reste rien d'opérationnel.

Ce qui capitalise réellement, c'est ce qui est écrit, versionné, et consulté. Un ADR écrit et jamais relu ne vaut rien. Un fichier learned-errors.md jamais ouvert n'existe pas fonctionnellement. Le système n'est pas le fichier — c'est la discipline de lecture et de mise à jour.

Voici à quoi ressemble une entrée réelle dans mon fichier de leçons capturées :

| Date       | Pattern              | Ce qui s'est passé               | Prévention structurelle          | Statut  |
|------------|----------------------|----------------------------------|----------------------------------|---------|
| 2024-03-12 | async void handler   | Exception avalée, bug silencieux | AsyncFixer01 en erreur + CLAUDE.md| résolu |
| 2024-07-08 | EF N+1 lazy nav      | 400 requêtes sur liste prod      | Convention test + .editorconfig  | résolu  |
| 2024-11-22 | event naming clash   | Sémantiques conflictuelles       | ADR + glossaire de domaine       | résolu  |
| 2025-02-14 | double-write sans tx | Données incohérentes sur timeout | Rule Roslyn + test d'intégration | résolu  |
| 2025-09-03 | HTTP sans timeout    | Cascade de timeouts en prod      | HttpClient factory + convention  | résolu  |

Ce tableau a aujourd'hui plus de 60 entrées. Chacune représente un bug ou un problème qui ne peut plus se reproduire dans mes projets sans que quelque chose de structurel l'attrape. C'est ça, la capitalisation réelle de l'expérience.

Les outils qui rendent le kaizen structurel

Je ne veux pas que ce soit une liste d'outils — ce serait passer à côté du sujet. Ce qui importe n'est pas l'outil, c'est la fonction qu'il remplit. Trois fonctions sont indispensables :

La mémoire des décisions — les ADRs

Un ADR n'est pas de la documentation — c'est un journal de décision daté, avec son contexte, ses alternatives rejetées et ses conséquences anticipées. Sa valeur principale n'est pas d'expliquer quoi a été décidé, mais pourquoi cette décision a été prise plutôt qu'une autre. Un an plus tard, quand un nouveau développeur s'interroge sur l'architecture events vs appels synchrones, l'ADR lui donne la réponse sans qu'il ait besoin de reconstituer le contexte de décision — et sans qu'il rouvre le débat pour de mauvaises raisons.

La mémoire des erreurs — la kill-list et learned-errors

C'est la colonne vertébrale du kaizen. Chaque entrée suit le même format : date, pattern, ce qui s'est passé, prévention structurelle mise en place, statut. L'objectif n'est pas de se souvenir des bugs — c'est de transformer chaque bug en garde-fou. Une entrée sans "prévention structurelle" est une entrée incomplète.

La mémoire des contraintes — CLAUDE.md

CLAUDE.md n'est pas un fichier de configuration — c'est un contrat de travail qui grossit avec l'expérience accumulée. Chaque règle qu'il contient a une origine : une erreur passée, une décision d'équipe, un pattern qui s'est révélé fragile. Sa valeur est proportionnelle à sa précision. "Écrire du bon code" n'est pas une règle. "Si la prévention repose sur la vigilance humaine, elle n'est pas dans CLAUDE.md — elle est dans un test ou un analyseur" est une règle.

## Code style (non-négociable)
# Chaque ligne ici représente une erreur passée transformée en contrainte
- if( pas if ( — détecté par un Sonar S1116 ignoré en 2023
- async void interdit — incident prod 2017, AsyncFixer01 en erreur
- const/static readonly → PascalCase — confusion de nommage sur 2 projets
- NEVER add Co-Authored-By — information non pertinente pour le repo client

## Tests — les leçons capitalisées
- Code-before-tests = delete (violation constatée chez 3 équipes différentes)
- Tests de convention dans /Architecture/ — chaque pattern récurrent y va
- Aucun mock par défaut sur les tests d'intégration — les stubs cachent les N+1

Ce que cela change pour les équipes que j'accompagne

Quand j'arrive en mission Lead ou en renfort sur une équipe .NET, je n'apporte pas une liste de bonnes pratiques à appliquer. J'apporte un système. La différence est importante : une liste de bonnes pratiques requiert de la discipline individuelle et s'érode sous la pression du delivery. Un système — tests de convention, ADRs, règles d'analyse statique, CLAUDE.md partagé — s'applique mécaniquement, sans dépendre de l'humeur de l'équipe ou de la pression du sprint.

Les premières semaines d'une mission, j'observe. Je note les bugs récurrents, les patterns qui font grimacer les développeurs en revue de code, les décisions prises et reprises sans mémoire. Puis je transforme chaque pattern observé en contrainte structurelle. Non pas parce que l'équipe ne sait pas ce qu'il faut faire — la plupart du temps, ils le savent parfaitement. Mais parce que savoir n'est pas suffisant sous la pression du delivery. Les règles doivent être dans le code, pas dans les têtes.

Ce n'est pas une posture de donneur de leçons — c'est une position pragmatique. Le meilleur développeur du monde introduira des régressions s'il travaille sans filets. Mon travail est de construire les filets, pas de surveiller les développeurs.

"Les meilleures équipes que j'ai vues ne planifiaient pas mieux que les autres. Elles apprenaient plus vite — et elles avaient des systèmes pour ne pas oublier ce qu'elles avaient appris."

Vous voulez introduire cette philosophie dans votre équipe .NET ?

Je peux auditer vos pratiques actuelles, identifier les patterns récurrents et mettre en place les mécanismes de capitalisation — tests de convention, ADRs, règles d'analyse statique. En mission ou en accompagnement ponctuel.

✉ Me contacter Voir mes services →