Dette technique: le choix radical d'un CTO chez une startup
La différence est comptable: après la fin de support, chaque vulnérabilité corrigée dans les branches maintenues devient potentiellement une exposition non corrigeable sans migration.
C’est là que la priorisation d’un backlog technique sur un projet PHP complexe cesse d’être une discussion abstraite entre CTO, Product Owner et équipe de développement. Une dette peut ralentir la livraison. Une branche PHP en fin de vie, une dépendance vulnérable ou une Definition of Done incomplète peuvent aussi invalider la livraison elle-même.
Le choix radical d’un CTO n’est donc pas forcément une réécriture. Le vrai choix radical consiste à traiter le passif technique comme du travail produit ordonné, estimé et livrable — pas comme une liste de tickets que l’on repousse jusqu’à l’incident.
La dette technique n’est pas synonyme de mauvais code
Un projet Symfony avec 180 contrôleurs, des services surchargés, des DTO absents et une couverture de tests lacunaire peut fonctionner correctement en production. Pendant un temps. La dette n’est pas mesurée par l’élégance d’un repository ou par le nombre de commentaires TODO. Elle se mesure par les contraintes que le système impose à chaque modification.
Le symptôme observable n’est pas « le code est sale ». C’est plutôt celui-ci:
- une modification de règle métier nécessite de toucher simultanément un contrôleur, un listener Doctrine, un service applicatif et une commande asynchrone;
- les tests d’intégration exigent une base de données complète et rendent le cycle CI trop lent pour soutenir le flux de pull requests;
- une dépendance Composer ne peut pas évoluer parce qu’elle entraîne une rupture de compatibilité en cascade;
- une montée de version PHP bloque sur des types implicites, des APIs dépréciées ou des extensions non compatibles;
- les équipes ne savent plus si une migration Doctrine est idempotente, rejouable ou dépendante de l’état exact d’un environnement.
Cette dette n’est pas nécessairement le résultat d’une faute. La grille de Martin Fowler reste utile parce qu’elle évite le jugement moral: une dette peut être prudente ou imprudente, délibérée ou involontaire. Une startup peut choisir un couplage temporaire pour livrer un flux de paiement, puis découvrir six mois plus tard que le modèle de facturation réel ne correspond plus aux hypothèses initiales. Le code devient inadapté après apprentissage du domaine. Ce n’est pas un échec de compétence. C’est une information nouvelle qui arrive dans une architecture ancienne.
Le problème commence quand cette information ne modifie pas l’ordre du backlog.
Une dette technique devient critique lorsqu’elle transforme une évolution ordinaire en opération à risque.
Pour un CTO, l’enjeu n’est pas de classer les anomalies entre « métier » et « technique ». Cette frontière est souvent artificielle. Un endpoint qui explose sa consommation mémoire sous charge est un sujet produit. Un cache incohérent qui affiche un tarif obsolète est un sujet produit. Un worker Messenger non idempotent qui duplique une facture est un sujet produit avec une cause technique.
La bonne unité de décision n’est donc pas la nature du ticket. C’est le coût du report.
Le Product Backlog doit absorber le passif, pas le masquer
Scrum est explicite: le Product Backlog est une liste émergente et ordonnée de tout ce qui est nécessaire pour améliorer le produit. Il est la source unique du travail de la Scrum Team. « Source unique » exclut mécaniquement le tableur technique tenu par le CTO, les notes de dette dans Notion et les alertes de sécurité perdues dans un canal Slack.
Dans une gestion de backlog agile Symfony, le défaut classique consiste à créer deux files séparées:
1. le backlog métier, visible, priorisé, lié aux objectifs commerciaux;
2. le backlog technique, invisible, rarement estimé, traité entre deux sprints.
Ce découpage produit une illusion de vélocité. Le produit semble livrer vite parce que le coût de fiabilisation est sorti du système de mesure. Puis les incidents, les régressions et les migrations urgentes reviennent dans le flux sous une forme plus coûteuse. Ils ne disparaissent pas. Ils changent seulement de libellé.
Le raffinement est le point de contrôle. C’est là qu’un élément flou devient une unité de travail exploitable: description, ordre, taille, dépendances, critères d’acceptation et effets de bord. Les développeurs qui feront le travail l’estiment. Le Product Owner aide à arbitrer la valeur et les compromis. Un CTO qui injecte une priorité sans rendre le risque observable fabrique de la résistance. Un Product Owner qui écarte un sujet technique sans en chiffrer l’impact fabrique de la dette cachée.
Une fiche de backlog technique utilisable doit répondre à des questions concrètes:
- Quel flux de production est affecté: commande, authentification, synchronisation, facturation, recherche?
- Quelle contrainte actuelle bloque: CPU, I/O, allocation mémoire, verrouillage SQL, typage faible, dépendance obsolète, absence de test?
- Quel est le mode de défaillance: indisponibilité, corruption silencieuse, exposition de sécurité, délai de livraison, dette de support?
- Quelle modification devient possible après traitement?
- Quel signal permettra de vérifier que le travail a produit son effet: durée CI, taux d’échec, p95, mémoire maximale, nombre de modules concernés, vulnérabilités ouvertes?
L’ordre du Product Backlog n’est pas une estimation. Ce n’est pas non plus une promesse de date. C’est une séquence d’arbitrage. Une migration PHP coûteuse peut être placée avant une feature rentable si le coût de son report est structurellement supérieur: fenêtre de support qui se ferme, incompatibilité d’une dépendance critique, exposition sécurité ou saturation d’un composant.
| Situation observée | Dette réelle | Traitement dans le backlog | Signal de sortie |
|---|---|---|---|
| PHP 8.2 encore en production | Fin de fenêtre de sécurité proche | Epic de compatibilité, découpé par domaine et dépendance | Déploiement validé sur une branche supportée |
| CI Symfony instable | Détection tardive des régressions | Isolation des tests, fiabilisation des fixtures, parallélisation contrôlée | Exécutions reproductibles et échecs exploitables |
| Service applicatif monolithique | Couplage qui rend chaque changement transversal | Extraction progressive derrière interfaces et tests de caractérisation | Modification locale sans régression latérale |
| Dépendances Composer non gouvernées | Surface d’attaque et compatibilité inconnues | Mise à jour, verrouillage, analyse des changements indirects | Aucune alerte bloquante à la fusion |
| Migrations Doctrine opaques | Risque de déploiement et de rollback | Normalisation des migrations et répétition sur environnement représentatif | Migration exécutable dans le pipeline de livraison |
Le conflit entre priorité métier et technique est mal posé lorsqu’il se limite à « livrer maintenant » contre « nettoyer avant ». La vraie question est: quelle capacité de livraison conserve-t-on après cette décision?
La Definition of Done élimine une partie de la fausse dette
Une équipe PHP accumule parfois de la dette parce que son incrément n’est pas réellement terminé. Le ticket est déplacé en colonne « terminé » dès que le comportement visible existe. Les tests, la migration, l’observabilité, les contrôles de sécurité et les cas limites sont reportés. Le travail non fini se déplace alors dans le futur sous le nom de dette.
La Definition of Done sert précisément à empêcher ce transfert comptable. Dans Scrum, un élément qui ne satisfait pas les mesures de qualité requises ne peut ni être livré ni être présenté à la Sprint Review. Il retourne dans le Product Backlog.
Sur un projet PHP complexe, une DoD utile ne doit pas être un manifeste de vingt pages. Elle doit contenir les contraintes qui empêchent un incrément de se comporter comme une dette fraîchement créée:
- le code respecte une convention homogène, avec PSR-1 et PSR-12 comme socle de lisibilité;
- les frontières applicatives sont testées au niveau adapté: unitaire pour la logique isolée, intégration pour les adapters, fonctionnel pour les flux HTTP critiques;
- les migrations de schéma sont exécutées sur une base représentative, avec estimation de leur coût et de leurs verrous;
- les messages asynchrones critiques disposent d’une stratégie d’idempotence, de retry et de traitement des échecs;
- les dépendances modifiées sont analysées, y compris celles transitivement modifiées dans le fichier de verrouillage;
- les métriques nécessaires au diagnostic existent avant la mise en production: logs structurés, traces, métriques de latence ou de consommation selon le flux.
PSR-12 n’accélère pas un endpoint. Ce n’est pas sa fonction. Son intérêt est plus froid: réduire la friction cognitive quand plusieurs auteurs interviennent sur le même code PHP. Dans un monolithe qui a traversé trois équipes et quatre années de produit, cette réduction est mesurable par le temps de revue, la capacité à localiser un effet de bord et le coût d’onboarding technique.
Si plusieurs équipes Scrum travaillent sur le même produit, elles doivent partager une même Definition of Done. Sinon, une équipe livre un module testé, instrumenté et compatible avec la cible PHP; une autre livre du code exécutable mais sans garantie opérationnelle. Le produit final prend alors la qualité de son maillon le plus faible.
Une fonctionnalité sans conditions de sortie techniques n’est pas livrée: elle est seulement déplacée.
Le cycle de support PHP est une donnée de roadmap
La modernisation PHP échoue souvent parce qu’elle est lancée sous la forme d’un projet massif: « passer à la dernière version ». Cette formulation est faible. Elle ne dit rien sur les extensions, Composer, les frameworks, les règles de typage, les images de conteneurs, les workers, les scripts de maintenance ni les comportements implicites modifiés par le runtime.
Le cycle PHP donne pourtant une contrainte claire. Chaque branche reçoit deux ans de support actif, puis deux ans de correctifs limités aux failles critiques. Après quatre ans, elle atteint sa fin de vie.
Au 17 juillet 2026, les branches soutenues sont PHP 8.2, 8.3, 8.4 et 8.5. Mais elles ne portent pas le même risque:
| Branche PHP | Support sécurité jusqu’au | Lecture stratégique |
|---|---|---|
| PHP 8.2 | 31 décembre 2026 | Migration à planifier et engager immédiatement |
| PHP 8.3 | 31 décembre 2027 | Fenêtre exploitable, mais non confortable pour un SI complexe |
| PHP 8.4 | 31 décembre 2028 | Cible stable pour une modernisation progressive |
| PHP 8.5 | 31 décembre 2029 | Cible envisageable si l’écosystème applicatif est compatible |
Le CTO ne doit pas décider seulement de la branche cible. Il doit ordonner les incompatibilités. Un audit efficace commence par le chemin d’exécution réel: front HTTP, workers Messenger, commandes planifiées, importateurs, batchs de clôture, processus de génération documentaire. Les scripts oubliés sont souvent ceux qui cassent après la mise à niveau parce qu’ils vivent hors du trafic principal et hors des tests.
La priorisation backlog technique projet PHP complexe devient alors beaucoup plus précise. On ne crée pas un ticket « migration PHP 8.4 ». On crée des éléments traçables:
1. Cartographier les contraintes Composer et les extensions PHP requises dans chaque environnement.
2. Exécuter les suites de tests sur la cible, puis isoler les dépréciations et ruptures par paquet ou domaine.
3. Traiter d’abord les chemins à forte criticité opérationnelle: paiement, identité, données personnelles, synchronisation.
4. Mettre à jour les images de build, les environnements CI et les workers avant de déclarer la migration complète.
5. Déployer de manière progressive avec métriques comparables: erreurs, p95, mémoire, nombre de jobs échoués, volume de retries.
La branche PHP n’est pas un détail d’infrastructure. Elle conditionne le niveau de risque acceptable pour le produit. Repousser la migration jusqu’à la fin du support transforme un programme de modernisation en intervention sous contrainte.
Sécurité: la dette qui ne négocie pas avec la roadmap
Le cadre SSDF du NIST ne demande pas une sécurité ajoutée après coup. Il recommande d’intégrer les pratiques de développement sécurisé dans chaque cycle existant afin de réduire les vulnérabilités livrées, de limiter l’impact de celles qui ne sont pas détectées et de traiter leurs causes racines.
Pour une équipe PHP, cette orientation a une conséquence directe: une vulnérabilité connue dans une dépendance ne relève pas d’un « lot sécurité » optionnel. C’est une condition de livraison. La dépendance indirecte compte autant que la dépendance déclarée explicitement. Composer verrouille un graphe. Le risque est dans le graphe, pas uniquement dans le premier niveau.
Un contrôle de revue de dépendances à chaque pull request permet d’identifier les modifications de packages et les vulnérabilités connues, y compris dans les fichiers de verrouillage. S’il est requis, il peut bloquer la fusion. C’est une bonne friction. Elle coûte quelques minutes au bon endroit plutôt que plusieurs jours d’investigation après déploiement.
Il faut néanmoins éviter le réflexe simpliste: « mettre à jour toutes les dépendances ». Une mise à jour sans stratégie peut déplacer le risque vers une rupture fonctionnelle, un changement de sérialisation, une modification de comportement HTTP ou une régression de performance. Le backlog doit rendre le compromis visible: vulnérabilité, version corrigée, compatibilité applicative, scénario de régression, fenêtre de déploiement.
Les indicateurs de santé du code PHP ne se résument pas à un score de quality gate. Les plus utiles relient une propriété technique à un effet de production:
- durée médiane et percentile élevé du pipeline CI;
- taux de flakiness des tests;
- temps de restauration après incident sur un composant;
- densité de dépendances obsolètes ou vulnérables;
- proportion de flux critiques dotés de tests de non-régression;
- consommation mémoire et latence sur les endpoints ou workers à volume élevé;
- âge de la branche PHP et distance à sa fin de support;
- taux d’échec des jobs asynchrones, retries et messages en échec.
Aucune de ces métriques ne produit une priorité automatique. Elles fournissent un signal. La décision reste architecturale: quel composant, quel risque, quelle séquence, quel coût de report.
Refactoriser ou réécrire: le test de la frontière
La réécriture totale est le fantasme récurrent de la dette technique. Elle promet une base saine, un typage strict, une architecture hexagonale nette, une version PHP récente et l’élimination des compromis historiques. Elle promet beaucoup parce qu’elle permet de ne plus regarder le système actuel.
En pratique, une réécriture remplace un passif connu par un risque de livraison moins connu. Les comportements implicites, les correctifs accumulés, les règles métier mal documentées et les exceptions de production ne disparaissent pas. Ils doivent être redécouverts. Parfois, ils sont oubliés.
Le bon critère n’est pas l’âge du code. C’est l’existence de frontières exploitables.
La refactorisation incrémentale est adaptée lorsque le système possède encore des zones isolables: un module de facturation, un adapter de paiement, un moteur de recherche, une couche d’intégration ERP. On peut poser des tests de caractérisation, encapsuler l’existant, extraire progressivement et mesurer la réduction du couplage.
La réécriture devient défendable lorsque l’architecture interdit durablement ces mouvements: modèle de données irréconciliable avec les contraintes actuelles, runtime non maintenable, dépendances fondamentales bloquées, impossibilité de sécuriser ou de tester des flux critiques sans recréer leurs fondations. Même dans ce cas, elle doit être découpée par capacité métier et fonctionner en parallèle de l’existant. Un basculement global n’est pas une stratégie; c’est une concentration de risque.
Le CTO doit formuler cette décision en termes de flux, pas de préférence technologique. « Remplacer Symfony ancien par Symfony récent » n’est pas un objectif produit. « Réduire le temps et le risque de modification du calcul tarifaire en isolant son domaine et en supprimant les dépendances non supportées » en est un.
Le choix radical n’est donc pas de tout jeter. C’est de refuser les tickets techniques sans impact explicité, les fonctionnalités sans DoD et les versions PHP sans horizon de support. Un backlog qui contient réellement le produit contient aussi ses contraintes d’exécution.
Verdict: la gestion de la dette technique doit vivre dans le Product Backlog, avec la même discipline de raffinement que le métier. À utiliser en production.




