Mesurer la qualité d’un projet pour le désendetter

En ingénierie logicielle, tant qu’un projet se développe, la dette technique s’accumule inexorablement. Les sessions de refactoring sont là pour contrebalancer cette tendance et leur mise en place régulière garantit la maintenabilité du projet. Mais ce qui est délicat avec la dette technique, c’est qu’elle n’est pas vraiment mesurable, et comme l’iceberg, on n’en voit que la partie émergée. Résultat, le refactoring est souvent sous-estimé et mal maitrisé. Si on ajoute à cela une mauvaise couverture de test, refondre le code applicatif fait peur, fait mal. Plus personne n’a l’audace de le tenter, et à long terme, c’est la banqueroute assurée du projet.

L’objectif de cet article est de proposer une approche efficace pour savoir comment attaquer efficacement un grosse refonte d’un code mal couvert par les tests. Cette tâche technique est divisée en plusieurs problématiques : comment déterminer les zones de risque du projet, trouver où poser les tests pour sécuriser l’étape de refactoring et trouver les zones à refactorer.

Pour rendre l’article concret, nous allons utiliser à titre d’exemple XDepend qui est un outil d’analyse de code statique java. En effet, on parle d’amélioration de la qualité, donc pour juger de l’efficacité de nos actions, il faut pouvoir les mesurer.

Déterminer et sécuriser les zones de risques du projet

Les méthodes les plus risquées sont premièrement les méthodes les plus utilisées: en effet, un bug introduit dans le refactoring d’une méthode utilisée dans la plupart des fonctionnalités de votre application et les impacts sont tout de suite démesurés.

Pour mettre en évidence ces méthodes , nous disposons de quelques métriques simples comme le couplage afférent (Ca) et le couplage efférent (Ce).
Le couplage afférent pour un élément de code est le nombre d’éléments qui l’utilisent. Cette métrique témoigne de la « responsabilité » de cet élément dans l’ensemble du code.
A l’opposé, le couplage efférent est le nombre d’éléments différents qu’utilise un élément, témoignant de son « indépendance » vis à vis du reste du code.

L’outil d’analyse de code statique que nous utilisons, XDepend, permet de sortir la liste des méthodes les plus utilisées en une simple requête :

SELECT TOP 10 METHODS ORDER BY MethodCa DESC

Les méthodes qui ressortent de ces métriques sont donc logiquement les premières sur lesquelles il faudra porter son attention.

XDepend permet d’aller encore plus loin dans cette recherche, en donnant un score à chaque méthode (MethodRank) et chaque type (TypeRank) selon son importance. Ce score est calculé à la manière du Pagerank de Google : tout comme la pertinence d’une page web, l’importance d’un élément de code est déterminée par le nombre d’éléments de code qui l’utilisent et de leur importance.

SELECT TOP 10 METHODS ORDER BY TypeRank DESC

Sécuriser l’existant

On peut ensuite filtrer la liste de méthodes ordonnée par importance avec les informations relatives à la couverture de tests existante (si celle-ci existe). Ainsi, on met en exergue les méthodes les plus importantes qui ne sont pas suffisamment testées :

SELECT TOP 10 METHODS WHERE PercentageCoverage < 98 ORDER BY MethodRank DESC

Poser des tests sur ces méthodes nous assurera que le refactoring des zones associées ne provoquera aucune régression.

Repérer les zones à refactorer

Une fois le coeur du projet renforcé par un harnais de tests, on peut sereinement envisager de casser morceau par morceau pour mieux reconstruire. Mais comment déterminer la pertinence des méthodes à refactorer? Comment mesurer le gain qu’apportera ce refactoring pour la suite du projet ? Voici quelques pistes classiques à envisager en premier lieu :

Les méthodes mal codées

La littérature logicielle regorge de préconisation permettant de s’assurer qu’une méthode reste maintenable en traçant des limites pertinentes : complexité cyclomatique, nombre de lignes de code, de paramètres, de variables…

Toutes ces limites peuvent être visualisées en une requête :

WARN IF Count > 0 IN SELECT TOP 10 METHODS WHERE (
NbLinesOfCode > 30           OR
NbBCInstructions > 200       OR
CyclomaticComplexity > 20    OR
BCCyclomaticComplexity > 50  OR
BCNestingDepth > 4           OR
NbParameters > 5             OR
NbVariables > 8              OR
NbOverloads > 6
) ORDER BY MethodRank DESC

Le manque de cohésion des méthodes (Lack of Cohesion in Methods, LCOM)

La notion de cohésion d’une classe est aussi importante : les responsabilités de la classe forment un tout cohérent. Pour déterminer la cohésion, on se base sur l’utilisation des variables d’instance par les méthodes de la classe.
Plus le manque de cohésion (LCOM) est élevé, plus la classe est considérée complexe et donc moins facile à maintenir. Sur des classes comportant de nombreuses variables d’instance et de nombreuses méthodes, il est alors judicieux de surveiller ces classes et de les refactorer pour faire descendre le LCOM.

WARN IF Count > 0 IN SELECT TOP 10 TYPES WHERE
LCOM > 0.8 AND NbFields > 10 AND NbMethods >10
ORDER BY LCOM DESC

Les dépendances cycliques entre packages

Les dépendances cycliques entre packages amènent à l’antipattern du plat de spaghetti qui ne permet pas de déterminer le qui, le quoi et le comment d’une modification de données. Cette faible indépendance fonctionnelle porte préjudice au critère de réutilisation du composant.
Si A dépend de B qui dépend de C qui dépend de A, alors A ne peut plus être développé et testé indépendamment de B ou C. Ces packages forment une unité indivisibe, une sorte de super-composant qui a un coup bien supérieur en maintenabilité que la somme des 3 packages. Il faut donc repérer les dépendances cycliques et les casser.

WARN IF Count > 0 IN SELECT TOP 10 JARS WHERE ContainsPackageDependencyCycle

Le code mort

Le code mort peut devenir une plaie dans la maintenabilité d’un projet
Un Ca (couplage afférent) à 0 signifie que dans le contexte de l’application, la méthode ou le type concerné n’est pas directement utilisé.
On peut ainsi mettre en évidence rapidement des portions de codes qui ne semblent pas être utilisées en associant quelques règles CQL simples définissant une méthode non-utilisée :

WARN IF Count > 0 IN SELECT TOP 10 METHODS WHERE
MethodCa == 0 AND !IsPublic

Attention : Les méthodes publiques peuvent faire partie d’une API exposée et donc logiquement ne pas être utilisées dans le contexte de l’application, il faut donc les écarter de cette recherche.

Mesurer l’impact d’une session de refactoring

L’idéal après un refactoring est de prendre le temps d’analyser ce que le refactoring a apporté.
Première piste, regarder toutes les méthodes qui ont été modifiées suite au refactoring.

SELECT METHODS WHERE (CodeWasChanged OR WasAdded)

Le résultat de cette requête est parfois surprenant lorsqu’on compare ce qui était prévu et tout ce qui a été modifié au final.
On peut aussi tracer le nombre de méthodes et de lignes de code qui ont été modifiées et opposer ces informations à la durée du refactoring. Garder ces traces permet de s’améliorer en  estimant plus justement les refactoring suivants.

On doit aussi s’assurer que le code qui a été modifié est maintenant couvert au maximum. En effet, le but du refactoring est de « perdre du temps » pour remonter le niveau de qualité exigé pour maintenir le projet et être à l’avenir entièrement serein sur les points refactorés.
La requête suivante permet cette détection :

WARN IF Count > 0 IN SELECT METHODS WHERE
PercentageCoverage < 98 AND
(CodeWasChanged OR WasAdded)
ORDER BY PercentageCoverage DESC , NbLinesOfCodeCovered , NbLinesOfCodeNotCovered

(l’idéal est d’avoir un code couvert à 100%, mais en pratique il n’est pas toujours possible d’atteindre les 100% de couverture, 98% est notre valeur empirique).
J’espère que ces quelques préconisations vous aideront dans vos refactoring afin que cette tâche apporte encore plus de valeur à votre projet. Et vous, quels sont les indicateurs que vous surveillez durant vos refactoring et quelles autres approches avez-vous pour détecter au mieux les douleurs dans votre code ?

Aparté sur l’outil

Les requêtes utilisées dans cet article sont du CQL (Code Query Language) et peuvent être exploitées par les outils d’analyse de code NDepend (pour .Net) et XDepend pour Java que nous utilisons pour auditer les projets de nos clients. Il suffit d’avoir les binaires du projet (.dll ou .jar) pour lancer une analyse et effectuer ces requêtes sur vos projets. La plupart des requêtes sont prédéfinies pour vous aider dans vos analyses mais la puissance du langage vous permet d’affiner les requêtes facilement et d’ajouter des conditions supplémentaires sur plus de 80 métriques de design logiciel. La force de cet outil réside dans sa GUI aussi riche que dynamique.

4 commentaires sur “Mesurer la qualité d’un projet pour le désendetter”

  • Effectivement, l'outil est impressionnant. Il s'est imposé en .NET depuis pas mal de temps, et son auteur, Patrick Smacchia (un français !), poste régulièrement sur CodeBetter des articles à son sujet : bonnes pratiques, techniques d'analyse, de restructuration, de refactoring ; tips, nouvelles features, etc. Il prend parfois le temps de faire des "project reviews" intéressantes à l'aide de l'outil. Comme une grande partie des données collectées par ce dernier provient du bytecode, il est possible d'analyser des projets qui ne sont pas forcément open source. http://codebetter.com/blogs/patricksmacchia/default.aspx
  • La dette technique n'est pas une fatalité, et à mon sens le refactoring n'est pas à utiliser dans des sessions dédiées juste à ça. La définition du refactoring est de changer le code existant à l'aide d'un ensemble de refactorings sans changer son comportement extérieur, afin qu'il puisse accueillir les modifications qu'on veut lui apporter. Donc quand doit-on faire du refactoring? Dès que que nous voulons ajouter de nouvelles fontionnalités. Et quand ajoutons-nous de nouvelles fonctionnalités? Tout le temps. Ici du coup je trouve peut être un peu dangereux de parler de session de refactoring, car finalement c'est comme ça que cette pratique est stigmatisée parfois comme étant chère et n'apportont aucune valeur fonctionnelle. Dans le cas très précis ou l'on veut absorber une dette technique, quelque soit les raisons de sa présence, peut être ferions-nous mieux de trouver un autre mot que refactoring.
  • Merci pour cet aperçu d'XDepend. Je suis bien d'accord avec Jean-Baptiste sur le fait de faire du refactoring en meme temps que l'implementation de nouvelles fonctionnalités (c'est souvent ce qui est recommandé). Le probleme est toujours le meme, le "vrai" refactoring requiert des très bonnes compétences de développeur. Ce qui facilite déja le refactoring c'est d'avoir déja un découpage en couche et l'utilisation des grands patterns classiques (MVC, Service, ORM) et l'utilisation de Spring bien sur (l'IDE de Spring crée d'ailleurs de très jolis diagrammes de dépendance) Par contre il faut surtout mettre en place des outils dans les IDE qui permettent d'etre prévenu en temps réel si on ne suit pas les best practices de base (javadoc, méthode trop longue, ...) Sinon on a utilisé Sonar qui permet assez rapidement d'avoir un aperçu de la qualité d'un projet en pouvant par une interface web aller voir les sources.
  • IDEA IntelliJ a des outils avancés qui informent le développeur en temps réel sur ce qu'il vient de taper : code inutile, boucle où l'on ne passe jamais, code mort, complexité trop élevée. Je trouve personnellement plus simple d'offrir cela au moment de l'écriture du code plutôt qu'à posteriori. Je comprends par contre le besoin d'avoir un outil afin d'analyser des projets existants.
    1. Laisser un commentaire

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


      Ce formulaire est protégé par Google Recaptcha