Intégrer la dette technique en tant que Product Manager
- La dette technique : Identifier les signaux faibles
- Si nous n’anticipons pas, nous aurons des bugs
- Les différents types de dette technique
- Les solutions à la dette technique : respecter les bonnes pratiques
- Les tâches techniques
- Traiter la dette technique n’est pas une option
- Ressources
La dette technique : Identifier les signaux faibles
« Un code propre est un code simple et direct. Il se lit comme une prose parfaitement écrite. Un code propre ne cache jamais les intentions du concepteur. » — Robert C. Martin (Uncle Bob), Clean Code.
Lorsque le code source d’un produit commence à « sentir mauvais », c’est qu’il y a des code smells : ce sont des indices, plus ou moins subtils, que le code du produit doit être rectifié. Et en tant que Product Manager, quand nous venons avec une nouvelle fonctionnalité, nous devons comprendre si le code « sent mauvais » ou non, suivant ce que vont dire les développeurs
Dans Clean Code, Robert C. Martin liste notamment quatre « code smells ».
- Le code devient rigide, c’est-à-dire qu’il est difficile d’apporter la moindre modification sans devoir modifier le code à de nombreux endroits différents.
- L’application devient fragile : modifier une partie casse quelque chose ailleurs… sans lien logique apparent.
- Lorsqu’il devient plus facile de « bidouiller » le code que de l’écrire proprement, Robert C. Martin appelle ça la viscosité.
- Lors d’anciennes conceptions, nous avions imaginé des tas de possibilités et des risques potentiels, et puis… finalement, rien de tout ça s’est produit. Par contre, l’architecture complexe qui a été développée est toujours là, et traîne dans les pattes des développeurs.
Les développeurs devront alors introduire des actions de maintenance visant le traitement de ces code smells, pour contrôler la dette technique : ils doivent rendre possible la croissance du produit.
Le Product Manager doit également être capable de comprendre les raisons de ces maintenances, et la valeur qu’elles peuvent apporter au produit, car elle n’est pas toujours évidente a priori.
Si nous n’anticipons pas, nous aurons des bugs
Et soyons tout de suite honnêtes : nous pourrons faire tout notre possible, il y aura toujours des bugs. Cela peut aller de petites erreurs dans le code, jusqu’à des problèmes subtils nécessitant de l’investigation, et depuis une erreur mineure que nous planifierons au prochain sprint jusqu’au « hotfix », bloquant pour les utilisateurs, et nécessitant une correction la plus rapide possible.
Lorsque le problème est en production et qu’il est critique, une solution classique est de livrer une solution de contournement : soit nous désactivons la fonctionnalité pour investiguer à tête reposée sur la prochaine itération, soit nous allons… « bidouiller » une solution rapide. Mais pour le coup, c’est surtout parce que nous ne prenons pas le temps de réfléchir à une solution propre, plutôt qu’une conséquence d’un code trop complexe… Quoique la question mérite d’être débattue avec les développeurs : est-ce que le code ne pourrait pas être simplifié, pour éviter de « bidouiller » la prochaine fois ?
Tout cela nous encourage à chercher une solution plus robuste, plus « stratégique », au problème, contrairement à la solution rapide, généralement « tactique », que nous emploierions dans l’urgence du hotfix. Nous pourrons même chercher à anticiper les éventuels problèmes que pourrait rencontrer notre produit dans le futur, notamment en améliorant la qualité du code.
Nous sommes partis du cas du hot fix, mais l’amélioration de la qualité de code peut également passer par la refactorisation ou la mise à jour de dépendances, pour bénéficier des corrections de failles ou bugs issues de nos dépendances, ou même pour apporter de l’innovation à la façon de répondre aux besoins.
Tous ces cas de maintenance font typiquement l’objet du traitement de la dette technique.
Les différents types de dette technique
Les failles de sécurité
Elles sont régulièrement identifiées sur les logiciels, les modules, les dépendances,… utilisés par notre produit. Il suffit parfois d’une « petite » mise à jour (une nouvelle version mineure) pour résoudre la faille de sécurité, mais elle peut avoir un impact conséquent sur le code, et nécessite plus de travail qu’en apparence.
Évolution des techniques de programmation
Il s’agit parfois de code qui avait été écrit en suivant des pratiques, désormais passées de mode. Cela peut paraître anodin, dit comme ça, mais les nouvelles pratiques peuvent améliorer la lisibilité, faciliter la maintenance du code, ou bien résulter d’évolutions du langage ou des applications tierces avec lesquelles nous travaillons, et qui apportent des fonctionnalités plus robustes, plus sécurisées, etc.
Il peut également s’agir d’une mise à jour du code pour une raison tout sauf anodine : attirer de nouvelles recrues en montrant du code moderne et à jour (dur d’attirer avec du code de 10 ou 20 ans d’âge et peu maintenu…).
Implémentation « à la va-vite »
C’est une chose très courante, car nous souhaitons rapidement mettre notre solution entre les mains des utilisateurs, pour bénéficier de retours rapides. Certaines optimisations sont mises de côté et restent quelque peu à l’abandon, car de nouvelles urgences arrivent sur la pile. Ce peut également être un problème de documentation manquante, rendant le code d’autant plus difficile à maintenir … Et lorsqu’une telle situation traîne, il devient parfois plus simple de recoder la portion concernée.
La charge de la plateforme évolue
Ce type est assez proche du précédent, dans la mesure où nous anticipons rarement la charge, lorsque nous démarrons un nouveau service, ou un nouveau produit.
Ici, nous parlons de la croissance dans l’acquisition de notre produit : elle entraîne d’autant plus d’activité sur l’application, et donc d’autant plus de données à traiter dans le même temps.
Le premier réflexe serait de provisionner plus de ressources, pour supporter cette charge… Mais ces ressources ont un coût, alors qu’optimiser le code peut être une solution plus pérenne.
Des besoins d’administration
Cet aspect reprend un peu des deux précédents. Nous allons souvent fournir à nos propres équipes une solution simplifiée pour « gagner du temps », et lorsque le produit commence à amener beaucoup d’activité, nos premiers outils d’administration ont souvent d’autant plus de mal à suivre.
Il va donc falloir se pencher sur les besoins des administrateurs afin de leur fournir un environnement de travail un « petit peu » plus agréable, quand même.
Dans mes discussions professionnelles, j’ai également déjà été confronté à des cas où un produit originellement à usage interne a été revu et amélioré pour être exposé à des clients particuliers, par exemple à des prestataires prenant en charge le support N1 (la hotline qui fait le tri dans les demandes et traite les incidents standards).
Néanmoins, même si le produit doit rester à usage interne, les équipes d’exploitation doivent être traitées comme les utilisateurs externes, et donc bénéficier des mêmes efforts dans le traitement de leurs besoins.
La dette… « pas que » technique
Certains produits impliquent la collaboration de plusieurs équipes, avec des processus, des outils, etc. permettant l’interface entre ces équipes. Ces processus et outils sont souvent mis en place sans une vision claire au long terme.
Cependant, ils nécessitent des évolutions, avec un coût d’autant plus élevé qu’il faut également changer des habitudes de travail … Mais avec un véritable bénéfice au long terme.
Dans ces cas-là, nous avons un travail d’ordre plus politique que technique à entreprendre : être capable de montrer que ces bénéfices à long terme valent l’effort de surmonter le coût à court terme.
Les solutions à la dette technique : respecter les bonnes pratiques
Bien souvent, traiter la dette technique consiste à refactorer le code. Derrière cette expression se cachent toutes les tâches que les développeurs entreprennent pour coller au maximum avec les normes et bonnes pratiques de développement logiciel.
Clarté du code
Robert C. Martin précise que « Le nom d’une variable, d’une fonction ou d’une classe doit répondre à trois questions : Pourquoi elle existe, ce qu’elle fait et comment on l’utilise. »
Pour le Product Manager, cela signifie que si le code est propre, il pourrait presque comprendre la logique métier en le lisant avec un développeur.
Documentation de code
La documentation de code est traditionnellement utilisée pour aider à comprendre ce que fait le code. Cependant, Robert C. Martin est assez critique à cet égard : « Les commentaires sont, au mieux, un mal nécessaire ». La raison à cela est que, lorsque le code change, le développeur se retrouve à devoir également changer les fonctions, ce qui rajoute du travail, et donc des risques d’erreurs supplémentaires (et potentiellement inutiles).
Un bon compromis serait de focaliser la documentation du code sur le « pourquoi », le code devant être assez explicite sur le « quoi » et le « comment ». En effet, une fonction est écrite pour répondre à un besoin spécifique, que ce soit de l’utilisateur final (par exemple une fonction qui correspond à un point d’entrée de l’interface), ou bien un besoin interne de l’application. Ce besoin n’est pas censé changer dans le temps, ou bien la fonction devrait être supprimée ou remplacée.
Réorganisation du code
Robert C. Martin parle du « Single Responsibility Principle (SRP) », un principe selon lequel une fonction ne devrait s’occuper que d’une tâche et la faire proprement. Les développeurs peuvent avoir à appliquer une logique similaire au niveau des classes ou composants, pour éviter la duplication, réutiliser des morceaux communs, et ainsi optimiser le programme, assurer un maximum de cohérence de l’ensemble et, là aussi, assurer la clarté du code.
Les paradigmes du développeur
Ces bonnes pratiques vont s’appuyer sur des paradigmes (ou « combinaison de paradigmes ») de programmation possédant des avantages et inconvénients suivant leur contexte d’utilisation et leur support pour le langage (ou les langages) de programmation utilisé pour notre produit.
Elles vont également s’appuyer sur les patrons de conception (ou « Design Pattern »), permettant de fournir des solutions spécifiques à des problèmes spécifiques dans la façon de structurer son code.
Les normes de développement
Les bonnes pratiques sont également constituées des règles que les développeurs se fixent dans leur façon de coder. Cela peut aller depuis la façon de nommer les variables jusqu’aux paradigmes de programmation et les objectifs de couverture par les tests unitaires (par exemple).
Autrement dit, les bonnes pratiques donnent un objectif vers lequel tendre lorsque les développeurs vont traiter la dette technique accumulée sur leur produit, dans le cadre de sa maintenance.
Les tâches techniques
Laisser traîner la dette technique la rend bien vite indigeste à traiter. Plus le temps passe, plus la solution devient difficile à maintenir, les impacts sur la performance peuvent s’en faire ressentir, et donc l’impact sur les clients. L’approche la plus saine sera donc de la traiter régulièrement, et de la prendre en compte au fil de l’eau, en tant que Product Owner / Product Manager.
Traiter la dette existante
La première chose à faire, c’est de traquer les tâches identifiées avec des tickets, afin de les retrouver dans le backlog. Elles doivent ensuite faire l’objet du même travail d’estimation que n’importe quelle autre tâche du backlog.
Il sera d’ailleurs pertinent d’estimer la portée et l’impact sur les utilisateurs (quels utilisateurs, et dans quelle mesure cela leur facilitera la vie), ainsi que la charge de travail que cette tâche représente. Dans cette estimation, il devrait être bon d’intégrer l’ancienneté de la tâche : plus elle a traîné dans le backlog, plus sa priorité augmente, de sorte à ce qu’elle soit rapidement traitée (autrement dit, lorsque nous dépilons les tâches techniques, nous devrons commencer par les plus anciennes).
Attention, nous serions vite tentés de laisser traîner une tâche de dette technique apportant peu de bénéfice mais représentant beaucoup de travail. Le travail en question doit être revu, justement pour réduire la charge de travail, quitte à découper la tâche en un ensemble plus digeste.
Après cela, il faut prévoir d’intégrer un minimum de ces tâches techniques dans les sprints, afin de maintenir le backlog à une dimension raisonnable.
Après tout ça, s’il traîne encore des tâches dans le backlog, dont nous repoussons constamment le traitement, il serait peut-être bon de se demander si elles sont toujours pertinentes, de les estimer à nouveau, de les réactualiser si possible, et sinon … de les retirer (i.e. « won’t do »).
Et pour finir, une question qui fâche : que faire, lorsque nous avons déjà accumulé beaucoup de dette technique ? Honnêtement, nous en sommes encore à expérimenter des solutions. La première à nous être venue à l’esprit (après « dépiler le backlog ») a été d’attribuer des labels à ces tâches. Nous avons ainsi identifié des thématiques, parfois très techniques, parfois rattachées à des besoins internes (e.g. les équipes en charge de l’exploitation ou les commerciaux), ou bien à des sujets produits (donc sur des services à destination des clients). Suivant les catégories, nous avons rattaché ces tickets à des sujets produits, ou bien nous avons estimé leur priorité, pour pouvoir ensuite les programmer dans nos sprints.
Une dernière solution qui a fait ses preuves passe par des refontes plus globales, de l’interface ou d’un service donné. Sans forcément cibler un ou plusieurs problèmes en particulier, nous allons cibler une partie où il s’en concentre beaucoup. C’est après la refonte que nous pourrons refaire une passe sur le backlog, et (si tout va bien) constater que la majeure partie des bugs sont soit résolus, soit obsolètes.
Le meilleur des mondes : anticiper
Pour terminer en beauté, Robert C. Martin propose une approche complémentaire : la règle du boy-scout. Cette règle consiste en fait à nettoyer le code au fil de l’eau. L’idée est de s’assurer constamment de laisser le code en meilleur état après ajout de la nouvelle fonctionnalité que tel que le développeur l’a trouvé avant. En tant que Product Manager, nous devons donc partir du principe que 10% à 20% du temps d’une tâche de fonctionnalité est dédié au nettoyage du code environnant. La dette se traite en mode « invisible » et en continu.
Traiter la dette technique n’est pas une option
« Vous ne respecterez pas les échéances en travaillant mal. À la place, vous serez ralenti instantanément par le désordre et vous serez obligé de manquer l’échéance. La seule manière de respecter le planning, ou d’aller vite, est de garder en permanence le code aussi propre que possible. » — Robert C. Martin.
La seule façon de maintenir une bonne allure sur le long terme, c’est de bien faire les choses dès le départ : nettoyer le code au fur et à mesure est la solution la plus efficace pour éviter la dette technique. Et s’il s’en accumule tout de même (ce qui arrive invariablement), elle doit être priorisée au plus tôt, car les conséquences à long terme peuvent être catastrophiques.
Ressources
- « Unraveling the Secrets of Software Maintenance: A Strategic Guide for Product Managers » de Henrique Maltez
- Clean Code de Robert C. Martin (Uncle Bob)