Comment nettoyer un code sale

Rappels

À l’ère du code propre et de la ‘mentalité Software Craftsmanship’, le code legacy fait figure d’épouvantail pour le nouveau panel de développeurs. D’ailleurs, peu importe quel type de développeur vous interrogez, ils auront tous la même mine de dégoût lorsque vous leur annoncerez qu’ils doivent reprendre un ancien code ou tout simplement celui de quelqu’un d’autre. Code dépassé, trop vieux, sale, plein de bug ; autant de caractéristiques qui déterminent aujourd’hui le code legacy pour la communauté des programmeurs.  

Aujourd’hui, il n’existe pas de définition précise du code legacy, la seule s’en rapprochant est celle de Michael Feathers, décrite dans son livre WorkingEffectivelywithLegacy Code : « To me, legacy code issimply code without test », traduisez par : « Pour moi, le code legacy n’est rien d’autre qu’un code programmé sans tests ». 

Malgré tout, les caractéristiques d’un code legacy restent trop changeantes et trop subjectives pour que l’on puisse en déterminer l’ensemble de ses variables problématiques. Le seul point que l’on pourrait considérer comme « d’objectivité commune » est l’absence de tests. 

C’est d’ailleurs, de mon point de vue, la clé de voute de ce qui va ensuite nous permettre de « nettoyer » ce code : l’ajout de ces tests manquants. Cependant, c’est la où la première difficulté majeure apparaît : comment rendre le code testable et surtout comment rédiger les tests adéquats pour ce type de code (langage, framework, etc.)  

D’aucuns vous diront qu’il est obligatoire de réécrire entièrement le code legacy, je préférerais angler cet article sur le fait de ‘simplement’ réussir à faire évoluer ce même code et le métamorphoser en code maintenable. 

Pourquoi s’embêter à rechercher les bonnes fonctionnalités, les variables sans bugs au lieu de tout simplement recommencer à zéro avec les bons principes et les bonnes méthodologies ? Et bien je répondrais à cela que dans tout code, même le plus sale, il existe des fonctionnalités, voire une architecture qui aura effet de valeur ajoutée pour l’entreprise.

Ainsi, même mal conçu, un code legacy peut posséder en son sein de véritables pépites qu’il serait dommage de supprimer sans prendre le temps de s’y intéresser. Même si vous prenez le temps de bien notifier les fonctionnalités avant de tout supprimer, vous prenez le risque que votre nouvelle implémentation n’ait que les traits de code de semblable à son ancêtre tout en ne fonctionnant pas de la façon attendue par le PO (product owner).  

Avant de refactorer

C’est ici que les choses se corsent véritablement. Il va tout d’abord vous falloir estimer le nombre, le coût, le temps passé et la faisabilité des tests à effectuer. Il est aussi primordial d’accepter de passer du temps à cette estimation et à la mise en place des tests 

Vous l’aurez compris, après avoir passé quelques dizaines d’heures à établir votre pronostic, il vous faudra passer des centaines d’heures à intégrer des tests appropriés absolument partout. Pourquoi le « partout » est-il en gras ? La cause est simple : si vous ne vous attardez que sur certains des problèmes de ce code – le plus souvent pour aller vite – vous passerez bien plus de temps à découvrir de nouveaux problèmes qu’à pouvoir y apporter de véritables solutions et effectuer vos tests.  

Il faut donc passer du temps à l’examination et l’intégration de tests !  

Conseils et solutions en phase test

Le premier pas dans l’évolution d’un ancien code vers un code plus propre et mieux maintenu consiste à appeler les différentes fonctionnalités du programme afin d’en analyser la réaction, puis concevoir les tests automatisés qui nous permettront ensuite de conserver les comportements fonctionnels lors de la phase de refactoring. 

Cette partie de la résolution problème peut porter deux noms. Le premier est la phase de tests de caractérisation mais vous la trouverez plus souvent sous le nom de « Golden Master». L’intégration de ces tests vous permettra ensuite d’avoir les informations préalables à toute modification et notamment une arborescence des différentes fonctionnalités et parties du code à modifier par la suite.  

Ainsi, il va falloir procéder en format test « boîte noire », c’est-à-dire en partant du postulat que nous n’avons aucune connaissance des méthodologies et des implémentations préalablement effectuées. Ces tests se limitent à savoir si les fonctionnalités répondent à ce qu’on leur demande sans s’attarder sur leur « façon » de le faire. L’objectif étant à terme de détecter si tous les inputs valides sont acceptés et inversement, que les non valides sont rejetés, la sortie attendue doit être au rendez-vous. C’est la méthode « trial and error ».  

Avant que je vous perde complètement, revenons à notre technique de Golden Master(tests de caractérisation).

En premier lieu, nous allons aller au plus simple en créant une méthode test par fonction à refactorer. L’important est de bien penser à stocker les données obtenues lors de chaque appel et leurs paramètres dans un fichier indépendant. Ce jeu de données est essentiel à la poursuite du processus. 

Dans un second temps, récupérez toutes les données et paramètres stockés afin de créer une nouvelle méthode de test. Comparez en les résultats afin d’obtenir la certification que les résultats sont identiques. 

Info
Pensez à intégrer le plus de paramètres possible dans le test afin de tester et vérifier le maximum de cas !

Il vous suffit dorénavant de modifiez le code source, mais attention cela ne doit être fait que lorsque les résultats obtenus sont strictement identiques ! Enfin, relancez une dernière fois le test afin de valider si l’implémentation dans le code source nous permet d’obtenir les mêmes résultats. 

Info
Une fois ces résultats obtenus, vous pouvez tout à fait utiliser les méthodes de tests unitaires ou métiers afin de sécuriser au maximum votre refactoring.