J’ai testé NDepend pour analyser l’architecture et la qualité d’un vieux projet scolaire Unity

Vous aimez les nouilles ? J’adore ça sauf dans mon code et pourtant ça m’arrive souvent. Quand j’ai de nouvelles idées, j’ai envie de voir le résultat rapidement, écorchant au passage l’architecture logicielle, ce qui mène inévitablement à une base de code pourrie ! Pour me canaliser et m’obliger à rester alerte quant à la qualité de mon projet, j’ai décidé d’intégrer NDepend à mes habitudes de travail. Dans cet article, je vous explique les atouts de cette arme secrète contre l’endettement technique.

 Préambule  NDepend soutient les étudiants, pour cette raison j’ai reçut gratuitement une licence du logiciel en échange de l’écriture de cet article. Il est prévu que le présent article soit complété et amélioré dans les mois à venir.

Plat de nouille, dette technique et perte de vélocité

De primes abords, il est nécessaire de comprendre ce qu’est une dette au sens d’un logiciel et des dangers de son accumulation. La dette technique, ce n’est pas une base de code exécrable né d’un proof of concept. Si comme je l’ai décris dans l’introduction, vous souhaitez tester rapidement une idée de gameplay, ce n’est pas grave de coder un prototype à l’arrache étant donné que vous ne garderez peut-être pas les mécaniques de jeu imaginées. En revanche, lorsqu’on souhaite atteindre un objectif commercial rapidement, tel qu’un salon où l’on voudrait dévoiler certaines fonctionnalités attrayantes aux futur joueurs et aux investisseurs, il est possible de choisir de faire un raccourci technique pour aller plus vite. C’est-à-dire de consciemment produire un code de mauvaise qualité, tout en prévoyant de le refaire plus tard. Par conséquent, il faut également prévoir d’allouer le temps et les ressources humaines nécessaires à la correction de ce code. Ce temps et ces ressources ne pourront être utilisés pour produire de la valeur ajoutée sur d’autres aspects du projet, ce qui signifie que l’on reporte le coût économisé avant l’objectif commercial à après. À l’instar du terme financier, on emprunte de la qualité logicielle en échange d’un résultat rapide, tout en sachant qu’on devra rembourser tôt ou tard cet emprunt.

Il n’est pas forcément nécessaire ou possible de rembourser l’entièreté de la dette générée. Certaines zones du code sont décorrélées du reste du système et donc ne génère pas d’instabilité notable. Néanmoins, en cas de surendettement technique, votre projet deviendra peu extensible, chaque évolution sera lente, génératrice de bug et vous n’aurait plus envie de travailler dessus. Cela s’appelle une perte de vélocité, car on se met à produire de la valeur métier très lentement par rapport aux ressources allouées pour la tâches. Le commun des mortels appelles aussi cela un bon gros plat de nouilles tout emmêlé ! En résumé, il faut équilibrer la dette de sorte à ne pas en contracter plus que ce que l’on est capable de rembourser, et surtout planifier judicieusement quelles parties du code source nous devons rectifier en priorité ou au contraire laisser en jachère. 

Estimer la qualité d’un projet avec NDepend

NDepend sert justement à nous guider dans tout cela. C’est un logiciel d’analyse statique pour les projet .NET et qui dispose de fonctionnalités spécifique pour Unity. J’ai décidé de le tester sur Spelunca, un ancien projet scolaire sous Unity réalisé en équipe de trois ans où je m’occupais de la programmation gameplay et d’une partie de l’interface. A l’époque, nous avions tenté de nous coordonner pour produire l’architecture la plus intègre possible, mais vous connaissez les projets scolaires… ça se termine toujours en rush ! J’ai donc trouvé intéressant de l’utiliser comme cas d’étude avec NDepend.  J’utilise le logiciel en plugin Visual Studio mais il dispose également d’une version stand-alone.

Tableau de bord généré à la création du projet NDepend pour Spelunca. Il est écrit “no diff” car il n’y a pas de snapshot avec lequel comparer.

Quand vous démarrez un projet NDepend pour la première fois, vous avez accès à un tableau de bord exposant plusieurs métriques. Cet outil utilise un système de snapshot qui présente l’état du projet à un instant T qui servira ensuite de comparaison pour la snapshot suivante. Ainsi, il est possible de se concentrer uniquement sur les problèmes qui viennent d’apparaître. L’exemple ci-dessus présente la première snapshot de Spelunca, comme il n’y en a pas d’autre en guise de comparaison, il y a indiqué “no diff” là où d’ordinaire vous verriez en rouge le pourcentage d’évolutions négatives et en vert celui des améliorations. Il manque aussi les informations relative à la dette car je n’ai pas fournit les informations de couverture de test étant donnée que nous n’en pas avion codé à l’époque.

En outre, Ndepend permet d’imposer certains critères de qualité en refusant de passer le code en production tant que certaines métriques dépassent un seuil. Ces dernières sont des critères définissables par l’utilisateur appelées “quality gates” et certaines sont déjà présente par défaut tel que le pourcentage de code couvert ou des erreurs critiques. 

Sur le tableau de bord de Spelunca on constate qu’il y a une “quality gate” non respectée. Celle-ci est intitulée “Avoid types too big”, ce qui se traduit par “Evitez les types trop grand”. Ce qui est génial, c’est que l’on peut lire la raison pour laquelle cette erreur apparaît, ainsi que pourquoi cela est considéré comme une mauvaise pratique. De plus, l’outil nous propose des conseils sur la méthode à suivre pour corriger le problème. Ce qui fait de NDepend, un bon moyen de se documenter et de s’éduquer sur le sujet. C’est un peu comme si un senior en la matière vous faisait une revue de code exhaustive. Si vous n’êtes pas d’accord avec les critères de la règle, il est possible de l’éditer ou de le désactiver pour une portion de code qui fait volontairement exception à la règle.

Les règles qui ne sont pas des “quality gates” sont à considérer comme des warnings qu’il n’est pas obligé de résoudre mais qui est conseillé de suivre. NDepend les tries par catégories de problème et certaines sont spécifiques à Unity. Selon vos objectifs et habitudes, vous pouvez créer vos propres règles liées à Unity, tel qu’interdire l’utilisation de fonction trop coûteuse tel que Object.FindObjectsOfType (oui, vraiment s’il vous plaît, n’utilisez pas ça). En double-cliquant sur une ligne, on ouvre un onglet listant les méthodes et classes, ce qui permet d’accéder rapidement au code source à corriger. Sur l’image ci-dessous, on peut voir également trois colonnes appelée “dette”, “intérêt annuel” et “point de rupture”. La première estime la durée nécessaire au remboursement de la dette. La seconde correspond au temps que coûte chaque année la non résolution de la dette technique, ce qui est un bon indicateur pour contrôler la perte de vélocité. Enfin, la dernière colonne est le ratio entre la dette technique et l’intérêt annuel, ce qui permet de trouver a quel moment le temps perdu causé par l’endettement est égale au temps que ça aurait pris si on l’aurait remboursé. Autrement dit, une fois ce point de rupture dépassé, ça n’a plus aucun intérêt de générer de la dette sans la fixer car on entre en déficit et on en ressortira forcément perdant. L’image ci-dessous montre que le point de rupture liée aux règles de Unity est de trois et deux-cent jours, ce qui pourrait penser qu’il n’est pas utile de les régler. De plus, il ne s’agit pas la de règle de design, de code smells ou d’orienté objet, ce qui signifie qu’en réalité ses erreurs n’influent absolument pas sur l’architecture et ne ralentissent pas la vélocité de la team de développement. Néanmoins, il est intéressant de satisfaire toute les règles relatives au moteur de jeu afin d’optimiser son fonctionnement. En effet, la première erreur par exemple, alerte sur le fait qu’un ScriptableObject instancié avec le mot-clé new au lieu de la méthode CreateInstance<T>() empêche le moteur de gérer les méthodes de message Unity tel que Awake(), OnDestroy(), etc…, ce qui est extrêmement problématique. D’ailleurs, je ricane sombrement car je me souvient très bien qu’il y a deux ans, j’avais pas mal rager en me plaignant que Unity est nul car mes ScriptableObjects ne marchaient pas à moins de les supprimer et d’en créer de nouveaux… au final, tout était de ma faute ! La troisième erreur quant à elle m’a appris que lorsqu’on laisse une méthode de message Unity sans corps, elle est quand même appelé par le moteur et ralentit pour rien son fonctionnement. Comme mon IDE a un template qui écrit par défaut les méthodes Start() et Update(), je les laisse souvent vides alors que je ne devrait pas ! Vous comprenez vite que régler les dettes concernant Unity nous aide en fait à optimiser notre jeu et à éviter des comportements non désirés.

Sur le tableau de bord, il y a un encart concernant le pourcentage de code couvert pas les commentaires. Une grande valeur de cette métrique n’est pas gage de qualité car c’est inutile de commenter un code déjà lisible utilisant des variables explicites et une indentation claire. Cependant, si le comment coverage est vraiment trop bas, on peut se poser des questions sur la compréhension des algorithmes. Afin d’éviter un temps trop long en rétro-engineering à chaque nouvelle modification, il est intéressant de laisser une note technique courte résumant ce qui s’y trame. De mon point de vue, 28.31% est un score beaucoup trop faible. Attention, ajouter cette métrique en “quality gate” pourrait vous apporter de mauvaise surprise, car dans le rush, la team pourrait être tenté d’écrire n’importe comment juste pour valider la petite croix sans pour autant apporté une meilleure compréhension.

Sur Spelunca, il me manque les informations relative aux données de couverture du code car nous n’avions pas fait de test unitaire. Les tests sont évidemment une pratique non négligeable sur les projets à long terme, mais pour un projet scolaire c’était malheureusement une perte de temps. Il n’est pas non plus nécessaire d’écrire des tests partout, cela reste chronophage. Il est préférable de cibler ce qu’on considère comme des hotspots, c’est-à-dire des zones de code à la fois modifiées fréquemment et qui servent de base à beaucoup où qui sont appelés à de nombreux endroits. Si des bugs apparaissaient au niveau de ces points critiques, cela provoquerai de gros dégâts et c’est pourquoi leur surveillance par des tests est primordiale. Encore faut-il pouvoir les identifier et c’est là que le graphe et la matrice de dépendance de NDepend peut nous aider.

Visualiser l’architecture et les dépendances fortes

Une autre fonctionnalité très pratique de NDepend est la possibilité de visualiser l’architecture de son projet à l’aide d’un graphe de dépendance ou d’une matrice de dépendance. En effet, le cerveau ne peut garder en mémoire un grand nombre d’information, ni visualiser les relations entre un trop gros nombre d’entités. Au fil de temps, le projet technique grossit et il devient de plus en plus difficile d’analyser son architecture et de réfléchir à une solution pour réduire les dépendances entres les différentes classes composants notre système. NDepend permet de visualiser cela grâce à un graphe de dépendance. Celui-ci permet de comprendre les interactions entre les objets et de voir rapidement là où il y a des cycles.

Graphe de dépendance montrant un cycle problématique

Cependant, sur un projet de grande taille, il n’est pas non plus facile de comprendre un graphe avec des nœuds et des flèches dans tout les sens. La matrice et le graphe se complètent, car il est possible de générer un graphe à partir de quelques éléments sélectionnés dans la matrice. Ainsi, on un moyen rapide de repérer les endroits problématiques dans un grand projet (la matrice) et de visualiser plus facilement cette endroits ensuite (le sous-graphe généré depuis la matrice). J’ai pris une capture d’écran de la matrice de dépendance de Spelunca. Tout d’abord, il est très facile de remarquer les dépendances cycliques car tout les éléments impliqués dans un cycle sont englobés dans un cadre rouge, comme le montre l’image ci-dessous. Il faut remarqué également les cases noirs car elles indiquent que l’élément de la ligne utilise l’élément de la colonne, mais que l’élément de la colonne utilise également celui de la ligne. Il s’agit donc d’une interdépendance et probablement l’une des raisons de la création du cycle indiqué par l’encadrement rouge. Le numéro à l’intérieur d’une case est une métrique qui mesure la force de couplage entre les lignes et les colonnes. L’utilisateur peut choisir de calculer cet valeur en fonction du nombre de membres, de méthodes, de champs, de types ou espaces de noms impliqués dans le couplage.

Example de matrice de dépendance. Dans le carré rouge se trouve les éléments impliqués dans un même cycle.

Quant aux autres couleurs, le bleu indique que l’élément de la ligne est utilisé par celui de la colonne et le vert signifie à l’inverse que l’élément de la ligne utilise celui de la colonne. Si l’architecture était parfaitement vide de dépendance, on aurait donc une matrice triangulaire avec toutes les cases bleus en bas et toutes les cases vertes au dessus.

Encore une fois, NDepend est très pratique pour les débutants car son aide contextuelle qui s’affiche quand on passe sa souris par dessus un élément de la matrice nous aide à lire et comprendre son fonctionnement. Ce type d’aide est disponible pour quasiment toutes les fonctionnalités du logicielle, ce qui rend son apprentissage rapide et sans douleur.

Example d’explication fournit par NDepend quand on passe sa souris par dessus une case de la matrice de dépendance

Conclusion

Pour conclure, NDepend est à la fois un logiciel fort pratique pour analyser son architecture logicielle, repérer les endroits problématiques de notre projet et améliorer ses pratiques de programmation. Pour un étudiant comme moi en pleine apprentissage, cela me permet d’avoir des retours et conseils sur mes mauvaises pratiques et mon utilisation de Unity. A bon escient, cela me permet même d’optimiser mon code pour Unity et de mieux comprendre le fonctionnement du moteur. J’imagine facilement son utilisation au sein d’une équipe de plusieurs personnes pour accélérer les vérifications des pulls-requests en automatisant une partie. NDepend est en plus personnalisable en ajoutant nos propres règles et formules de calcule pour les métriques. Enfin, grâce aux bulles d’aide contextuelles, c’est un outil facile à comprendre et apprendre. Pour ces raisons, je compte continuer à l’utiliser sur mes projets Unity et j’espère avoir le temps et l’opportunité de vous montrer d’autre fonctionnalité de NDepend plus poussés dans mes prochains articles !

Be the first to comment

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.