Nous avons défini notre projet de réingénierie comme la réécriture de notre application Legacy dans un nouveau langage ou son portage vers une nouvelle technologie, par opposition à un refactoring qui consiste à réorganiser le code et à en corriger certains défauts afin de rendre celui-ci plus maintenable et de réduire sa dette technique.
Nous avons vu également, à l’aide de SonarQube et du plugin SQALE, différents plans de refactoring plus ou moins ambitieux, depuis la résolution des défauts les plus critiques jusqu’à la réduction de la dette technique afin de ramener celle-ci à un niveau (rating SQALE) ‘A’.
Cependant, pour une même application Legacy, est-il plus intéressant d’effectuer un projet de réingénierie ou ‘simplement’ de refactoring ?
Normalement, l’investissement sera supérieur pour un reengineering, puisqu’il est rarement envisageable de pouvoir moduler celui-ci, sauf dans le cas d’une réingénierie partielle, de l’interface graphique par exemple, alors qu’un refactoring peut se moduler au fil du temps.
Mais on peut supposer que les bénéfices d’un reengineering seront également largement supérieurs à ceux d’un refactoring puisque la dette technique devrait, non pas disparaître complètement – il restera toujours des objets complexes ou des défauts d’inattention – mais du moins se réduire très fortement. Ceci dans le cas où le projet de réingénierie est un succès bien évidemment.
Car les risques d’échec sont importants :
- Vous ne maîtrisez pas la nouvelle plate-forme technique ou le langage cible, vous manquez de connaissances ou d’expertise dans ce domaine. Votre nouvelle application rencontre des problèmes de temps de réponse.
- Vous avez mal calculé l’effort de reengineering, vous développez rapidement et oubliez les tests unitaires, afin de tenir les délais de projet. Votre nouvelle application est boguée.
- Certains modes opératoires ou processus fonctionnels sont modifiés, sous prétexte de simplification, mais en fait pour alléger la charge de réingénierie. Votre nouvelle application ne répond pas aux exigences métier et s’avère plus complexe à utiliser.
- Vous n’avez pas identifiés précisément les objectifs, les utilisateurs ne sont pas incorporés au projet. Vous faites face à de nombreuses demandes de leur part et vous explosez votre budget et votre planning.
Performance désastreuse, dysfonctionnements, utilisateurs mécontents : nous allons voir quelques points à prendre en compte afin de limiter ces risques et comment SonarQube peut nous aider.
Divide and Conquer
Nous avons vu que SonarQube permettait une vue générale sur l’application, notamment en matière de métriques quantitatives telles que le nombre de lignes de code (LOC), la Complexité Cyclomatique (CC), le niveau de commentaires dans le code, etc.
Nous avons ensuite zoomé sur ces composants afin d’identifier les fonctions et programmes les plus complexes et les défauts rencontrés dans leur code.
Nous avons joué également avec certains widgets, et notamment le Bubble Chart afin de croiser certaines de ces métriques entre elles ou avec la dette technique, et distinguer les programmes monstrueux qui constituent la cible privilégiée de notre réengineering.
Figure 1 – Technical Debt x Cyclomatic Complexity/Function x Lines of Code
Dans la figure précédente, on note immédiatement le fichier ‘RTFOUT.C’, de par la taille de la bulle qui représente la Complexité Cyclomatique (CC) moyenne des fonctions de ce programme. Mais le fichier ‘fltexp.c’ tout en haut posséde la dette technique la plus élevée, avec une CC moyenne par fonction moins importante, tout simplement parce qu’il compte plus de fonctions.
Un tel graphique nous permet de visualiser immédiatement les programmes avec une taille en nombre de lignes de code élevée, comportant des fonctions complexes et une dette technique importante. Il nous est alors possible d’examiner comment les découper en plusieurs composants de granularité moins importante afin de réduire leur complexité, améliorer leur lisibilité et leur maintainabilité.
Nous avons vu par exemple la fonction ‘monstre’ du programme ‘RTFOUT.C’ qui s’occupe à elle seule de gérer le Rich Text Format (RTF) de Microsoft. Différents blocs de code prennent en charge les paramètres concernant les polices de caractères, les fontes (gras, italique, etc.), les propriétés du document (auteur, nombre de mots ou de lignes, etc.), … Il est donc assez facile de spécialiser ce code au sein de fonctions ou de classes distinctes.
Deux points importants pour ce redécoupage fonctionnel :
- Les tests unitaires : si on en a déjà, il faudra probablement les modifier afin de les adapter à notre nouveau design. Si on n’en a pas, ou s’ils ne sont pas compatibles ou utilisables avec notre nouveau langage cible, il faudra les (ré-) écrire au fur et à mesure dans le nouveau langage.
- Il n’est pas nécessaire de réduire complètement la Complexité Cyclomatique (CC) : on peut se permettre d’avoir quelques composants très complexes, mais pas trop. Par exemple, une classe unique pour gérer toutes les variables ‘utilitaires’ ou les constantes, avec autant de méthodes (en fait, autant de setters/getters), aura une CC proportionnelles au nombre de ces données et méthodes, mais en fait restera très lisible et facilement maintenable.
Notre reengineering est en fait proche ici d’un reverse engineering qui consiste à comprendre le design actuel afin de repenser celui-ci différemment. Il est souvent intéressant de se faire assister pour cela d’un outil d’analyse d’impact afin de dessiner une carte des différents modules ou packages et des relations entre eux, identifier les liens entre les différentes couches de l’application (présentation, logique, données), visualiser les objets les plus complexes, etc.
Un outil d’analyse d’impact sera également utile pour identifier du code mort, c’est-à-dire des objets (classe, méthode, table de base de données, etc.) qui ne sont pas utilisés, qui ne sont appelés par aucun autre composant. Inutile de réécrire des composants qui ne sont plus utilisés.
Attention cependant : ces outils produisent fréquemment des false-positives et nécessitent de bonnes compétences pour une utilisation optimale.
Code dupliqué
Pendant que SonarQube me montre le code des fonctions complexes ou très complexes, je peux en profiter pour noter les blocs de code dupliqués. Dans la figure suivante :
Figure 2 – Lignes des blocs de code dupliqués dans SonarQube
nous rencontrons deux blocs de 33 lignes copiés/collés aux lignes 1188 et 1255 du programme. Dans d’autres cas, ces duplications interviendront entre différents fichiers.
Il est important de les identifier assez tôt car :
- Elles sont souvent synonymes d’un design peu optimisé.
- Je peux éviter de réécrire le même bloc de code plusieurs fois dans le nouveau langage cible.
- Je diminue la maintenance et les risques pour l’application : toute modification dans du code dupliqué doit être reportée sur chacun de ses exemplaires, ce qui coûte plus cher en maintenance et accroît le risque de défauts en cas d’oubli.
Interface utilisateurs
Nous avons vu que l’interface utilisateur pouvait constituer en elle seule un objectif de reengineering. Dans ce cas, il est important de l’envisager le plus en amont possible car la modification des écrans peut avoir un double impact :
- Modification des données : auquel cas, il faudra commencer par cette étape et ensuite regarder l’impact en termes de classes ou programmes ou fonctions.
- Modification des processus métiers : dans ce cas, travaillez le plus tôt possible avec les utilisateurs afin de valider ces modifications. N’essayez même pas de commencer votre reengineering avant cette validation. Et surtout pas de programmer les écrans à l’identique.
Attention à bien vérifier des opérations qui ne sont pas nécessairement ‘codées’ dans l’interface graphique. Par exemple, tous les utilisateurs font des Copier/Coller, et généralement, vous n’avez pas programmé un bouton ou un menu pour ce faire. Et l’utilisateur évoquera rarement ce point car pour lui, c’est naturel et évident. Veillez à bien identifier ces processus ‘implicites’ et à tester qu’ils sont reproductibles dans la nouvelle version de l’application, après réingénierie.
Vérifiez la consistance des messages utilisateurs : on normalise, soit autour d’une phrase telle que ‘Le montant saisi est erroné’, soit autour d’une expression : ‘Erreur de saisie du montant’.
Vérifiez les inconsistances dans les représentations de données, parfois avec décimales, parfois sans par exemple.
Suite et fin dans le prochain post: nous verrons comment étudier la restructuration des traitements avec SonarQube.
Cette publication est également disponible en Leer este articulo en castellano : liste des langues séparées par une virgule, Read that post in english : dernière langue.