Quelles stratégies pour maintenir ou sauver un code legacy ?

Beaucoup de développeurs baissent les bras en présence de code legacy. Il est vrai que "code legacy" est souvent synonyme de complexité, d'instabilité, ou de projet de migration très lent et long à réaliser.

Il existe pourtant des solutions simples pour faire évoluer un code legacy tout en continuant à livrer de nouvelles versions de son application.

Comment s’y prendre ?

Oubliez tout de suite vos velléités de tout révolutionner du jour au lendemain. La stratégie la plus efficace consiste à aborder le problème par étapes simples, et rapides. En procèdent de la sorte, on est certain d’avoir un impacte régulier. On évite aussi les blocages liés aux développements qui doivent continuer en parallèle.

L’évolution d’un code legacy passe donc par un refactoring progressif et régulier. La maintenance ou l’évolution suit le même principe : il faut débuter par refactoriser la partie du code qui rend le nouveau développement trop complexe ou difficile. Ceci vous évitera de creuser votre dette technique.

Pour où commencer ?

Certaines personnes préfèrent commencer en réalisant le profilage de l’application (usage de la RAM, du CPU et du GC). Cette approche n’est véritablement envisageable que si l’application a un nombre de fonctionnalités réduites.

Mais quand on parle de code legacy, il est rare que l’on ait affaire à une pette application. Le profilage n’est véritablement envisageable que si l’on dispose de personnes étant en mesure d’établir le chemin critique de l’application (cette série de fonctionnalités critiques dont les utilisateurs ne peuvent pas se passer, ou qui couvrent au moins 90% des usages quotidiens).

Si vous êtes dans ce contexte, vous pouvez établir la liste des codes dont le refactoring est prioritaire.

Dans le cas contraire, il convient d’identifier les méthodes et classes les plus utilisées, ou celles dont le code est le plus long (ex : classes de plusieurs milliers de lignes, ex: méthodes ayant plus de 5 arguments).

Dans le cas où votre objectif est d’ajouter une fonctionnalité, de la faire évoluer ou de la corriger, il faut identifier le code qui vous complique la compréhension de cette fonctionnalité ou son implémentation.

Quels outils ?

Par le passé, j’ai testé plusieurs outils de qualité de code ou d’identification de la dette technique. J’ai déjà perdu beaucoup de temps à cause de ceux-ci. Ils donnent souvent beaucoup trop d’indications (parfois difficiles à filtrer ou ordonner), et ne sont pas toujours adaptés à .net (conseils totalement décalés, ex : utilisez des SecureString… alors que cela est déconseillé depuis de nombreuses années).

Ma toolbox se limite donc à deux outils :

Quelles méthodes ?

Pour chaque problème, il y a une solution. En général :

  • Un code legacy est tout sauf simple -> KISS.
  • Un code legacy reproduit souvent les mêmes patterns, encore et encore… mais de manière un peu trop complexe ou datée. Ce qui aboutit inévitablement par des pans entiers de codes produits par copier-coller -> DRY ! DRY ! DRY !
  • Un code legacy joue souvent les couteaux suisses (aussi appelé « papa, maman » par certains développeurs) -> Il faut donc séparer les responsabilités entres plusieurs classes. Ce qui nous amène à SOLID.
  • Un code legacy est souvent difficile à faire évoluer -> SOLID.
  • Un code legacy intègre souvent des méthodes dont on ne connait pas l’intérêt, pour des fonctionnalités que les développeurs ne comprennent pas toujours -> Le TDD est la solution pour identifier et documenter une bonne fois pour toutes ces méthodes et leurs usages (tout en renforçant la fiabilité et en évitant les régressions).

Côté méthode, je serai donc tenté de dire que c’est dans les vieux pots que l’on fait les meilleures soupes. KISS, DRY, et SOLID sont les approches qui doivent guider votre travail. Le code legacy doit progressivement respecter ces principes pour ne plus nuire à votre application et permettre les évolutions que vos utilisateurs ne manqueront pas de vous demander.

TDD et / ou TDR?

Le TDD peut vous sembler impossible à appliquer. Un code legacy est rarement accompagné de tests unitaires. Il est aussi souvent impossible à tester. Quand j’évoque le TDD, il ne faut pas avoir peur. L’objectif n’est pas d’arriver à une couverture de code de 100%.

La méthode à suivre porte le nom de Test Driven Refactoring (TDR) :

  • Avant de modifier un code, on crée un jeu de tests.
  • Si la méthode ou la classe n’est pas testable, on déplace le code à tester dans une nouvelle classe testable. Bien évidemment la classe d’origine évolue pour utiliser cette nouvelle classe.
  • Quand les tests sont opérationnels, on peut procéder au refactiring ou à l’évolution du code legacy.

En procédant petit à petit, l’ensemble du code legacy et non testable finit par respecter les principes KISS et SOLID.

Conclusion

Un code legacy peut être maintenu. Il peut aussi évoluer. Il suffit d’être méthodique et de procéder à un refactoring par petites touches (ou en fonction des demandes d’évolution).

Il ne faut pas imaginer qu'il soit possible de laisser un code legacy de côté. Quand un changement devra être opéré sur ce code, cela aboutira inévitablement à une perte de productivité et probablement à des régressions. Ces régressions conduiront finalement les développeurs à craindre le changement. Ce qui finira par avoir un impact sur leur moral, et qui réduira davantage leur productivité.

Dans les pires cas, il arrive que des développeurs dupliquent le code legacy pour implémenter une nouvelle fonctionnalité… Si les développements suivants appliquent la même logique, le code legacy augmente, la dette technique aussi et le projet fini dans le mur.

Jérémy Jeanson

Comments

You have to be logged in to comment this post.