Un jour, un des développeurs de mon équipe a annoncé qu'il devait refactorer un bout de code, car il le trouvait sale. Je lui ai alors demandé plus de détails quant au type de refactoring. Était-ce à cause du nommage ou était-ce plus profond ?
En creusant un peu, il s'est avéré qu'il avait en fait mis du temps à comprendre le code et cela n’était pas en accord avec sa façon de faire. Dans l'open-space, le chef d'équipe qui suivait la discussion, a rebondi en insistant « Il ne faut pas changer un code de production s'il n'y a pas de bug remonté ».
Je n'étais et je ne suis toujours pas d'accord avec ce point de vue. Je considère que si un code est trop compliqué à appréhender, il doit être refactoré surtout s'il est bien couvert par des tests.
Mais alors, quand est-ce qu’on refactore ?
Je trouve qu'il y a plusieurs raisons de faire du refactoring :
• Quand un code est considéré comme sale
• Quand un code est incompréhensible
• Pour préparer une nouvelle fonctionnalité
• Planifier un refactoring non urgent pour plus tard
• Refactoring de longue haleine (long terme)
Refactorer un code sale
La définition d'un code sale est propre à chacun. Elle peut être aussi déterminée au niveau de l’équipe via des règles. Une règle de nommage par exemple, peut aider à éviter du code sale.
Prenons l’exemple ci-dessous, nous avons une méthode qui retourne un résultat à partir de deux entiers.
public double GetResult(int int1, int int2) { if (int2 == 0) { throw new InvalidOperationException("Can't divide by zero !"); } return (double)int1 / (double)int2; }
À partir de la signature, on ne peut pas déterminer ce qu’elle fait. Il faut vérifier l’implémentation pour comprendre qu’il s’agit de diviser un numérateur par un dénominateur. Ce code peut donc être refactoré comme suit en nommant correctement les variables à minima.
public double Divide(int numerator, int denominator) { if (denominator == 0) { throw new InvalidOperationException("Can't divide by zero !"); } return (double)numerator / (double)denominator; }
Prenons un autre exemple, la classe "File" a une propriété Name avec un setter publique.
public class File { public File() { } public string Name { get; set; } }
Certains ne trouveront pas ce code choquant et pourtant cette classe ne respecte pas l'immutabilité. Si dans l’équipe, l’immutabilité doit être respectée, il vaut mieux créer un champs privé initialisé via le constructeur et à minima avoir un getter pour accéder à la valeur, voire même supprimer la propriété pour rajouter du comportement à la classe comme préconisé dans les règles des objets calisthenics ("No getters/setters properties") ou "Tell, don't ask".
Le résultat doit être donc comme suit.
public class File { private string name; public File(string name) { this.name = name; } public string Name { get { return this.name; } } }
Refactorer un code incompréhensible
Parfois, je mets du temps à comprendre un bout de code. Je le lis, je le débugue pour essayer de comprendre. Je schématise les classes, les méthodes et le lien entre elles. A la fin mon schéma ressemble à un sac de nœuds que je dois démêler.
Cette motivation est proche de la première, mais elle n'a pas la même intention. En effet, le collègue qui passera après mettra le même temps ou plus pour comprendre le code en question, si ce dernier ne change pas. C'est pourquoi je considère que ce refactoring est nécessaire pour gagner du temps pour délivrer les fonctionnalités futures. Je me pose toujours ces questions « Pourquoi les développeurs préfèrent écrire du code compliqué ? C'est pourtant tellement simple d'écrire du code lisible et compréhensible. Pourquoi ne pas appliquer la règle des Boy Scout qui se résume en une phrase : « Toujours laisser un endroit dans un meilleur état que celui où vous l’avez trouvé » ? »
Refactorer pour préparer une nouvelle fonctionnalité
Vous devez rajouter un traitement dans une méthode existante, mais en l'état c'est compliqué à faire. Vous décidez donc de simplifier le code en extrayant une classe par exemple.
Ce type de refactoring vous permet de gagner du temps de réalisation de la nouvelle feature.
Refactoring planifié
Vous être en train de rajouter du code pour la nouvelle feature, vous tombez sur du code à refactorer, mais vous n'avez pas le temps ou vous ne voyez pas l'intérêt de le faire à ce moment. Vous décidez donc de le noter sur un post-it, un ticket Jira ou un autre support pour ne pas l'oublier et pour le prioriser par la suite. C'est une bonne initiative car la livraison de la fonctionnalité ne sera pas impactée et en même temps, vous prendrez le temps pour le faire proprement juste après.
Refactoring de longue haleine (long terme)
Il s'agit d'un grand chantier de refactoring comme une nouvelle vision ou une nouvelle architecture de l'application. Vous allez par exemple passer d'un monolithe à une architecture micro-services. C'est un chantier qui risque de prendre du temps. Il vaut mieux donc déterminer les étapes à suivre avant de commencer directement à refactorer le code, voir les impacts sur l'application et si elles (les étapes) sont toujours compatibles avec de potentielles évolutions en attendant l'architecture cible.
Mais en vrai pourquoi refactorer ?
La réponse que j'entends souvent est "pour avoir un code de meilleure qualité" ou encore "pour avoir du code propre" ou même "pour faire les choses proprement". Au fil des expériences, j'ai vu que ces arguments ne valaient rien en face des clients, qui considèrent que ce type de refactoring est une perte de temps. A leurs yeux, l'application est en production, fonctionne correctement et n'a donc pas besoin de modification avec le risque d'y apporter des régressions.
Pourtant je reste convaincue qu'une des facettes de notre mission en tant que consultant est de s'assurer que le code soit clean et s'améliore lorsqu'on intervient dessus. Ça fait partie de l'hygiène de vie de l'application.
Je représente souvent l'application comme un bébé. Ce dernier ne sait pas se laver tout seul. Il faut donc le surveiller, lui faire sa toilette si on trouve qu'il en a besoin. Une bonne hygiène lui permet de rester en bonne santé, éviter les infections et surtout d'avoir une bonne croissance. Pour une application c'est pareil. Il faut l'améliorer en continu avec du refactoring régulier.
Si vous vous trouvez à court d'argument, en voici un qui a le mérite d'exister. Il faut en faire pour faciliter l'ajout des fonctionnalités futures. L'amélioration continue du code permet de gagner du temps le jour où il faut passer sur le code en question pour y ajouter une nouvelle feature ou corriger un bug. Plus on refactore pour obtenir un bon design plus les fonctionnalités futures sont rapides à développer comme le montre le schéma de Martin Fowler sur le "Design Stamina Hypothesis"
Source : https://www.martinfowler.com/bliki/DesignStaminaHypothesis.html
Mais quand s’arrêter ?
Faites attention avec votre bébé. Il est fragile tout de même. Il ne faut pas le laver souvent non plus, car ça risque d’irriter sa peau surtout si l’eau est calcaire !
Un refactoring n'est pas une mince affaire. Il ne faut pas prendre le sujet à la légère et surtout il vaut mieux éviter de prendre la décision seul. En tant que développeur et surtout membre d'une équipe, votre devoir est d'informer les autres de vos intentions de refactoring pour deux raisons. La première est que vous êtes dans une équipe et donc c'est important de communiquer et de partager. Il ne faut pas tomber dans le piège de la demande de permission non plus. Vous êtes majeur et responsable. Vous devez convaincre les autres des bienfaits de votre démarche. Et la deuxième raison est pour éviter un éventuel problème de merge par exemple surtout si vous travailler en mode feature branching.
C'est la responsabilité de chaque développeur de se demander si ce bout de code nécessite d'être refactoré. Posez-vous toujours ces questions "Est-ce que le code, tel qu'il est, me ralentit pour l'ajout d'une nouvelle feature ? Si je le modifie, est-ce que ça va me permettre de gagner du temps pour développer ma feature ?"
Si la réponse est oui alors informez l'équipe et faites le tout simplement. J'ai envie de dire que ça fait partie de la feature à développer. Par contre si la réponse est non ou si vous n'êtes pas sûr, je vous conseille de ne pas le modifier et d’attendre le moment où cela sera vraiment nécessaire. Sinon faites-le dans le cadre d'un refactoring planifié.
Enfin, je vous invite à toujours garder ces phrases en tête : "Refactoring is about an economic argument. Clean code allows you to go faster. That’s the only justification behind it." comme l'a annoncé Martin Fowler lors de sa conférence "Workflows of refactoring". Il ne s'agit donc pas d'un dogme, mais d'un raisonnement économique raisonnable !
J'espère que vous êtes mieux armés maintenant.
Bon courage et à bientôt pour un nouvel article 🙂
Ref :
https://www.youtube.com/watch?v=vqEg37e4Mkw
https://williamdurand.fr/2013/06/03/object-calisthenics/
https://dzone.com/articles/motivation-for-software-architecture-refactoring?utm_medium=twitter&utm_source=dlvr.it&utm_campaign=Feed:%20dzone%2Fdevops