Theodo apps

Maitriser le refactoring: partie 2 - le manuel pour les développeurs

Le refactoring est une activité quotidienne pour les développeurs, mais elle est souvent perçue comme complexe. D'après les statistiques sur mon projet précédent, un mauvais refactoring se classe au 6e rang parmi les causes les plus fréquentes d’introduction de bugs. Cependant, ne pas refactorer n'est pas une solution, car c'est un processus essentiel pour maintenir la qualité et l’évolutivité d’un projet logiciel. Au fil du temps, la dette technique s'accumule, ce qui ralentit le développement, démotive les intervenants du projet, impacte la stabilité des livrables et freine la mise à l’échelle d'un projet. Le refactoring est un outil indispensable non seulement pour prévenir l’augmentation de la dette, mais également pour la réduire dans les cas les plus extrêmes.

J'ai récemment assisté à des conférences lors de l’événement Mobilis in Mobile et suivi une formation sur ce thème. J'aimerais partager les points clés sur le refactoring et comment le maîtriser. Cet article est le deuxième d'une série de trois, et il vous aidera, en tant que développeur, à réaliser un refactoring sans introduire de bugs.

La Connaissance

Refactoriser, c'est aspirer à un code de meilleure qualité. Pour y parvenir, il est crucial de connaître les principes qui définissent un "bon" code et de savoir reconnaître les signes d'un "mauvais" code, souvent appelés code smells. Voici quelques théories fondamentales pour vous guider.

Clean Code

Au cœur de la compréhension du code de qualité se trouve le livre Clean Code de Robert C. Martin, alias Oncle Bob. Cet ouvrage regorge de règles essentielles du développement, classées en catégories : règles générales, tests, commentaires, fonctions, nommage, etc. Chaque règle est associée à un code smell, un indicateur commun de code défaillant.

Dans une approche similaire, consultez le site Refactoring Guru, qui liste également ces code smells.

Design Patterns

Même en maîtrisant les bonnes pratiques de Clean Code, vous pouvez rencontrer des situations complexes où ces principes ne suffisent pas. Les design patterns, ou patrons de conception, sont des solutions éprouvées pour résoudre des problèmes spécifiques. Les connaître en profondeur est crucial pour construire des logiciels robustes. Toutefois, une mauvaise application peut complexifier le code et augmenter la dette technique. Utilisez-les judicieusement.

Exemple pratique :

Dans le cas d’une évolution future, nous avons déjà les classes W et X, et nous voulons ajouter les classes Y et Z, définies comme suit :

Plutôt que de copier-coller les méthodes doA et doC encore une fois dans mon code, limitant ainsi la maintenabilité, avant de faire l’évolution, je vais appliquer un refactoring vers le pattern decorator.

Les 5S

Inspirée du système de production de Toyota, la méthode des 5S optimise l'organisation du lieu de travail. Adaptée au développement, elle améliore la qualité du code :

  1. Seiri - Éliminer : Supprimez le code inutile ou obsolète.
  2. Seiton - Ranger : Organisez le code pour le rendre accessible et compréhensible, à l’endroit où l’on s’attend à le trouver.
  3. Seiso - Nettoyer : Maintenez un style de code cohérent pour faciliter la détection des erreurs.
  4. Seiketsu - Standardiser : Établissez des normes pour les 3 premiers "S" et partagez-les dans l'équipe.
  5. Shitsuke - Maintenir : Assurez-vous que ces principes sont continuellement appliqués, notamment via des revues de code.

SOLID

Les principes SOLID sont cinq règles fondamentales pour un code orienté objet maintenable :

  • S - Single Responsibility Principle : Une classe ou méthode ne doit avoir qu'une seule responsabilité, donc une seule raison d’être modifiée.
  • O - Open/Closed Principle : Le code doit être ouvert à l'extension, mais fermé à la modification. Vous pourrez ajouter du code sans altérer l’existant.
  • L - Liskov Substitution Principle : Les sous-classes doivent pouvoir remplacer leurs superclasses sans altérer le comportement du programme.
  • I - Interface Segregation Principle : Préférez plusieurs interfaces spécifiques à une interface générale, afin de réduire les couplages inutiles.
  • D - Dependency Inversion Principle : Dépendre des abstractions plutôt que des implémentations concrètes. Cela favorise la modularité, la flexibilité et la réutilisabilité.

DRY

Le principe DRY (Don't Repeat Yourself) stipule qu'il ne faut pas dupliquer le code. Chaque fonctionnalité doit avoir une implémentation unique. Cela favorise la maintenabilité et réduit les erreurs. Cependant, dans certaines architectures comme les microservices, une duplication contrôlée peut éviter des couplages indésirables.

KISS

Le principe KISS (Keep It Simple, Stupid) encourage la simplicité dans le code. Un code simple est plus facile à comprendre, à tester et à maintenir. En somme, KISS est l'essence même du refactoring : rendre le code plus clair et accessible.

La Méthode

Armé de ces connaissances, vous pouvez désormais identifier le mauvais code. Chaque code smell a une ou plusieurs techniques de refactorisation associées.

Ce qu'il ne faut pas faire

Avant de commencer, évitez ces erreurs courantes :

  • Ignorer la dette technique.
  • Entreprendre un long refactoring en silo.
  • Refactoriser sans consensus.
  • Mélanger évolution et refactoring dans un même commit, car cela complique la relecture et augmente le risque d'erreurs.
  • Combiner plusieurs refactorings dans un commit. Gardez les changements atomiques pour faciliter la revue et éviter la surcharge mentale.
  • Refactoriser du code non couvert par les tests. Sinon, vous ne pouvez pas garantir le bon fonctionnement du code modifié.

Les Points Clés

Pour un refactoring réussi :

  • Invariant Fonctionnel : Le comportement du programme ne doit pas changer du point de vue de l'utilisateur (ou du consommateur de la ressource dans le cas du backend).
  • Amélioration du Code : Le code doit être plus lisible et mieux structuré après refactoring.
  • Facilitation des Développements Futurs : Le code refactorisé doit simplifier les évolutions à venir.

Pour chaque commit :

  • Atomique: Chaque commit doit représenter une action unique.
  • Couvert: Les modifications doivent être couvertes par des tests.
  • Stabilité : Les tests doivent passer avant de valider le commit.

Appliquer les Méthodes en Respectant les Points Clés

Pour réaliser un refactoring efficace, il est essentiel de respecter les points clés évoqués précédemment. Commencez par identifier le code smell grâce à vos connaissances, puis appliquez la technique de refactorisation appropriée. Vous pouvez trouver ces techniques détaillées dans le livre Refactoring de Martin Fowler ou sur le site Refactoring Guru.

Exemple pratique :

Imaginons que vous ayez repéré une méthode excessivement longue et complexe. Le corps de cette fonction dépasse 100 lignes, et les structures conditionnelles sont si confuses qu'elles rendent le code difficile à lire et à maintenir.

Voici comment vous pourriez procéder :

  1. Vérifiez la Couverture de Tests : Assurez-vous que cette méthode est bien couverte par des tests unitaires. Si ce n'est pas le cas, rédigez les tests nécessaires et faites un commit. Cela vous garantit que les fonctionnalités existantes restent inchangées pendant le refactoring.
  2. Identifiez la Technique de Refactoring Appropriée : Pour une fonction trop longue, la technique recommandée est l’extraction de méthode. Cette approche consiste à décomposer la fonction en sous-méthodes plus petites et cohérentes.
  3. Effectuez le Refactoring par Étapes : Commencez à extraire des parties du code en créant de nouvelles méthodes. Faites-le progressivement, en vous concentrant sur une section à la fois pour minimiser les risques d'erreur.
  4. Validez les Tests à Chaque Étape : Après chaque modification, exécutez vos tests unitaires. Si tous les tests passent, vous pouvez committer les changements. Si des tests échouent, corrigez immédiatement les problèmes avant de poursuivre.
  5. Adressez les Conditions Complexes : Une fois la méthode décomposée, attaquez-vous aux structures conditionnelles compliquées. La technique adaptée ici est le décomposer les conditionnelles, qui vise à simplifier et clarifier les instructions if, then et else.
  6. Répétez les Tests et Commits : À chaque étape stable du refactoring, par exemple, après avoir simplifié une condition, exécutez à nouveau les tests. Si tout est en ordre, commitez vos changements avant de passer à la prochaine section.

En suivant cette démarche structurée, vous améliorerez progressivement la lisibilité et la maintenabilité de votre code sans introduire de bugs. Chaque étape validée par les tests vous assure que le comportement du programme reste inchangé du point de vue de l'utilisateur, tout en facilitant les développements futurs.

Utilisez la force de votre IDE

La plupart des IDE offrent des fonctionnalités de refactorisation automatisées (extraction de méthode, renommage, etc.). Utilisez-les pour gagner du temps et réduire les erreurs.

La Méthode Mikado

Pour les refactorings trop complexes pour être traités comme expliqué précédemment, la méthode Mikado est une approche efficace. Chaque bâton représente une partie du code que vous voulez modifier. Si vous essayez de tout changer en une seule fois, vous risquez de tout faire tomber, c'est-à-dire de provoquer des bugs ou de casser des fonctionnalités.

  1. Définissez clairement ce que vous voulez accomplir comme refactoring.
  2. Faites le changement et observez ce qui casse.
  3. Identifiez les obstacles: ce qui casse, ce qui doit être modifié en amont pour que votre changement précédent puisse fonctionner.
  4. Mettez vos changements de côté (stash ou branche à part), puis récommencer au 1. avec pour objectif l’obstacle rencontré

En résolvant les dépendances une par une, récusivement, vous atteindrez votre objectif sans casser d’autres éléments du code, rendant le refactoring plus sûr et contrôlé. Cette approche est particulièrement utile pour les refactorings à grande échelle. Il est conseillé de construire un Mikado-graph pour suivre toutes vos dépendances.

Exemple pratique :

Pour mieux comprendre la méthode Mikado, prenons une analogie avec nos héros bien connus, Han Solo et Chewbacca, qui cherchent à améliorer le Faucon Millenium pour échapper plus rapidement à l'Empire.

Jour 1 : L'achat de l'hyperpropulseur

Han décide d'acheter un nouvel hyperpropulseur pour augmenter la vitesse du vaisseau. Plein d'enthousiasme, il enlève l'ancien et installe le nouveau. Mais lorsqu'il tente de le connecter, il se rend compte que les câbles existants ne sont pas compatibles avec ce nouvel équipement.

Conscient du problème, Han remet l'ancien hyperpropulseur en place pour que le Faucon Millenium reste opérationnel. Il réalise qu'il doit d'abord s'attaquer au remplacement des câbles avant de pouvoir installer le nouvel hyperpropulseur.

Jour 2 : Le remplacement des câbles

Le lendemain, Chewbacca se charge d'installer de nouveaux câbles adaptés. Ils lancent une vérification via l'ordinateur de bord, mais une nouvelle erreur apparaît : l'alimentation électrique du vaisseau doit être reprogrammée pour supporter ces câbles récents. Plutôt que de laisser le vaisseau dans un état instable, Chewbacca remet les anciens câbles en place, gardant ainsi le Faucon Millenium prêt à décoller en cas d'urgence.

Jour 3 : La reprogrammation et l'installation finale

Le troisième jour, Han et Chewbacca décident de reprogrammer l'alimentation électrique pour qu'elle soit compatible avec les nouveaux câbles. Une fois cette étape réalisée, ils installent les câbles et replacent le nouvel hyperpropulseur. Cette fois-ci, tous les systèmes fonctionnent parfaitement. Le Faucon Millenium est désormais plus rapide et prêt à semer l'Empire à tout moment.

Les Autres Méthodes

Il existe d'autres techniques, comme l'utilisation de patterns Proxy ou Facade pour isoler le code en cours de refactorisation. Cependant, ces méthodes peuvent ajouter de la complexité et potentiellement introduire des bugs. En étant plus permissives, elles laissent la possibilité de laisser échapper une erreur. Il est généralement préférable de s'en tenir aux techniques éprouvées.

Et Ensuite ?

Vous avez compris d’où vient la dette technique grâce au premier article de cette série. Vous avez maintenant une compréhension approfondie des méthodes pour refactoriser sans introduire de bugs grâce à ce dernier. Le défi restant est de trouver le temps pour le faire. Si les petits refactorings peuvent s'intégrer aisément dans votre flux de travail, les plus importants nécessitent une planification et une communication avec votre équipe ou vos clients. Dans le prochain et dernier article de cette série, nous explorerons comment démontrer au reste de l’équipe que l**'investissement dans la qualité du code est bénéfique pour tous**.

Les ressources

Développeur mobile ?

Rejoins nos équipes