Introduction : pourquoi refactorer ?
Vous connaissez tous cette partie du code que personne ne souhaite maintenir dans votre équipe ? Le code fonctionne et personne ne souhaite y toucher, toute l'équipe le sait, il n'existe pas de façon simple de modifier ce code, on parle alors de code legacy.
Refactorer consiste à changer l’implémentation sans changer la logique et le comportement du code ce qui va nous permettre sur un code legacy de :
Réduire la complexité ;
D’améliorer la lisibilité ;
De faciliter la maintenabilité ;
De rendre le code plus extensible et ouvert aux améliorations.
Les coûts de la dette technique peuvent s’élever à plusieurs millions d’euros et ce pour des applications de taille moyennes.
Il est très difficile sur un projet avec une dette technique importante d’avoir une stratégie pour agir sur cette dette et plus cette dette est importante plus il est difficile de faire évoluer le système et de le maintenir.
Mais aujourd'hui, la roue a tourné et vous avez été désigné pour traiter une story qui touche à cette partie du code, vous ne souhaitez pas ajouter encore plus de complexité au code et augmenter la dette technique. Mais comment y parvenir ? Chaque changement risque de faire s’effondrer toute la structure.
La méthode mikado est votre allié pour y parvenir, c’est une méthode relativement simple pour aborder des problèmes précis. En sondant le code, nous identifions ce qui sera impacté et où les changements doivent avoir lieu.
Sur le principe du jeu nous devons faire de petits changements sans que toute la structure s’effondre, petit à petit jusqu’à ce que notre objectif soit atteint. Si un changement a un impact sur toute la logique déjà existante il faut annuler tous nos changements et itérer de nouveau en prenant en compte ce nouveau prérequis.
Les étapes à suivre pour refactorer
Fixer un objectif clair et défini au début ;
Expérimenter une solution ;
Visualiser les changements ;
Annuler si le code n’est pas livrable et itérer de nouveau.
Exemple d’application pour refactorer
Prenons l’exemple suivant, nous avons un nouveau besoin métier qui consiste à intégrer des utilisateurs de types prospects, nous allons tenter d’implémenter cet objectif de la façon la plus naïve possible. Nous ajoutons un nouvel attribut « TYPE » sur notre objet métier utilisateur, une fois que nous exécutons l’application elle plante, en effet il manque les modifications au niveau du schéma.
Ce n’est pas grave nous allons pouvoir annuler nos modifications et implémenter ce nouveau prérequis, une fois implémenté si nous ajoutons de nouveau le type sur l’utilisateur notre application se lance bien et tous les tests fonctionnent comme avant nos modifications. Ce prérequis est isolé et peut-être livré tel quel en production.
Pour l’instant nous n’utilisons pas le type dans notre application, le second prérequis est donc de modifier les requêtes actuelles de recherche des utilisateurs pour rechercher les utilisateurs de type client dans notre application.
Cette seconde étape nous fait planter les tests d’intégration car nous n’avons pas initialisé le stock de nos utilisateurs au bon type. Nous pouvons de nouveau annuler notre code, nous avons identifié un nouveau prérequis à cette seconde étape et nous devons commencer par implémenter ce prérequis. Nous attaquons systématiquement par les branches pour arriver à la racine ce qui nous permet d’atteindre notre objectif initial sereinement.
Notre code est de nouveau livrable tel quel et nous attaquons alors l’ajout du type prospect de la même façon en expérimentant et en définissant de nouveaux prérequis.
Débriefer
Sans nous en rendre compte nous avons créé un graphique et implémenté les 4 étapes de la méthode :
Fixer l’objectif ;
Expérimenter ;
Visualiser les changements ;
Annuler et itérer de nouveau.
Lorsque nous avons traité une des feuilles de notre arbre nous pouvons mettre un indicateur de succès pour avoir un visuel sur l’avancé, ici dans notre cas concret une fois que nous avons ajouté notre nouvelle colonne au schéma de notre base de données nous pouvons valider la feuille qui y correspond et passer au prérequis suivant.
L’avantage ici c’est que chaque branche peut être livrable en l’état et nous pouvons également identifier d’autres améliorations. L’erreur à ne pas faire c’est de prendre tous les changements à la volée, il faut se forcer à uniquement les noter et mêmes s’ils ne sont pas relatifs à notre besoin initial ils feront l’objet d’autres refactoring (pourquoi pas les intégrer par la suite dans une roadmap technique de notre projet s’il n’y a pas de besoin métier relatifs).
La théorie en détail pour mieux refactorer avec la méthode Mikado
Nous avons donc suivi les étapes préconisées par la méthode et voici ma réécriture du schéma qui définit les étapes de la méthode :
Mais avant de commencer il faut s’assurer que tout notre travail est bien sur le serveur distant et que nous pouvons revenir vers un état stable assez facilement. Pour ça notre base de code doit être bien testée pour ne pas inclure de régressions, nous détaillerons des astuces pour simplifier l’implémentation de ce filet de sauvetage sur un prochain article.
Écriture de l’objectif
Une fois que nous avons ce filet de sauvetage nous pouvons commencer par écrire l’objectif de notre refactoring sur un double cercle (sur une feuille, ou utiliser un outil, pour ma part j’utilise l’outil coggle.it qui est très simple pour créer des diagrammes).
Implémentation naïve
La seconde étape consiste à tenter d’implémenter la solution de la façon la plus naïve possible sans se soucier des éventuelles erreurs de compilation ou de logique métier, ces deux étapes sont représentées ainsi :
Il y a maintenant deux possibilités, votre code fonctionne ou ne fonctionne pas.
Si le code fonctionne et que ça correspond à l’attendu, c’est super vous pouvez pousser votre code. Vous pouvez alors indiquer que votre objectif est terminé.
Itération
Mais dans la plupart des cas l’objectif ne sera pas atteint aussi facilement, vous pouvez alors noter comme nouveau prérequis les correctifs à apporter liés aux erreurs remontées par cette première tentative et revenir à l’état initial.
Annulation des modifications
C’est cette étape qui est la plus compliquée à assumer, devoir annuler tous nos changements afin de revenir à l’état stable initial, cela demande beaucoup de travail sur soi, mais une fois que vous verrez le résultat final vous ne serez pas déçus. L’étape est matérialisée sur cette partie du graphique :
Nous attaquons alors de nouveau avec ce nouveau prérequis et une fois complété nous pouvons attaquer une autre expérimentation de notre arbre pour compléter la branche. Il faut réitérer autant que possible jusqu’à atteindre notre objectif initial.
Conclusion sur la méthode Mikado pour refactorer
La méthode nous a permis de structurer tout notre travail et ne pas se perdre dans des refactorings interminables. Elle nous permet de rester bien focus sur notre objectif, on ne se disperse pas en cours de route sur d’autres sujets, nous pouvons les noter sur un nouvel arbre et appliquer également la méthode pour les solutionner.
Notre approche a été incrémentale ce qui a réduit les risques et gardé un socle stable durant nos développements. Enfin au niveau communication nous pouvons être transparent avec l’équipe et les sujets peuvent être repris plus facilement par une autre ressource de l’équipe !