Application Legacy – Refactoring ou reengineering? (IV)

Qualilogy-Legacy-Reengineering-RefactoringAu retour des vacances estivales, je reprends cette série de posts sur l’utilisation de SonarQube avec une application Legacy en C, en l’espèce la première version de Word publiée par Microsoft en 1990.

Nous avons posé l’hypothèse suivante : Microsoft vient de se faire racheter et son nouveau propriétaire vous demande, en tant que consultant Qualité, de recommander une stratégie concernant cette version de Word.

Ne croyez pas que cela n’arrive jamais : des boîtes de software se font racheter tous les jours, et la R&D et le code de ces logiciels sont au cœur de ces acquisitions.

Plus précisément, vous devez répondre à ces trois questions :

  • Quel serait le coût pour transférer cette application à une nouvelle équipe de R&D ? C’est la question qui se pose chaque fois que l’on souhaite outsourcer une application Legacy à une société de services.
  • Quel serait le coût d’un refactoring de cette application, afin d’en améliorer la qualité et réduire ainsi les coûts de maintenance ? Toute application Legacy se caractérise généralement par une dette technique élevée. Un refactoring permettrait de réduire les intérêts de cette dernière, et donc les coûts de transfert de connaissances vers un outsourcer et/ou les coûts de maintenance par celui-ci.
  • Quel serait le coût de reengineering de cette application ? Un refactoring signifie très souvent de repenser le design, c’est-à-dire la conception voire l’architecture de l’application. Pourquoi ne pas en profiter pour la porter dans un autre langage, plus récent, moins difficile à maintenir ?

Evidemment, il vous faut non seulement tenter de répondre à ces 3 questions, mais également proposer un plan d’action pour chacune de ses stratégies.

Nous avons vu comment analyser ce code Legacy avec SonarQube, et les résultats obtenus en matière de métriques – taille (LOCs), complexité (CC), commentaires et duplications – ainsi que  les différentes ‘Issues’ de type Blocker, Critical, Major et Minor.
Nous avons également vérifié l’existence de composants ‘monstres’ : fonctions et programmes volumineux et complexes, comportant un nombre élevé de violations aux bonnes pratiques de programmation, notamment en matière de lisibilité et de compréhension du code.

Legacy code

Qu’est ce que du code Legacy ?

A l’origine, il s’agit d’une application ‘héritée’ d’une génération précédente de programmeurs, développée dans un langage d’une génération antérieure et s’exécutant sur une plateforme (hardware, OS, …) également ancienne, voire non supportée. L’expression désignait essentiellement du code Mainframe Cobol.

Aujourd’hui, les jeunes générations vont plus souvent l’utiliser pour désigner des applications anciennes (plus de 5 à 10 ans) et/ou sans interface web et/ou écrites dans des langages non orientés-objet (C, Forms, Powerbuilder, Visual Basic, etc.)

Dans tous les cas, le terme ‘Legacy’ évoque un code difficilement compréhensible, avec une dette technique importante et une documentation souvent éparse et pas à jour.

Edit and Pray

Selon Michael Feathers, auteur très connu d’un livre très recommandé – « Working Effectively with Legacy Code » – le code Legacy se caractérise par l’absence de tests.

Dans le chapitre ‘2 – Working with Feedback’ de son livre, il décrit un processus très courant, notamment chez les R&D des éditeurs de software : « Edit and Pray », qui peut se traduire par ‘Change et Prie’ (pour que cela marche). Face à une demande d’évolution, le développeur va planifier soigneusement sa tâche, s’assurer au préalable qu’il comprend parfaitement le code à modifier et les éventuels impacts, implémenter l’évolution, compiler et vérifier que celle-ci fonctionne correctement, et effectuer quelques tests supplémentaires, ici et là, dans les zones éventuellement impactées, afin de s’assurer qu’il n’a rien cassé. Ensuite, il va passer son évolution à une équipe de testeurs qui va réaliser une batterie de tests de régression et autres tests plus ou moins automatisés, afin de s’assurer du bon comportement de l’application.

Dans le meilleur des cas, ces tests seront effectués durant la nuit, et notre développeur en connaîtra les résultats le lendemain, et donc si son code est valide, ou tout au moins validé. Le plus souvent, l’ensemble des évolutions de la semaine sera testé en fin de semaine, avec quelques tests manuels additionnels, et cette validation n’interviendra que la semaine suivante. Une autre pratique encore très courante aujourd’hui chez les éditeurs logiciels consiste à définir :

  • une date de sortie d’une nouvelle version du software, par exemple le 15 octobre ;
  • une période de développement pendant laquelle les évolutions sont implémentées, par exemple jusqu’au 20 septembre ;
  • une période de QA (entre le 20 septembre et le 15 octobre) pendant lesquelles toutes les demandes d’évolution sont gelées, la nouvelle version passe dans les mains de l’équipe de QA, plus aucun nouveau développement n’est lancé et les développeurs se dédient uniquement à la correction des problèmes découverts par la QA.

Cover and Modify

Une autre manière de travailler selon Michael Feathers consiste en une couverture de tests unitaires afin de protéger notre code des conséquences de toute modification, comme un filet de sécurité. Quels sont les avantages d’une bonne couverture de tests ?

  • Vous disposez d’une batterie de tests que vous lancez chaque fois que vous réalisez une modification, avec succès la plupart du temps. Parfois, vous commettez une erreur, par exemple de logique métier dans une condition, et un ou plusieurs des tests échoue(nt). Vous corrigez votre erreur dans la minute qui suit : le feedback est instantané, pas besoin d’attendre le lendemain, la semaine suivante ou la période de QA pour savoir si le changement effectué dans le code a introduit un bug ou une régression.
  • Vous devez modifier cette méthode ou cette fonction vraiment très volumineuse et complexe. C’est l’occasion d’effectuer un refactoring et splitter (séparer) celle-ci en plusieurs autres méthodes plus simples. Mais vous craignez qu’un tel changement ‘casse’ ce qui fonctionnait jusqu’alors. « Don’t fix it if it ain’t broke » dit-on souvent : c’est malheureusement le meilleur moyen de développer du nouveau code Legacy au-dessus du code Legacy existant. Si vous disposez de tests, non seulement vous-même mais aussi toute l’équipe, aurez une meilleure confiance dans les changements effectués. Vous pouvez améliorer l’existant au fur et à mesure, éviter l’inflation de la dette technique, et surtout travailler avec l’agréable sentiment de développer un code de qualité au lieu d’ajouter un nouveau poids à la charge qui pèse déjà lourdement sur vos épaules.
  • Vous développez quelques nouveaux tests unitaires afin de couvrir votre évolution et tester celle-ci. Toute prochaine modification que devra réaliser un membre de votre équipe sera plus facile, plus rapide et plus sûre.

Contrairement à ce que l’on pourrait croire, développer des tests unitaires ne signifie pas développer deux fois plus ou plus lentement. Le code de ces tests est généralement très simple, et si elle représente une charge de travail additionnelle, celle-ci est n’est finalement pas si importante au regard des bénéfices. Il s’agit d’ailleurs d’une pratique de plus en plus courante, au point que l’absence de tests unitaire est considérée comme un des 7 péchés capitaux du développement.

De toutes façons, l’écriture de code n’est pas non plus le coût le plus important : un développeur passe plus de temps à essayer de comprendre le code qu’il doit modifier plutôt qu’à développer cette modification. En fait, vous ne pouvez même pas estimer le temps nécessaire à l’implémentation d’une évolution si vous ne connaissez pas déjà le code existant et ce qu’il fait.

La raison pour laquelle la lisibilité et l’intelligibilité du code est une des sources les plus importantes de coûts de maintenance élevés réside dans le fait que le temps passé à programmer est négligeable par rapport au temps nécessaire à comprendre ce que fait le code et comment implémenter tout changement.
Par contre, si vous disposez déjà de tests unitaires, généralement très simples et très lisibles, vous réduisez énormément cette charge.

Mais qu’en est-il dans notre cas, lorsque notre application Legacy ne dispose pas déjà de tests unitaires ? C’est ce que nous verrons dans le prochain post.

Ce article est également disponible en Leer este articulo en castellano et Read that post in english.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *