Initiation à l’analyse de code

Publié le 2 août 2022 par Mathieu De Gracia
Couverture de l'article Initiation à l’analyse de code

Sommaire

  1. Avant propos
  2. phpmetrics
  3. Métriques
    1. LOC
    2. Class
    3. Coupling
    4. Complexité cyclomatique
  4. L’interface graphique
  5. Analyse de laravel-france
  6. Conclusion

Avant propos

Les outils d’analyse statiques permettent d’explorer et d’obtenir tout un lot de métriques à partir de votre code.

Il est extrêmement important de garder une chose en tête : une métrique n’est qu’un indicateur, elle ne remplacera jamais le jugement d’un développeur.

Il est essentiel de comprendre ce que la métrique cherche à vérifier, la problématique qu’elle met en exergue, avant de juger de la pertinence de son résultat.

Une class entity constituée uniquement de getter/setter aura un “lack of cohesion” très faible, pour autant … est-ce une mauvaise chose pour votre application ? C’est vous seul qui pourrez répondre à cette question.

L’analyse statique n’est qu’une alarme détectant un code potentiellement problématique, vous devez l’utiliser comme un outil de recherche et non comme un moyen de recevoir des sentences.

Une métrique est une sonde objective mais ignorante.

phpmetrics

De nombreux outils d’analyse statique sont aujourd'hui à notre disposition, de la détection de pattern à la correction automatique de bug, il est désormais difficile de faire le tri parmi toutes ces solutions.

Pour la suite de ce tutoriel nous utiliserons celui qui à mes yeux est le plus simple d’accès pour s’initier à l’analyse de code : phpmetrics.

Créé en 2014 par Jean-François Lépine, phpmetrics est un outil rapide à prendre en main, démocratisant et facilitant la compréhension des métriques.

Pour installer le package, une simple commande composer :

1composer require phpmetrics/phpmetrics --dev

Vous pouvez ensuite vérifier que le package est fonctionnel avec la commande suivante :

1vendor/bin/phpmetrics --version

Vous êtes désormais prêt à analyser votre code !

Par défaut phpmetrics analysera votre composer.lock afin de récupérer des informations sur vos dépendances en contactant les APIs de packagist, cette analyse supplémentaire peut fortement ralentir l’execution de phpmetrics, il est possible de désactiver ce comportement avec un fichier de configuration.

Métriques

LOC

Pour commencer, avant même d’aborder des notions plus complexes, une basique analyse de l’architecture de votre projet vous en apprendra énormément sur cette dernière.

Les métriques LOC vous donneront des indicateurs sur l’envergure de votre projet.

1vendor/bin/phpmetrics app | grep LOC -A 8
2
3LOC
4 Lines of code 1320
5 Logical lines of code 993
6 Comment lines of code 327
7 Average volume 56.8
8 Average comment weight 20.29
9 Average intelligent content 20.29
10 Logical lines of code by class 14
11 Logical lines of code by method 8

Vous pourrez ainsi effectuer des ratios entre le nombre de “Logical lines of code“ sur le nombre de “Lines of code“, une ligne logique (LLOC) est une ligne qui n’est pas un commentaire.

Ces métriques sont intéressantes car elles mesureront l’évolution de votre projet au fil du temps.

La dernière métrique est probablement la plus pertinente, le “Logical lines of code by method” donnera la moyenne de ligne par méthode de votre projet.

Il est fortement conseillé de limiter la taille de vos méthodes, plus une méthode est longue et plus elle devient naturellement complexe, démultipliant sa complexité logarithmique.

Une méthode longue sera probablement difficile à tester, à comprendre et à maintenir dans le temps.

“La première règle est d’écrire des fonctions courtes. La deuxième règle est qu’elles doivent être encore plus courtes que cela” ~ Robert C. Martin

Attention, tous les projets possèdent leurs quelques class “problématiques” qui alourdissent artificiellement les moyennes de vos métriques, il sera souvent plus judicieux de restreindre l’analyse sur un périmètre plus limité de fichier !

Class

Cette seconde commande sera orientée sur l’architecture de vos class :

1vendor/bin/phpmetrics app | grep "Object oriented programming" -A 5
2
3Object oriented programming
4 Classes 70
5 Interface 1
6 Methods 118
7 Methods by class 1.69
8 Lack of cohesion of methods 1.14

Dans ce résultat deux métriques sont particulièrement intéressantes.

La première, le “Methods by class” vous donnera le nombre moyen de méthodes toutes visibilités confondues de vos class.

Vos class sont les engrenages de votre application, il sera toujours conseillé de les garder les plus petites possible, avec le moins de méthodes afin de conserver une cohérence favorisant le principe de responsabilité unique.

“La première règle est que les classes doivent être petites. La seconde règle est qu’elles doivent être encore plus petites que cela.” ~ Robert C. Martin

Une class avec beaucoup de méthodes aura (peut-être) trop de préoccupations et deviendra ce que phpmetrics nomme un god object: une class qui sait tout, qui fait trop de choses, qui possède une forte inertie.

La seconde métrique qui mérite votre attention est le “Lack of cohesion”, cette dernière identifie les class dont les méthodes ne sont pas liées entre elles.

1class Foo {
2 
3 public function a()
4 {
5 return;
6 }
7 
8 public function b()
9 {
10 return $this->c();
11 }
12 
13 protected function c()
14 {
15 return;
16 }
17}

La cohésion de notre class Foo est de 2 car la méthode A est isolé des méthodes B et C, il existe deux chemins distincts dans l’utilisation de cette class.

Le lack of cohesion prend également en considération les propriétés de vos class.

Nous devons désormais nous poser la question suivante : La méthode A étant isolée des autres méthodes, est-elle vraiment à sa place dans cette class Foo ?

Une class avec une forte cohésion (une valeur de Lack of cohesion proche de 0) respectera certainement le principe de responsabilité unique, au contraire, une class avec une faible cohésion (une valeur élevée de Lack of cohesion) aura assurément plusieurs responsabilités.

Un fort “Lack of cohesion” sera symptomatique d’un problème de conception nécessitant une refactorisation pour extraire et isoler les différentes préoccupations.

Coupling

Maintenant que vous connaissez mieux l’architecture de votre projet il est temps de s’intéresser aux notions de coupling !

Le coupling est l’association de deux métriques : l’afferent et l’efferent coupling.

1vendor/bin/phpmetrics app | grep "Coupling" -A 2
2
3Coupling
4 Average afferent coupling 1.04
5 Average efferent coupling 2.47

Contrairement aux précédentes métriques, il n’y a pas de valeur idéales aux coupling, elles déterminent juste l’usage et la criticité de vos class.

Afferent coupling

La première de ces métriques, l’Afferent coupling permet de calculer le nombre de fois qu’une class apparaît dans d’autres class.

1class Pneu
2{
3 //
4}
5 
6class RenaultClio extends Voiture
7{
8 public function __construct(
9 protected Pneu $pneu,
10 ) {}
11}
12 
13class DaciaSandero extends Voiture
14{
15 public function __construct(
16 protected Pneu $pneu,
17 ) {}
18}
19 
20class Peugeot208 extends Voiture
21{
22 public function __construct(
23 protected Pneu $pneu,
24 ) {}
25}

En d’autre termes, l’afferent coupling de ma class “Pneu” est de 3 car elle apparaît dans 3 class voitures : RenaultClio, DaciaSandero & Peugeot208.

Quelles conclusions doit-on en tirer ?

Plus l’afferent coupling d’une class est élevé et plus votre projet est dépendant du bon fonctionnement de cette class.

Imaginez désormais que la class Pneu possède un bug, ce bug se répercutera immédiatement sur 3 autres class !

L’afferent coupling identifie les class sensibles de votre application qui nécessiteront une attention particulière, une class avec une fort afferent coupling se doit d’être testé en profondeur et de manière exhaustive afin de limiter l’impact éventuel d’une anomalie.

Efferent coupling

Contrairement à l’afferent, l’efferent prend la problématique à l’envers en comptant le nombre de dépendances d’une class.

1class Peugeot208 extends Voiture
2{
3 public function __construct(
4 protected CrossClimate $crossClimate,
5 protected Aerotwin $aerotwin,
6 ) {}
7}

CrossClimate et aerotwin sont respectivement des marques de pneu et d’essuis glace.

Ici notre class Peugeot208 est dépendante de deux autres class (CrossClimate & Aerotwin), son efferent coupling est donc de 2.

Que se passera-t-il si notre class Peugeot208 ne doit plus utiliser Aerotwin mais une autre marque d’essuis glace ?

Nous serons bloqué car Peugeot208 est dépendante d’une implementation concrete et non d’une abstraction : cette class sera difficile à faire évoluer car elle est directement liée à CrossClimate.

L’efferent coupling permettra d’identifier toutes les class qui sont dépendantes d’un grand nombre d’autres class, vous pourrez ainsi vous assurez que ces class utilisent des abstractions.

1class CrossClimate implements Pneu {}
2class Aerotwin implements EssuieGlace {}
3 
4class Peugeot208 extends Voiture
5{
6 public function __construct(
7 protected Pneu $pneu,
8 protected EssuieGlace $essuieGlace,
9 ) {}
10}

Complexité cyclomatique

Pour finir, intéressons nous à l’une des métriques phares de toute analyse de code : la complexité cyclomatique (CC).

Sous ce nom un peu barbare se cache une métrique d’une grande simplicité : la valeur de complexité de votre code.

1vendor/bin/phpmetrics app | grep "Average Cyclomatic complexity by class"
2
3Average Cyclomatic complexity by class 1.34

Pour déterminer cette complexité, le CC comptabilise tous les points de decision de votre code : les boucles, les conditions, les return … la sommes de ces points de décision correspondra à la valeur de complexité cyclomatique de votre méthode.

La méthode suivante possède un CC de 3 : car elle contient deux points de decision (if & for) qu’on additionne à 1.

Le CC d’une méthode commence toujours à 1.

1function foo (bool $var) {
2 
3 if ($var) {
4 return;
5 }
6 
7 for ($i = 0; $i < 3; $i++) {
8 //
9 }
10}

Une méthode avec une forte valeur de complexité cyclomatique doit immédiatement attirer votre attention, cela signifie qu’elle contient de multiples points de décisions la rendant inévitablement complexe.

Cette complexité rendra votre méthode difficilement testable car chaque point de décision ajoutera des cas particuliers à gérer favorisant l’apparition de bugs.

La lisibilité de votre méthode en pâtira également, il sera difficile de rendre votre méthode expressive si elle accumule plusieurs niveaux d’indentations.

1if ($foo) {
2 if ($bar) {
3 for ($i = 0; $i < 3; $i++) {
4 //
5 }
6 }
7}

La règle est simple : limitez au maximum l’indentation et les points de décision de vos méthodes pour conserver un code limpide et facile à appréhender.

“Une fonction ne doit faire qu’une seule chose, elle doit la faire bien et ne faire qu’elle” ~ Robert C. Martin

L’interface graphique

Toutes ces métriques peuvent paraitre intimidantes quand on découvre l'analyse de code.

Pour palier à cette problématique, phpmetrics dispose d’une commande générant un rapport html de l’analyse présentant les métriques de manière plus friendly :

1vendor/bin/phpmetrics --report-html=phpmetrics-report

Rendez-vous ensuite dans le dossier phpmetrics-report et laissez vous guider !

Analyse de laravel-france

Maintenant que vous avez quelques connaissances en analyse statique, voyons ce que nous pouvons détecter sur le repository du projet laravel-france.

Afin de limiter le périmètre d’analyse, nous allons nous concentrer sur les class services de l’application :

1~ vendor/bin/phpmetrics app/Services
2 
3/**
4* LOC
5* Logical lines of code by class 17
6* Logical lines of code by method 8
7 
8* Class
9* Methods by class 2.29
10 
11* Cohesion
12* Lack of cohesion of methods 1.14
13 
14* Coupling
15* Average afferent coupling 0.29
16* Average efferent coupling 3
17 
18* Complexity
19* Average Cyclomatic complexity by class 1.43
20*/

En moyenne les class du projet sont petites (~17 lignes) tout comme les méthodes quelles contiennent (~8 lignes), bonnes nouvelles : les class sont donc relativement sveltes et contiennent peu de code.

En moyenne les class disposent de deux méthodes et leur cohesion est proche de 1, cela signifie deux choses : les class sont petites et cohérentes, il n’y a qu’une façon de les utiliser, elles respectent probablement le principe de responsabilité unique.

L’efferent coupling est de 3, les class sont donc dépendantes en moyenne de 3 autres class uniquement, ce qui est faible et reste cohérent pour un projet de cette taille.

Pour finir, une complexité cyclomatique de 1.43 est un excellent indicateur que les méthodes possèdent peu de logique et probablement un seul niveau d’indentation.

Conclusion

Ce tutoriel constitue une première approche simplifiée du monde de l’analyse statique.

Nous n'avons fait que brosser succinctement les possibilités offertes par les différentes métriques et par phpmetrics pour améliorer la qualité de votre code.

Pour autant, j’espère que ce tour d’horizon aura attisé votre curiosité et vous motivera à lancer votre première analyse de code !

Mathieu De Gracia avatar
Mathieu De Gracia
Des fois, mon chat code à ma place 🐱

A lire

Autres articles de la même catégorie