Le refactoring en agilité
L'agilité vient répondre à des besoins d'innovation, d'autonomie de l'équipe, de confiance pour découvrir ensemble et maximiser l'efficacité et la qualité des livrables. Chaque méthode agile aborde les différentes couches d'un projet d'un certain angle mais sont toutes unies sur le placement du client au cœur de l'action.
En effet, contrairement aux méthodes dites classiques, les méthodes agiles rendent transparents plusieurs aspects du développement applicatif. Le client - on parle plutôt de stakeholders, qui sont l'ensemble des décideurs (clients, investisseurs et toute partie prenante dans la validation finale du produit ou projet) est aujourd'hui plus en plus proche des réalités et contraintes techniques. Il sait qu'un produit n'est jamais réellement fini, que le développement technique d'une application n'est pas une science infuse mais évolue dans un écosystème dynamique. Un livrable a bien beau répondre au besoin initié et satisfaire les tests de recettes, devra et sera être maintenu voire revu. Ceci impliquera forcément des coûts supplémentaires et étendra la durée du projet.
La question qui se pose est quand est ce qu'on peut dire qu'un produit est réellement fini? Doit-on inclure cette notion de refactoring dans la DoD ou établir un plan voire un contrat de maintenance indépendamment du projet ? Comme l'agilité le laisse entendre, chaque équipe et projet doit être vu comme un ensemble et ce qui marche pour l'un ne marchera pas forcément pour l'autre. Mais une chose est certaine, le plus grand avantage d'une équipe agile, auto-suffisante, évolutive et où la confiance est maître mot, c'est sa capacité à améliorer son rendement et sa qualité. Un code qui marche c'est bien. Un code qui marchera encore pendant un moment, mieux.
Définissons d'abord quelques concepts de base sur le refactoring, puis parcourons ensemble les différentes manières d'inclure un plan de refactoring dans son projet de développement agile.
Le refactoring, quésaco ?
Le refactoring (ou remaniement du code en français) est une technique disciplinée à améliorer un corps de code existant en modifiant sa structure et sa lisibilité sans changer le comportement fonctionnel de celui-ci. Cela peut être dans le cas de l'évolution d'un framework de développement ou simplement pour améliorer la structure de ce dernier.
En règle générale, les tests unitaires (permettant de s’assurer que l’application fonctionne toujours comme prévu malgré des évolutions dans le code),sont écrits avant de procéder à la refactorisation du code. Ces tests unitaires sont exécutés une fois avant le refactoring et une fois après le refactoring. Cela aide à valider que le comportement externe du système logiciel n'est pas interrompu tout au long du processus.
Pourquoi le refactoring est-il important ?
La vie d'un projet de développement ne s'arrête pas à sa mise en production. Le refactoring s'impose souvent de lui-même, que ce soit en termes d'ajout de nouvelles fonctionnalités ou dans la résolution des problèmes ou simplement dans l'amélioration de l'existant.
En effet, la nature du code détermine la facilité et l'efficacité de sa maintenance, que ce soit en interne ou en TMA. Les modifications au niveau du code source s'imbriquent les unes sur les autres et rendent difficile sa lisibilité et sa maintenance. Entrainant ainsi une complexité ralentissant ainsi les nouveaux chantiers.
Le refactoring permet de supprimer la duplication de code ainsi que le code inutile (ou mort), à rendre le code moins complexe (par exemple, en divisant une grande classe en plus petites), et également de rembourser la dette technique avant de commencer une nouvelle fonctionnalité.
Comment aborde-t-on le refactoring en agilité ?
1- Le refactoring en Scrum
La méthode agile Scrum ne donne aucune pratique autour du refactoring. Les pratiquants recommandent de ne pas l'intégrer dans le développement d'un projet pour la raison principale qu'il augmente la dette technique.
En effet, avancer sur des parties de code source de mauvaise qualité, l’équipe ne fait qu’augmenter la dette technique ; alors, quand le refactoring diminue la dette technique d’un côté, nous l’augmentons de l’autre. Au final, le refactoring ne s’arrête jamais et la dette technique a plutôt tendance à augmenter sur le temps.
Certaines équipes scrum adoptent leurs propres règles par exemple : faire du refactoring sur 20% du sprint. Cela permet de diminuer au fur et à mesure la dette technique et améliorer le corps du code en général.
D'autres préfèrent faire du refactoring bien avant de démarrer sur le développement des éléments du backlog a forte valeur. Cela permet de démarrer sur ces briques du produit sur de bonnes bases.
Par ailleurs, l'équipe de développement peut aussi bien négocier le refactoring avec le product owner pour obtenir un temps supplémentaire spécialement dédié à ce faire. Ils peuvent même intégrer le refactoring en tant qu'éléments du backlog. Cette pratique est contradictoire dans la pratique de scrum, mais l'agilité c'est aussi bien de s'adapter à ces besoins et ses possibilités.
Dans le cas où le refactoring concerne un nouveau framework de développement (ou une évolution), l’équipe scrum décide d'abord au complet d’y switcher ou non. Une fois la décision prise, surtout si cette évolution corrige des failles de sécurité ou améliore la stabilité du produit, l’équipe de développement ne devrait pas à avoir à négocier la mise à jour du code ; mais elle devra être transparente avec le product owner pour que celui-ci puisse s’adapter à ce besoin.
Si par ailleurs des parties du code source peuvent être améliorables sans forcément impacter le temps de livraison (ce qui est encore une fois contradictoire avec la pratique scrum), l’équipe de développement les travaillera sans même demander au product owner pendant les sprint. Cependant, elle restera vigilante à l’objectif du sprint pour perturber au minimum l’avancement du sprint.
2- Le refactoring dans la méthode XP (l'eXtreme Programming)
Le plus grand avantage de la méthode XP est qu'elle intègre a relecture et l’amélioration du code, ainsi que des tests systématiques tout au long du processus. Cela permet le développement de système constamment opérationnel. Toute anomalie est rapidement repérée et corrigée et l’équipe produit un code de qualité supérieure.
Comme pour les autres méthodologies agile, les spécifications ne sont pas écrites dès le début, l’intégration continue et la fréquence des livraisons permettent de conserver une vision précise de l’avancement. L’automatisation des tests permet également de s’assurer de la non-régression de chaque livraison.
Cette méthode a prouvé qu'il était possible de réduire drastiquement le coût d'un projet quand le développement technique est d'une haute qualité.
Elle se fait principalement par ce qu'on appelle le développement piloté par les tests (TDD). C'est une approche d'ingénierie logicielle qui consiste à écrire des cas de test défaillants couvrant d'abord les fonctionnalités. Et puis implémenter le code nécessaire pour réussir les tests et enfin refactoriser le code sans modifier le comportement externe.
TDD traite 2 types de tests, c'est-à-dire :
- Tests unitaires : utilisés pour vérifier la fonctionnalité d'une seule classe séparée de son environnement.
- Tests d'acceptation : utilisés pour vérifier une seule fonctionnalité
Dans le même ordre d'idées, le refactoring peut également être classé en différents types:
- Refactoring au niveau de la classe locale : Les tests unitaires pour cette classe sont inchangés et les tests sont toujours au vert. Si les tests unitaires sont conçus pour vérifier des scénarios complets au lieu d'appels à un seul membre, la refactorisation entre dans cette catégorie.
- Refactoring impactant plusieurs classes : dans ce cas, les tests d'acceptation sont le seul moyen de vérifier la fonctionnalité complète après le refactoring. Ceci est utile si une nouvelle fonctionnalité est fournie et implémentée en modifiant la classe après la classe et les tests unitaires associés.
Un aspect important à retenir pour la refactorisation dans TDD est que lorsque le code est refactorisé, ce qui a un impact sur l'interface/l'API, tous les appelants de cette interface/API ainsi que les tests écrits pour l'interface/l'API doivent être modifiés dans le cadre de la refactorisation. Conformément à la définition, le refactoring est un changement « préservant le comportement ».
Les bonnes pratiques de refactoring
Pour extraire de la valeur du refactoring, il est important de garder à l'esprit certaines bonnes pratiques :
1- Refactoriser uniquement le code qui a des tests unitaires
Comme susmentionné, les tests unitaires permettent de vérifier le bon fonctionnement d'une unité d'un système applicatif. Ils s’assurent qu’une méthode reste fonctionnelle telle qu'elle a été conçue même après un nombre de modifications. En effet, refactoriser du code sans test enclenche un risque de casser le code.
A noter que dans certains cas, la refactorisation du code passe les tests mis en place même s'ils cassent le code. Dans ces cas, ce sont les tests qui sont de bas niveau. Il est alors possible de les refactoriser ou carrément en écrire de nouveau de niveau supérieur.
2- Utiliser des étapes incrémentielles
La refactorisation doit être effectuée sous la forme d'une série de petits changements, chacun d'entre eux améliorant légèrement le code existant tout en laissant le programme en état de marche.
3- De nouvelles fonctionnalités ne doivent pas être créées lors de la refactorisation
Il est important de ne pas mélanger refactoring et développement direct de nouvelles fonctionnalités. Essayez de séparer ces processus au moins dans les limites des commits individuels.
4- Rester consistant
Practice makes perfect. Le refactoring est une question de pratique et de discipline constantes. Ce n'est qu'en le faisant d'une manière consistante sur ces projets qu'il devient une seconde nature.