Développement
Quoi ne PAS corriger dans une base de code héritée
La maintenance d'une base de code héritée n’est pas chose facile. Très souvent, il y a une quantité impressionnante de codes à refactoriser et si peu de temps pour le faire. Quand choisissez-vous de refactoriser ou de laisser tomber? Vous ne pouvez pas passer la majeure partie de votre temps à nettoyer: vous devez aussi fournir des fonctionnalités et corriger les bogues. Mais si vous ne prenez pas, ne serait-ce qu’un peu de votre temps, pour nettoyer votre code, les choses peuvent rapidement devenir plus compliquées. Vous serez ralenti dans vos progrès et vous risquez de créer plus de bogues que d'autres choses…
Des réponses comme « mesurer le coût de la dette technique » ne sont pas si utiles si elles ne vous disent pas le pourquoi du comment. En pratique, vous ne pouvez pas comparer le coût de la refactorisation à la non-refactorisation d'un morceau de code particulier. Sera-t-il changé régulièrement? Combien de temps sera-t-il maintenu? Qui va travailler dessus? Est-ce que cela causera des bogues? Très rarement vous obtiendrez des réponses précises pour ce type de question, la majorité du temps se sera plutôt des estimations intuitives. Il est cependant difficile de s’en tenir à ces «intuitions» lorsque vos collègues vous demandent si une refactorisation est de mise.
Je n'ai pas de réponse simple à ces questions. Comme souvent dans le développement logiciel, It Depends™.
Cependant, je vous propose de regarder le problème sous un autre angle! Et si nous pouvions commencer en énumérant les choses qui ne valent PAS la peine d’être réparées? Tous les problèmes n’ont pas le même impact sur votre travail. Lorsque vous traitez votre «legacy codebase», vous devez en laisser une partie de côté afin de pouvoir concentrer vos efforts sur ce qui reste.
Ne réécrivez pas tout
J'ai vu un tas de projets qui suivent ce modèle :
- La base de code se développe à mesure que l'entreprise demande des fonctionnalités et des corrections de bogues
- Les anciens développeurs quittent progressivement le projet, de nouveaux développeurs se joignent au projet
- La base de code vieillit ce qui rend le travail de l’équipe plus difficile
- L'équipe (principalement constitué de récents développeurs) veut réécrire la base de code en utilisant des outils plus modernes
À ce stade, le plan est de remplacer la «base de code héritée» existante par une nouvelle. Si l'équipe parvient à faire accepter le projet de réécriture™, le reste se passe comme suit :
- l'équipe commence à réécrire le projet ailleurs, en utilisant différents outils (encore une fois, yay!)
- Parce que la réécriture prend beaucoup de temps, l'équipe doit fournir un certain support pour l'ancien système (corrections de bogues urgents, fonctionnalités critiques, etc.). Ceux-ci qui doivent également être implémentés dans la nouvelle base de code.
- Au bout d'un moment, l'équipe essaie de remplacer l'ancien système par le nouveau. Rapidement, de nombreux problèmes sont signalés. Plus de temps est nécessaire jusqu'à ce que le remplacement puisse être effectué.
- Les anciens développeurs quittent progressivement le projet, de nouveaux développeurs rejoignent l’équipe.
- La nouvelle base de code vieillit et à nouveau le travail de l’équipe s’en retrouve affecté.
- L'équipe (principalement composé de développeurs récents) veut réécrire la base de code, en utilisant des outils plus modernes 🙃
Je ne plaisante pas! J’ai déjà passé à travers toutes ces étapes, en passant de la maintenance d’une base de code héritée jusqu’à sa tentative de réécriture, toutes deux exécutées en production. L'idée de redémarrer en remplaçant l’ancien code par des outils plus récents a été fréquemment évoquée lors des réunions.
Lorsqu'il s'agit de systèmes hérités qui servent des clients en production, une Big Bang Rewrite est rarement la solution. Non pas qu'il ne faille PAS moderniser le code. Mais, il est très naïf de penser que nous pouvons remplacer tout un système sans que les clients ne remarquent la différence.
Que faire à la place
D'après mon expérience, une meilleure approche consiste à réduire la portée de la réécriture. Identifiez une partie du système hérité et réécrivez-la. Juste ça.
Les réécritures incrémentielles peuvent être mises en production plus rapidement.
J'aime utiliser La technique du navire de Thésée pour ce faire. Elle porte aussi le surnom "The Strangler Fig Pattern” et ressemble à ceci:
Si vous recherchez un exemple réel, je vous recommande cette conférence de Adrianna Chang qui l'a appliqué sur Shopify 🪖
Ne répare pas ce qui ne fait pas entrave à ta progression
Pour être honnête, je ne pense pas que ce soit un grand dicton. C'est facile de ne pas bien le comprendre et de l'utiliser pour entraver les changements 😬
Le but n'est pas d'empêcher nos semblables de changer la façon dont les choses sont faites aujourd'hui. Je crois que les progrès doivent l'emporter sur la cohérence, mais les nouvelles normes doivent être explicites. Ainsi, je pense que c'est bien de changer quelque chose qui n'est pas « cassé » si c'est un pas vers l'état souhaité.
Cependant, cela peut rapidement devenir une distraction si vous n'êtes pas attentif.
Peut-être avez-vous déjà été dans cette situation auparavant :
- Vous voyez une ancienne syntaxe/modèle lors de la lecture du code
- Vous décidez de le moderniser au fur et à mesure
- Plus tard, lorsque vous poussez votre version, une problème est signalée
- Vous devez annuler cette modification et corriger le bogue au plus vite
- Enfin, vous passerez beaucoup plus de temps sur ce code que vous ne le pensiez
J’ai passé par là plusieurs fois. En général, c'est parce que je ne fais pas attention à l'étape 2. Par exemple, quand j'oublie de tester si mon changement a introduit une régression, peut-être que je n'étais même pas conscient du réel #oops 🤦
Cependant, nous ne devrions pas conclure qu’il faut éviter de moderniser le code après cette petite aventure.
Moderniser le code que vous devez maintenir est souhaitable. Si vous avez besoin de modifier du code, il serait sage de passer d'abord un peu plus de temps pour faciliter le changement. Mais, en tant que professionnel, vous devez être conscient des changements que vous apportez. Introduisez-vous une régression? Quel est le comportement actuel? Les tests automatisés sont généralement le moyen le plus rapide d'obtenir continuellement ces informations. Sans tests, vous devez soit les écrire (c'est long la première fois), soit les tester manuellement (c'est long à chaque fois), soit laisser quelqu'un d'autre les tester (c'est ainsi que vous êtes dérangé pour faire des corrections de bogues urgentes).
Par conséquent, ma version de ce conseil est la suivante :
Si cela n’affecte pas la valeur commerciale de votre produit, ne le corrigez pas.
Nous ne factorisons pas le code pour le plaisir. Nous factorisons le code afin de pouvoir continuer à ajouter de la valeur au logiciel sans introduire de bogues.
Plusieurs parties du code qui ne datent pas d’hier peuvent sembler très moches, mais cela n’a pas d’importance à moins que vous ne devez les lire et les mettre à jour sur une base régulière. Ne perdez pas de temps à refactoriser du code simplement parce qu'il n'est pas propre. C'est une distraction qui peut vous coûter très cher en temps. Pire : cela peut donner un mauvais goût au « refactoring », conduisant votre équipe à ne plus faire le nécessaire.
Que faire à la place
Mon point c’est qu'il faut du temps pour apporter des modifications sûres à une base de code héritée. Ainsi, vous devez réduire le nombre de modifications que vous apportez. Lorsque le temps est compté, faites des compromis sur la quantité et non sur la qualité.
Restez concentré sur la cible. Faites une chose à la fois et faites-le bien.
Votre temps est mieux utilisé pour ajouter des tests manquants et refactoriser le code qui vous gêne réellement. Rester concentré.
Si vous avez du mal à le faire, je vous recommande d'essayer la Méthode Mikado. Elle fournit une recette claire à suivre lorsque vous plongez dans l’inconnu, afin que vous restiez sur la bonne voie.
Des commits plus petits et plus fréquents peuvent également aider.
Ne perdez pas de temps avec des refactorings simples
Celui-ci est plus subtil. Il s'agit de prioriser le type de refactorisation à aborder en premier.
Certaines refactorisations sont plus faciles à réaliser que d'autres. Certains ont plus d'impact que d'autres. La difficulté et l' impact des refactorings ne sont pas nécessairement corrélés. Le piège consiste à n'opter que pour les plus faciles et à ne pas tenir compte de l'impact de ceux-ci.
Encore une fois : votre temps est compté. Il y a tellement de choses que vous devez faire avant la date limite à venir. Lorsqu'il ne vous restera plus de temps, vous aurez tendance à prendre des raccourcis quelques fois plus risqués. En règle générale, les tests sont abandonnés lorsque le temps presse. C'est pourquoi il ne faut pas perdre trop de temps sur des refactorings insignifiants qui pourraient être abordés plus tard.
Passez plutôt du temps sur les refactorisations nécessaires qui rendront le code testable. Cela sera payant car vous pourrez vous déplacer plus en sécurité et plus rapidement dans le code à mesure que les exigences changent et que la date limite approche!
Il est préférable d'expédier une refactorisation percutante plutôt que 50 petites qui n'ont que peu ou pas d'impact sur la valeur commerciale de votre produit.
Que faire à la place
Pour vous aider à identifier les parties du système hérité sur lesquelles vous feriez mieux de dépenser votre énergie, je vous recommande d'effectuer une analyse de votre code et de sélectionner les hotspots.
Vous avez besoin de 2 choses pour exécuter une telle analyse :
- Un moyen de mesurer la complexité du code. Si votre langue ne dispose pas d'un tel outil, vous pouvez utiliser le niveau d'indentation comme une métrique suffisante.
- Métadonnées de contrôle de version. Si votre projet utilise git, vous êtes prêt à partir.
Avec cela, vous pouvez rapidement rédiger un profil de votre base de code, ce qui vous aidera à prioriser le code que vous devez refactoriser en premier, en fonction de l'impact que ce refactoring aura sur votre vélocité:
En bref : concentrez-vous sur le code qui nécessite des modifications fréquentes.
Les refactorisations qui aident le plus sont les suivantes :
- Extraire la logique dans des fonctions pures que vous pouvez nommer, tester et refactoriser
- Rendre visibles les effets secondaires. En particulier, le code séparé qui renvoie une valeur du code qui effectue des effets secondaires (le principe CQS).
- Améliorer les noms. Ceci est généralement une refactorisation facile car elle peut être automatisé par votre éditeur de code (par exemple, F2 sur VS Code). Pourtant, c'est un élément clé à ne pas manquer.
- Supprimer la duplication des concepts. La duplication est moins chère à maintenir que la mauvaise abstraction. Mais, lorsque vous remarquez que les choses changent toujours ensemble, vous avez alors un bon candidat pour la refactorisation.
Enfin, je voudrais ajouter une nuance à ce conseil. Parfois, il est bon de commencer par quelques refactorisations faciles pour s'échauffer. C'est une raison valable de le faire. Mais, si vous cherchez à rendre la base de code plus facile à utiliser, ne perdez pas votre temps sur ces refactorisations trop longtemps 😉
Si vous souhaitez découvrir d'autres conseils et astuces pour améliorer votre productivité, consultez cette section de notre blogue.
Merci à Nicolas Carlo pour cette collaboration. Vous pouvez lire d’autres de ses articles sur son blogue Understand Legacy Code et le suivre sur Twitter @nicoespeon.
Crédit photo: Markus Spiske.
Cet article vous a donné des idées ? Nous serions ravis de travailler avec vous ! Contactez-nous et découvrons ce que nous pouvons faire ensemble.