jobsphp

Dette technique PHP : le bilan de nos arbitrages ratés

Gestion & Stratégie. Dette technique PHP : le bilan de nos arbitrages ratés

Un TDR qui grimpe progressivement sur plusieurs années, voilà ce qu'on appelle un arbitrage accumulé.

Dette technique PHP: le bilan de nos arbitrages ratés

Chaque commit ajoute sa ligne au compteur; personne n'ouvre le rapport PHPStan pour regarder ce qui se dégrade derrière le tableau de bord.

Voilà ce qu'est un arbitrage raté: non pas une décision prise à la légère, mais une décision dont on n'a mesuré ni le coût immédiat ni le remboursement futur. La dette technique ne se voit pas toujours au moment où on la contracte. Elle apparaît plus tard, quand chaque modification commence à coûter davantage qu'elle ne rapporte, quand les corrections réclament davantage de tests et quand la moindre évolution métier oblige à traverser des couches de code devenues difficiles à isoler.

La dette ne vient pas seulement d'un mauvais développeur ou d'une équipe pressée. Elle peut naître d'une contrainte légitime: tenir une date de lancement, contourner une dépendance instable, maintenir une compatibilité avec un ancien système ou livrer une fonctionnalité avant un concurrent. Le problème commence lorsque cet emprunt ponctuel devient la façon normale de travailler et que personne ne l'inscrit dans les décisions suivantes.

Cet article n'est ni une leçon de morale ni un plaidoyer pour la réécriture totale. C'est un audit froid d'une pratique de gestion: comment on mesure réellement la dette technique dans un projet PHP, comment on la catégorise sans se raconter d'histoire, comment on l'intègre au backlog — et surtout, ce qu'on cesse de se dire pour justifier de ne rien faire.

La dette technique est un arbitrage qui doit rester visible

Une métaphore financière, pas un slogan

Ward Cunningham a posé le terme en 1992, et il faut le lire comme un cadre comptable, pas comme une simple image. Chaque raccourci pris dans le code — un mixed là où un DTO strict suffirait, un if imbriqué au lieu d'un retour anticipé, un test écarté parce que la fonctionnalité paraît triviale — est une forme d'emprunt. Comme un emprunt bancaire, il porte intérêt: le code contourné sera plus coûteux à modifier la prochaine fois qu'on le touchera.

La comparaison a toutefois ses limites. Une dette financière est généralement associée à une somme et à une échéance. Une dette technique est plus diffuse. Son montant dépend du périmètre concerné, du nombre de personnes qui devront intervenir dessus, de la fréquence des changements et du risque accepté par l'entreprise. Une classe peu élégante mais rarement modifiée peut rester un irritant mineur. Une classe tout aussi imparfaite, au centre du tunnel de commande ou de l'authentification, devient un poste prioritaire parce que chaque évolution y coûte du temps et augmente le risque de régression.

Martin Fowler a distribué la dette sur deux axes: délibérée ou involontaire, prudente ou imprudente. Le quadrant le plus dangereux n'est pas forcément celui qu'on imagine. Ce n'est pas seulement la dette délibérée et imprudente, prise dans l'urgence sans garde-fou. C'est aussi la dette involontaire et prudente: une décision prise avec de bonnes intentions, mais sans visibilité sur son coût futur, parce qu'aucun instrument de mesure n'était en place pour la détecter et la provisionner.

Un choix peut donc être raisonnable au moment où il est fait et devenir mauvais lorsqu'il n'est jamais réévalué. Repousser un refactoring pour tenir une mise en production n'est pas, en soi, une faute de gestion. Le laisser disparaître du backlog et continuer à bâtir dessus sans réexaminer le coût, oui.

Une dette technique n'est jamais mauvaise en soi. Elle est mal gérée lorsqu'aucun instrument ne la mesure et qu'aucun backlog ne la rembourse.

Sur un projet PHP moderne, la métaphore fonctionne tant qu'on garde trois réflexes: quantifier — combien coûte le remboursement? —, classer — où se trouve l'urgence? —, puis prioriser — à quel moment l'équipe peut-elle intervenir? —. Sans ces trois étapes, on parle de qualité de code, de vélocité ou de modernisation, mais on ne gère rien.

Mesurer la dette dans un projet PHP: plusieurs signaux, pas un score miracle

Le piège classique consiste à chercher un outil qui donnerait le chiffre de la dette technique. Ce chiffre unique n'existe pas. Ce qui existe, ce sont plusieurs signaux qu'on croise pour construire une lecture exploitable du projet.

L'important n'est pas de transformer chaque métrique en objectif isolé. Une équipe peut améliorer sa couverture de tests sans réduire le risque réel, faire disparaître des alertes statiques sans traiter les zones les plus sensibles ou moderniser la version de PHP sans toucher aux composants qui ralentissent effectivement les évolutions. Les indicateurs servent à orienter la discussion, pas à la remplacer.

La couverture de tests: une surface de confiance

Premier signal: la couverture de tests PHPUnit. Un projet sans suite de tests fiable ne peut pas prétendre rembourser sa dette à l'aveugle. Toute refactorisation importante y devient risquée, parce que l'équipe ne sait pas précisément quels comportements sont protégés ni quels effets de bord sont attendus.

Une couverture globale basse indique une surface de confiance réduite, mais elle ne dit pas tout. Une couverture élevée peut se concentrer sur des accesseurs, des objets simples ou des scénarios nominaux et laisser sans protection les règles métier les plus complexes. Il faut donc regarder la répartition de la couverture: quels modules sont couverts, lesquels sont fréquemment modifiés, quels chemins d'erreur sont testés et quelles parties du système sont indispensables au chiffre d'affaires ou à la continuité du service.

La question utile n'est pas seulement: combien de lignes sont couvertes? Elle est plutôt: que peut-on modifier sans devoir valider manuellement toute l'application? C'est cette réponse qui permet d'estimer le coût réel d'un refactoring PHP.

Il faut également distinguer les tests unitaires, les tests d'intégration et les tests fonctionnels. Une règle de calcul de prix peut être bien protégée au niveau unitaire, tandis que son intégration avec le panier, la fiscalité ou le système de paiement reste fragile. À l'inverse, une batterie de tests fonctionnels peut signaler qu'un parcours fonctionne sans expliquer quelle règle métier échoue lorsqu'il ne fonctionne plus. La dette de test n'est donc pas simplement l'absence de lignes couvertes: c'est aussi l'absence du bon niveau de vérification.

La complexité cyclomatique: un indicateur ciblé

Deuxième signal: la complexité cyclomatique. Elle correspond au nombre de chemins d'exécution indépendants dans une fonction. Plus elle augmente, plus il devient difficile de comprendre les cas possibles, de construire une suite de tests pertinente et de prévoir les effets d'une modification.

Au-delà d'un certain niveau, une méthode devient difficile à tester de manière exhaustive; une fonction très ramifiée devient surtout un point de friction permanent pour toute évolution. Les seuils sont des repères, pas des lois universelles: une méthode courte et bien nommée ne présente pas le même risque qu'un bloc métier de même complexité, dispersé dans plusieurs conditions et dépendant d'un état global.

Il faut surtout corriger une confusion fréquente dans les chaînes d'analyse. PHPStan est précieux pour l'analyse statique du typage, des appels, des retours et de nombreuses incohérences détectables dans le code. En revanche, il ne fournit pas nativement la complexité cyclomatique comme métrique équivalente à celle de SonarQube. Cette mesure peut être remontée par SonarQube ou par un outil dédié à la complexité du code. Les rapports PHPStan et SonarQube ne doivent donc pas être présentés comme interchangeables: ils ne répondent pas exactement aux mêmes questions.

Le rôle de PHPStan reste central dans un plan de remboursement. Une montée progressive du niveau d'analyse peut révéler des types ambigus, des branches impossibles à vérifier et des contrats implicites qui rendent les composants difficiles à faire évoluer. Mais l'équipe ne doit pas lui demander un indicateur qu'il n'a pas vocation à produire.

Le Technical Debt Ratio: une vue financière imparfaite

Troisième signal: le Technical Debt Ratio, ou TDR. Il se calcule en divisant le coût estimé de remédiation de l'ensemble des violations remontées par l'analyse statique — en jours-homme — par le coût total du développement du projet, lui aussi exprimé en jours-homme.

Un ratio faible peut suggérer que la dette détectée reste maîtrisable. Lorsqu'il augmente durablement, il peut signaler que les défauts accumulés commencent à peser sur le coût des évolutions. Il faut néanmoins manipuler ce ratio avec prudence: sa valeur dépend des règles configurées, du coût attribué à chaque type de violation et de la qualité de l'estimation. Deux projets peuvent afficher le même TDR avec des risques opérationnels très différents.

Le TDR ne mesure pas bien la connaissance perdue, la fragilité d'une intégration externe, la dette de données ou la difficulté à recruter quelqu'un capable de reprendre un composant ancien. Il ne doit donc pas devenir le seul critère d'arbitrage. Il est surtout utile pour observer une tendance, comparer des zones d'un même projet avec des règles constantes et déclencher une discussion lorsque la situation se dégrade.

Croiser les indicateurs avec le contexte produit

Croiser les signaux donne une photographie plus honnête. Une équipe peut observer un niveau élevé d'erreurs statiques, une couverture PHPUnit correcte mais concentrée sur les modules historiques et une complexité cyclomatique importante dans les services que le produit modifie chaque semaine. C'est déjà un diagnostic plus utile qu'un score global, parce qu'il relie la qualité technique à la réalité du backlog.

À l'inverse, un projet peut afficher une dette mesurée faible tout en restant exposé: version de PHP non maintenue, dépendance critique sans solution de remplacement, absence de tests sur un flux de paiement ou procédure de déploiement manuelle. Les indicateurs doivent être lus avec les incidents, les retours de production, le temps passé en correction et la concentration des changements sur certains modules.

SignalOutil ou source principaleCe qu'il permet de voirLimite à garder en tête
Couverture de testsPHPUnit et rapport de couvertureLa surface de confiance avant une modificationElle ne garantit ni la qualité des assertions ni la couverture des scénarios critiques
Typage et incohérences statiquesPHPStanLes contrats implicites, types ambigus et erreurs détectables avant exécutionIl ne remplace ni les tests ni une mesure native de la complexité cyclomatique
Complexité cyclomatiqueSonarQube ou outil dédiéLes méthodes et modules difficiles à tester ou à faire évoluerUn seuil ne suffit pas à juger le risque sans contexte métier
Technical Debt RatioSonarQube et règles configuréesUne estimation globale du coût de remédiationLe résultat dépend fortement des paramètres de calcul
Dette de versionCalendrier de support PHP et dépendancesLe risque lié à une plateforme qui n'est plus maintenueElle peut rester invisible dans les indicateurs de qualité du code

Le backlog: la dette comme un poste de travail, pas comme un sprint épisodique

L'erreur structurelle la plus fréquente consiste à traiter la dette technique comme un sujet de hackathon ou de « semaine qualité ». Une semaine de refactoring ponctuelle, sans intégration au backlog produit, produit trois effets prévisibles: elle reporte le reste du travail, elle génère ensuite du code nouveau non couvert par les tests et elle permet de dire que l'équipe a déjà consacré du temps à la qualité.

Ce format peut avoir une utilité pour lancer un audit ou débloquer un chantier précis. Il ne constitue pas une stratégie. La dette revient dès que la pression produit reprend, et elle revient souvent avec une nouvelle couche de contournements ajoutée pendant la période où personne ne suivait les conséquences du premier arbitrage.

La bonne pratique d'arbitrage consiste à intégrer la dette au backlog comme un poste de travail ordinaire, avec une description claire, un périmètre limité, des critères d'acceptation et une estimation. Cela change la nature de la discussion en réunion de planification: la dette n'est plus un coût caché qu'on évite d'évoquer. Elle devient un poste en concurrence directe avec les fonctionnalités, ce qui est exactement le bon niveau de tension.

Une tâche de remboursement correctement formulée ne dit pas seulement: améliorer le service de facturation. Elle précise ce qui doit changer: isoler une règle métier, supprimer une dépendance globale, ajouter les tests d'un flux déterminé, faire passer un module à un niveau de typage convenu ou remplacer une API devenue obsolète. La fin du travail doit être observable, sinon la dette se déplace sans réellement diminuer.

L'unité de mesure n'est pas nécessairement le story-point. Pour le pilotage financier, une estimation en jours-homme peut être plus lisible, à condition de ne pas la traiter comme une promesse au jour près. Pour l'équipe, la complexité, le risque de régression et les dépendances techniques comptent autant que la durée brute. L'objectif est de rendre la comparaison possible, pas de donner une fausse précision à des travaux exploratoires.

Quand une dette est estimée à plusieurs jours de travail et qu'une fonctionnalité concurrente possède une valeur produit clairement identifiée, l'arbitrage devient lisible. On peut décider de livrer d'abord la fonctionnalité, de réduire son périmètre, de rembourser la dette avant de la développer ou d'accepter explicitement le risque. C'est une décision de gestion, pas une négociation de préférences entre le CTO, le produit et les développeurs.

Un backlog qui ne contient pas de dette technique provisionnée est un backlog qui sous-estime systématiquement la vitesse réelle de l'équipe.

Quelle place réserver au remboursement?

Le format de sprint agile, souvent compris entre deux et quatre semaines, permet d'ancrer cette discipline. Certaines équipes allouent une part fixe de leur capacité aux tâches de remboursement; d'autres réservent cette capacité aux zones dont le risque augmente le plus vite. Les deux approches peuvent fonctionner. La valeur exacte se calibre empiriquement, projet par projet, à partir du temps passé en correction, des incidents et de la fréquence des changements.

Une allocation fixe apporte de la visibilité. Elle évite de renégocier la dette à chaque sprint et empêche les fonctionnalités urgentes de consommer toute la capacité. Une allocation variable permet de répondre à un incident, à une migration de version ou à un module devenu central dans la feuille de route. Dans les deux cas, la règle doit être connue avant la réunion de planification; sinon elle ne résiste pas à la première échéance commerciale.

Type de detteCritère de priorisationTravail à engagerCible de remboursement
Délibérée et prudenteLe raccourci avait une justification, mais son coût de maintien devient supérieur au bénéfice initialReprendre le périmètre qui sera prochainement modifiéProchain cycle de planification pertinent
Délibérée et imprudenteRisque de sécurité, d'indisponibilité ou de régression déjà identifiéRéduire le risque avant d'ajouter une nouvelle dépendanceDès que possible, selon la criticité
Involontaire et prudenteFriction observée sur plusieurs itérations ou ralentissement récurrentDocumenter le problème et traiter le module le plus sollicitéQuelques cycles, avec un responsable identifié
Involontaire et imprudenteIndicateurs absents, comportements mal connus, forte incertitudeCommencer par un audit ciblé et une sécurisation par les testsAprès chiffrage du périmètre réel

La priorisation du backlog produit PHP ne doit donc pas opposer abstraitement « technique » et « métier ». Une dette qui ralentit une fonctionnalité stratégique est déjà un sujet produit. Une migration de PHP qui conditionne la réception des correctifs de sécurité est déjà un sujet de continuité. Le rôle de l'arbitrage du CTO consiste précisément à rendre ces liens visibles.

La dette invisible: PHP en fin de vie

Il existe une catégorie de dette que les indicateurs précédents capturent mal: la dette de version. Elle ne se voit ni dans la complexité cyclomatique, ni dans la couverture de tests, ni dans le TDR. Elle se voit dans la fin de support, dans les dépendances qui cessent d'accepter la version installée et dans la difficulté à obtenir des correctifs compatibles avec la plateforme.

Rester sur une version ancienne de PHP peut sembler moins coûteux qu'une migration. Tant que l'application fonctionne, le sujet est facilement repoussé derrière une fonctionnalité visible. Mais la migration ne devient pas plus simple avec le temps. Les versions intermédiaires s'accumulent, les bibliothèques abandonnent leurs anciennes contraintes, les extensions changent de comportement et les personnes qui connaissent les contournements quittent parfois le projet.

La dette de version se traite rarement par un grand remplacement brutal. Un projet PHP ancien a souvent besoin d'une séquence plus prudente:

1. inventorier les extensions, bibliothèques, services et environnements réellement utilisés;

2. identifier les incompatibilités bloquantes avant de modifier le code métier;

3. mettre en place une chaîne de tests suffisamment fiable pour détecter les régressions;

4. traiter les avertissements et comportements obsolètes par zones cohérentes;

5. faire évoluer l'environnement d'exécution séparément des changements fonctionnels autant que possible;

6. conserver une procédure de retour arrière compréhensible et testée.

La migration est aussi une occasion de réduire la dette documentaire. Si personne ne sait quelle version est utilisée en production, quelles extensions sont nécessaires ou quel service dépend d'un comportement particulier, le problème n'est pas uniquement technique. Il relève de la gouvernance du système.

Le piège de la réécriture totale

Face à un code ancien, la réécriture complète ressemble à une solution propre. Elle promet de repartir sur une architecture saine, une version récente de PHP et des conventions homogènes. Elle efface cependant une partie de la connaissance accumulée dans le comportement de l'application: règles implicites, exceptions métier, contraintes de partenaires, traitements de données hérités et cas particuliers que la documentation n'a jamais décrits.

La réécriture ne supprime pas la dette par magie. Elle la convertit en risque de compréhension et de livraison. Pour décider si elle est justifiée, il faut comparer le coût du maintien progressif au coût de la reconstruction, mais aussi considérer la capacité de l'équipe à faire fonctionner deux systèmes pendant la transition.

Le refactoring incrémental est moins spectaculaire. Il consiste à travailler sur les zones que la roadmap oblige déjà à toucher, à caractériser leur comportement, à poser des interfaces plus nettes puis à déplacer progressivement les responsabilités. Cette méthode ne produit pas immédiatement une architecture parfaite. Elle réduit en revanche le risque de modifier une application entière sans savoir ce qui, dans son comportement actuel, est réellement indispensable.

Dans un projet PHP, le principe est simple: ne pas réécrire ce qu'on n'a pas encore appris à mesurer. Une classe difficile à comprendre n'est pas automatiquement une classe à remplacer. Il faut d'abord savoir qui l'appelle, quelles données elle manipule, quels effets elle produit et quelles garanties existent autour d'elle.

Ce que les arbitrages ratés nous ont appris

Le premier enseignement est que la dette technique est rarement créée par une seule mauvaise décision. Elle résulte d'une série de décisions raisonnables prises sans rendez-vous de réévaluation. Une livraison repoussée, un test reporté, une dépendance conservée pour éviter une migration, une règle métier laissée dans un contrôleur: chaque choix paraît limité. Leur accumulation finit par modifier la capacité de l'équipe.

Le deuxième enseignement concerne la visibilité. Une dette absente du backlog n'est pas une dette inexistante. C'est une dette que l'organisation a décidé de ne pas regarder. Elle réapparaît alors sous d'autres formes: estimation qui dérive, ticket qui revient plusieurs fois, correction qui mobilise les mêmes personnes, mise en production retardée ou développeur expérimenté devenu indispensable pour une zone précise du code.

Le troisième concerne les indicateurs. Un rapport statique ne sait pas quelle fonctionnalité mérite d'être livrée avant une autre. Un taux de couverture ne sait pas si une règle métier est stratégique. Un TDR ne sait pas si une dépendance externe menace le prochain déploiement. Les outils sont nécessaires pour objectiver la conversation, mais l'arbitrage reste une décision située, liée au produit et à ses risques.

Enfin, le remboursement doit avoir une fin observable. Dire que le code est plus propre ne suffit pas. Il faut pouvoir constater qu'un module est isolé, qu'une dépendance a disparu, qu'une règle est testée, qu'un niveau de typage est atteint, qu'une procédure de déploiement est reproductible ou qu'une migration peut désormais avancer sans bloquer toute la feuille de route.

Reprendre la main sur la dette technique PHP

Un projet n'a pas besoin d'être débarrassé de toute dette pour avancer. Il a besoin de savoir laquelle il accepte, pourquoi il l'accepte et à quel moment il réexaminera ce choix. C'est cette différence qui sépare un arbitrage assumé d'un abandon progressif du contrôle technique.

La méthode la plus solide reste peu spectaculaire: observer les zones qui changent, relier les incidents et les ralentissements aux composants concernés, sécuriser les comportements importants par les tests, puis inscrire des tâches de remboursement suffisamment précises dans le backlog. Le refactoring PHP devient alors une opération de gestion du risque, pas une récompense accordée à l'équipe quand le produit a enfin une semaine de calme.

Le bon arbitrage ne consiste pas à choisir systématiquement la solution la plus moderne. Il consiste à rendre explicite le prix de chaque choix. Accepter une dette peut être pertinent pour tenir une échéance. Faire comme si elle n'existait pas n'est jamais gratuit. Avalée par les estimations, la dette finit toujours par réapparaître dans le coût du prochain changement.

Questions fréquentes

Pourquoi le Technical Debt Ratio (TDR) ne suffit-il pas à évaluer la dette ?
Le TDR est une estimation financière imparfaite qui ne prend pas en compte la perte de connaissance, la fragilité des intégrations externes ou la difficulté de recrutement sur des composants anciens.
Comment intégrer efficacement le remboursement de la dette dans le backlog ?
Il faut formuler des tâches précises avec un périmètre limité, des critères d'acceptation clairs et une estimation, afin de les mettre en concurrence directe avec les fonctionnalités métier.
PHPStan est-il suffisant pour mesurer la complexité du code ?
Non, PHPStan est idéal pour l'analyse statique du typage et des erreurs, mais il ne fournit pas nativement la mesure de la complexité cyclomatique, qui nécessite des outils comme SonarQube.
La couverture de tests est-elle un indicateur fiable de la qualité ?
Elle indique une surface de confiance, mais elle peut être trompeuse si elle se concentre sur des objets simples au lieu de protéger les règles métier complexes et les chemins d'erreur critiques.
Faut-il privilégier une réécriture complète ou un refactoring progressif ?
Le refactoring incrémental est préférable car il permet de sécuriser les comportements indispensables par les tests, alors que la réécriture totale risque de faire perdre des règles métier implicites.