- Installer Nextcloud au travers son image docker All-in-One
- Truc & Astuces : modifier et fusionner des PDF avec Libre Office Draw
Ce billet est une réflexion suite à un ènième débat sur la notion de réécriture de projet et de dette technique, et c’est parti sur du tellement long que je me suis dit que je devrais en faire un billet séparé.
À propos de la dette technique dans un projet
J’en ai vu passer des projets avec de l’historique dans ma vie (dont un avec du code et des données de plus de 40 ans) ; et quoi que tu décides, quoi que tu fasses, la simple longue existence du projet va amener à des choix compliqués, surtout quand il s’agit d’exister face à de nouveaux arrivants ou des concurrents (au sens large) qui ont beaucoup plus de moyens.
Le simple fait que le projet vive fait qu’il va vieillir, par rapport aux nouvelles (et sans cesse renouvelées) façons de faire. Les outils changes, les besoins changent, les contraintes changent (pensez aux contraintes CPU, mémoire, I/O, disque… d’un projet il y a 40 ans ; le matériel moderne n’a absolument plus rien à voir), les exigences utilisateur et de sécurité changent.
Le point le plus critique là-dedans, de ce que j’ai vu personnellement (votre expérience peut être différente), c’est les outils au sens large. Les frameworks, les langages eux-même évoluent et les versions anciennes voient progressivement leur support disparaitre.
Les possibilités (je n’ose pas parler de « choix ») classiques sont :
1. Ne rien faire, ou presque
On s’arrange pour faire le minimum vital pour que ça tourne encore en production sur l’existant, et on met le gros des ressources sur les nouvelles fonctionnalités. On laisse donc filer la dette technique et ne corrige que le minimum pour garder la tête hors de l’eau. Ça a l’air con dit comme ça, mais dans l’industrie c’est un choix très fréquent, parce que la maintenance et la mise à niveau, ça coute cher et ça ne se vends pas au client, qui a souvent beaucoup de mal à comprendre pourquoi il paierait pour une nouvelle version qui fait la même chose (ou moins bien), avec potentiellement de nouveaux bugs, alors que « l’ancienne marche ». À ce sujet il y a beaucoup d’éducation des clients à la réalité du monde informatique, surtout sur les projets ouverts sur Internet, mais c’est encore un autre débat.
Ça peut fonctionner comme ça assez longtemps, en réalité. Jusqu’à la catastrophe, en général une technologie clé totalement à l’abandon, qui nécessite un effort démesuré pour être remplacé. Exemple : il y a 40 ans vous vous êtes beaucoup reposés sur Pro*C ou Progress 4GL et objectivement c’était à l’époque une excellente solution, peut-être la seule capable de tenir la charge avec le matériel disponible. Maintenant, la technologie est à l’abandon, et les seules personnes encore capables de comprendre votre base de code métier critique s’approchent de la retraite.
À propos : c’est très facile de juger à postériori qu’un projet a été mal géré, qu’un choix technologique est mauvais. C’est beaucoup, beaucoup plus complexe sur le coup – voire être complètement faux. Avant de cracher sur les personnes qui ont travaillé historiquement sur le projet (un sport très commun), rappelez-vous qu’une choix technologique ou de gestion aujourd’hui mauvais pouvait très bien être, au moment de la décision, un bon choix, peut-être le meilleur disponible, à cause de contraintes différentes dont vous n’avez sans doute même pas idée. Ne faites pas ça : ça ne sert à rien, le passé est le passé, vous n’allez pas le changer en méprisant des gens.
2. Maintenir la base de code à peu près à jour, en permanence, tout le temps
On limite donc en permanence la dette technique sur le projet. Ça nécessite un gros effort continu, et assez de ressources (de tous types) pour y arriver, surtout si on veut continuer à évoluer, proposer des nouveautés en même temps.
C’est un choix qui est assez rarement fait, mais qui est tout à fait tenable avec des équipes bien organisée ; son principal problème est son pré-requis absolu : tous les directeurs techniques du projet, doivent tous et toujours garder cette priorité, et s’assurer d’avoir assez de ressources en permanence pour y arriver. Le moindre trou dans la raquette ramène très vite au cas 1, avec une dette technique irrattrapable.
Ça implique d’avoir des briscards du développement à la tête du projet, des gens qui savent exactement quoi faire et quand pour le bien du projet, et qui ont les moyen d’imposer des priorités de maintenance quand c’est nécessaire ou de refuser des « améliorations » qui vont poser trop de problèmes à long terme. Ce genre de ligne est difficile à tenir, et fonctionne surtout sur des projets libres avec un leader fort. Il implique aussi de choisir des technologies stables dans le temps, donc pas de truc trop propriétaire, ni exotique, ni le dernier framework JS à la mode (ou d’avoir des équipes massives pour refaire des choses en permanence).
3. Tout casser et tout recommencer (la fameuse réécriture de zéro)
Idéalement ça devrait être la solution à ne choisir que quand les deux premières ont échouées ; en pratique on aime beaucoup réécrire pour de mauvaises raisons. Les gens sous-estiment massivement la difficulté et le cout d’une réécriture d’un projet, surtout s’il est un peu complexe. J’en ai vu, sur des petits projets, qui se sont bien passées ; mais aucune n’a tenu les charges et délais estimés au début.
D’autre part, il y a des cas où la réécriture est objectivement un bon choix, même sur un projet énorme. Une base de code trop mal conçue, trop mal maintenue trop longtemps, qui se repose sur des technologies complètement obsolètes… et l’effort de remise à zéro peut dépasser celui d’une réécriture tout en imposant une forte charge de maintenance en permanence pour le futur.
En fait, il y a deux sous-cas dans cette fameuse réécriture.
- Le projet est petit et simple (disons, un site web vitrine). Là c’est facile. Oubliez que c’est « une réécriture » ; en fait c’est un tout nouveau projet qui ne va rien récupérer de l’ancien, sauf par coïncidence. Ça peut très bien se passer.
- C’est un gros projet. Et là ça, quelle que soit la raison de la réécriture – aussi bonne soit-elle –, quels que soient les moyens employés, ça va être très long et douloureux. Bon courage.
Mais alors, comment faire avec ce projet historique ?
La solution idéale dans un monde parfait est généralement la 2, « maintenir la base de code à jour en limitant en permanence la dette technique ».
Sauf qu’on est pas dans un monde parfait mais dans le monde réel ; que vous pouvez déjà faire face à un projet quasiment irrécupérable quand vous arrivez dessus ; que vous aurez du monde à convaincre pour cette histoire de maintenance qui n’apporte rien au « client ».
Alors, on fait quoi ?
Je n’ai pas de réponse absolue à cette question, mais voici quelques pistes issues de mon expérience.
Communiquez au sein des équipes
Les personnes en charge des ressources doivent savoir ce que va leur couter la maintenance, mais connaitre le cout de l’absence de maintenance. Ils vont râler, mais moins que quand leur produit sera ignoré par tout le monde en faveur d’une concurrence plus à jour et plus fiable bien qu’ayant moins de fonctionnalités.
Parlez aux gens en utilisant leur langage. Vous avez un responsable technique en face de vous ? Parlez de mise à jour de framework, de technologies. Un responsable sécurité ? Mentionnez les failles à corriger, et l’impossibilité de le faire si les outils ne sont pas à jour. Un responsable financier ? Mentionnez les couts de maintenance et ceux des pertes dues à l’absence de maintenance (contrats perdus, etc). Un commercial ? Donnez-lui des billes pour vendre cette maintenance, à base de qualité de service par exemple.
N’oubliez pas non plus que personne n’aime faire de la maintenance idiote sur un projet obsolète. Un projet à jour, c’est des équipes motivées et plus productives, avec moins de risque de partir voir ailleurs.
Mettez les utilisateurs dans la boucle
Les utilisateurs savent ce dont ils ont besoin. Pas vous.
Donc, mettez les utilisateurs dans la boucle, recueillez leur besoin, leur avis, leurs retours ; faites des sessions de démonstration, trouvez des utilisateurs prêts à essuyer les plâtres de versions expérimentales, et surtout, écoutez-les.
Bien sûr, il ne s’agit pas de dire amen à toutes les demandes de tous les utilisateurs (c’est une garantie pour avoir une usine à gaz), mais il devrait être assez facile de comprendre ce qui est vraiment indispensable, ce qui est utile et ce qui est superflu bien que ça semblait être une bonne idée à l’origine.
D’autre part, un utilisateur qui discute avec vous, qui sait comment le logiciel est créé, sera plus à même à comprendre les besoins de maintenance, les versions qui n’apportent presque que des mises à jour technique, pourquoi cette évolution qui a l’air simple de son point de vue ne l’est pas du tout.
Dans le domaine, sortir du clientélisme pour venir à une relation de partenaires de confiance permet à tout le monde d’être gagnant, sauf peut-être le commercial qui est récompensé aux profits immédiats.
Accompagnez le changement
La communication est d’autant plus importante en cas de réécriture : à moins d’avoir des moyens colossaux, une réécriture, c’est l’abandon d’une version « qui marche », la mise en pause de nouvelles fonctionnalités pour longtemps, des régressions, des fonctionnalités qui disparaissent…
Aucun utilisateur n’aime ça, et c’est normal.
La bonne nouvelle, c’est que les techniques d’accompagnement au changement existent depuis longtemps et fonctionnent bien. C’est du travail, ça ne va pas être facile, ça sera simplifié s’il y a une relation de confiance avec les utilisateurs (cf plus haut), mais c’est toujours indispensable.
Faites dans le stable et classique
Bon, d’accord, ce nouveau langage / framework / outil a l’air très cool. Mais il n’est éprouvé par personne et est développé par une personne dans son garage. Avez-vous vraiment envie de baser des composants critiques sur un socle qui aura peut-être disparu dans deux ans ? C’est encore pire si l’outil est produit par une start-up, parce qu’en plus vous n’aurez même pas le code source pour maintenir cette brique indispensable vous-mêmes, mais uniquement vos yeux pour pleurer.
Alors oui, c’est sans doute moins amusant de rester dans ces langages classiques et outils un peu dépassés au lieu d’être à la pointe de la technologie. Mais ici on parle de projets au long court, pas de projets jouets, de code jetable ou de R&D.
Découplez !
Oui, ça a l’air con dit comme ça, mais c’est un problème ultra-courant dans la vraie vie. Surtout, il y a plus de chance de réutiliser du code, de changer d’outils sans tout casser si chaque partie est soigneusement découplée dans son propre coin, la plus indépendante possible du reste.
D’autre part : mettez en place un outillage pour forcer ce découplage. Parce qu’un projet à 150 modules censés être indépendants mais qui contient des milliers de dépendances cycliques n’est pas seulement devenu un gros blob. C’est surtout devenu un projet qu’il n’est plus possible de redresser en gardant un effort correct.
Testez !
Pour des raisons évidentes. Avec un code bien testé, on peut plus facilement se lancer dans des changements majeurs sans craindre de tout casser avec un déluge de régressions.
À ce propos : pensez aussi à découpler vos tests du code de run. Ça implique d’avoir des tests d’intégration, des tests fonctionnels en plus des tests unitaires. Ces outils de tests plus-ou-moins-fonctionnels intégrés aux frameworks peuvent être très pratiques, mais induisent un couplage fort, et vous vous retrouvez avec une batterie de tests complètement inutiles (et à réécrire) si vous voulez changer de framework…
Vous n’imaginez pas la quantité de projets que j’ai croisés qui n’avaient pas la moindre ligne de test – ou des tests qui fonctionnent uniquement sur la machine du développeur principal.
Documentez !
Là aussi ça a l’air con. Mais d’expérience, un énorme problème lors de la reprise de maintenance ou de la réécriture, c’est précisément l’absence de documentation. Et à ce sujet, je l’ai tellement entendue et elle m’énerve que je la met en rouge avec des grossièretés dedans :
Le code n’est pas une putain de documentation. Jamais.
« Regarde comment c’est fait aujourd’hui » n’est jamais une réponse valable à une question qui porte sur le fonctionnel.
Un code lisible est très pratique – disons même « indispensable » – pour aller retrouver un détail d’implémentation ; et surtout pour garder le projet maintenable.
Mais jamais, au grand jamais, le code ne pourra servir de référence pour la documentation fonctionnelle du projet ; parce que même si on arrive à faire « facilement » (ça n’est jamais le cas) le lien entre le code et le besoin fonctionnel, rien ne dit si tel ou tel comportement étrange est volontaire, ou un bug dans un cas aux limites. Or, quand on veut reprendre des pans entiers de code (pour cause de maintenance lourde ou de réécriture), le premier point c’est de conserver la cohérence fonctionnelle du code.
Alors, sortez-vous les doigts et écrivez au moins une documentation sur ce que le projet devrait faire d’un point de vue fonctionnel ! Embauchez des petits jeunes pour ça si besoin. Ça existe : une de mes missions en stage, c’était littéralement de réécrire une documentation à partir du code.
Sachez abandonner
Parfois, un projet n’est plus viable, et il faut l’abandonner, jeter plein de travail à la poubelle. C’est d’autant plus difficile qu’on a beaucoup investi dedans, et qu’on a l’impression qu’il ne faudrait « pas gâcher » en continuant coute que coute.
Ce biais, malheureusement pas assez connu, porte le nom de couts irrécupérables, et vous pouvez en savoir plus sur la page Wikipédia ou via cette excellente vidéo de Science Étonnante.
Ce que ça dit surtout, c’est que parfois, la meilleure solution consiste à mettre du travail à la poubelle. Que ce soit un projet trop ancien et trop mal en point qu’il faudra réécrire complètement ; ou une réécriture qui se passe mal et va dans le mur. Dans tous les cas, parfois, mieux vaut sacrifier du travail déjà fait que persister à vouloir rendre viable ce qui ne l’est pas.
N’hésitez pas à parler explicitement du biais des couts irrécupérables autour de vous : le connaitre, c’est déjà le premier pas vers son identification et donc son contournement.
Oui, beaucoup de réécriture de zéro ne sont pas justifiées.
Oui, parfois c’est intéressant, objectivement, de tout casser pour tout réécrire.
Oui, travailler sur un projet historique peut être passionnant (déjà parce que c’est souvent du gros projet avec un fonctionnel intéressant, et des défis techniques uniques).
Non, un projet n’est pas condamné à devenir un blob de n’importe quoi ni à se faire réécrire tous les trois mois. Mais ça demande de la méthode.
Je vous ai présenté ici quelques pistes issues de mon expérience personnelle ; n’hésitez pas à ajouter les vôtres en commentaires.
Icône : CC BY-SA d’après « Anciano », peinture à l’huile d’Ulpiano Checa (1860–1916) ; numérisation CC BY-SA Poniol.