Développement logiciel
24 minutes de lecture

Complexité du code : un guide simple pour la comprendre et la mesurer

Complexité du code : un guide simple pour la comprendre et la mesurer

On considère souvent la complexité du code comme subjective. Ce qui semble un fouillis pour un développeur ou une développeuse peut être un jeu d’enfant pour un autre. Cependant, la complexité du code est un phénomène objectif qui peut être mesuré et maîtrisé, en particulier à l’aide des bons outils et métriques. 

La lecture d’aujourd’hui couvrira tout ce que vous devez savoir sur la complexité du code. Nous expliquerons la complexité du code et comment la mesurer, nous démystifierons les mythes les plus courants, nous explorerons les causes et les pièges, et nous partagerons des conseils pratiques pour que votre code reste propre et facile à maintenir.

Qu’est-ce que la complexité du code et pourquoi est-elle importante ?

La définition de la complexité du code est simple : elle mesure à quel point un segment de code est complexe à comprendre, à modifier ou à tester. Prenons rapidement un exemple.

Version 1 du code

def do_stuff(x, y):

    return (lambda x, y : x * y)(x, y)

w = 5

h = 10

a = do_stuff(w, h)

print(f"The area is {a}")

Version 2 du code

def calculate_area_of_rectangle(width, height):

    return width * height

area = calculate_area_of_rectangle(5, 10)

print(f"The area is {area}")

Les deux extraits de code ci-dessus permettent d’obtenir le même résultat : calculer l’aire d’un rectangle. Cependant, seule la version 2 du code fait un excellent travail en rendant l’intention du développeur ou de la développeuse immédiatement évidente. La version 1 du code utilise des fonctions vagues, des noms de variables et une fonction lambda inutile pour la multiplication.

Pourquoi cette différence de compréhensibilité est-elle importante ? Il y a plusieurs raisons à cela :

  • Un code complexe rend plus difficile l’ajout de nouvelles fonctionnalités, ce qui peut ralentir le développement.
  • Les échéances des projets sont retardées, car l’équipe de développement prend plus de temps pour comprendre, modifier et tester un code complexe.
  • Un code complexe s’accumule au fil du temps, en particulier lorsque l’équipe de développement est contrainte de donner la priorité à la vitesse plutôt qu’à la lisibilité. Au fil du temps, il devient de plus en plus difficile et coûteux de remédier à cette dette technique.
  • Il est souvent plus compliqué d’écrire des scénarios de test complets pour un code complexe avec des chemins logiques enchevêtrés. Cela peut conduire à des bogues cachés qui passent à travers les mailles du filet.

Quelles sont les principales causes d’un code complexe ?

Examinons quelques-unes des raisons les plus courantes de la complexité du code dans les logiciels :

Mauvais noms de variables

Les noms de variables cryptiques tels que "x", "y" ou "temp" n’offrent aucun indice quant à leur objectif et frustrent l’équipe de développement. Des noms descriptifs tels que "customerName" ou "orderTotal" facilitent l’autodocumentation et la compréhension du code.

Absence de commentaires

Certains prétendent que c’est trop complexe si votre code nécessite une explication. En réalité, il y a des cas où les commentaires sont nécessaires pour transmettre l’intention. Par exemple, les nouveaux développeurs et les nouvelles développeuses risquent de ne pas comprendre la logique d’un algorithme complexe implémenté sans commentaires.

Cela dit, un excès de commentaires peut encombrer le code. La clé est de trouver le bon équilibre : ajoutez des commentaires que vous aimeriez avoir à l’avenir.

Trop de lignes de code

Les fonctions ou les fichiers de classe volumineux comportant des centaines de lignes de code deviennent difficiles à parcourir et à traiter. Par exemple, vous pouvez avoir une fonction ProcessRequest() qui comprend une logique de stérilisation des requêtes, d’authentification, d’autorisation, d’opérations sur la base de données et de génération de réponses. Une telle fonction sera excessivement longue et, par conséquent, difficile à comprendre, ce qui augmentera le temps nécessaire à sa mise à jour. 

L’alternative recommandée serait de décomposer la fonction ProcessRequest() en fonctions plus petites et plus spécifiques. 

Couplage étroit

Les modules de code fortement interdépendants sont difficiles à modifier ou à tester de manière isolée. Par exemple, dans un système à couplage étroit, la modification d’un élément du module A peut entraîner des problèmes inattendus dans le module B. Un couplage étroit avec des interfaces bien définies favorise la modularité et réduit la complexité. 

Code suroptimisé

Même si l’optimisation est essentielle, se concentrer excessivement sur les performances peut conduire à un code ambigu et difficile à comprendre, car vous optimisez pour la machine qui exécute le code plutôt que pour l’être humain qui le lit. L’équipe de développement doit viser un équilibre entre efficacité et lisibilité. 

Absence de documentation sur le projet

Ne pas documenter son code revient à construire une maison sans plan. Sans une documentation appropriée, votre équipe de développement n’est pas sûre de l’architecture globale du projet, des décisions de conception et des normes de codage. Ce manque de clarté complique la compréhension et la maintenance du code, ce qui contribue à accroître la complexité et les incohérences du projet.

Une base de code bien documentée est également plus facile à assimiler pour les nouveaux développeurs et les nouvelles développeuses. 

Mauvaise conception générale

Des choix de conception inadéquats dès le départ peuvent conduire à une base de code complexe difficile à maintenir au fil du temps. Par exemple, ne pas suivre le principe de la responsabilité unique peut se traduire par des classes ou des fonctions qui essaient d’en faire trop, ce qui rend la base de code plus complexe. 

Il est essentiel de respecter les principes de conception établis pour créer une base bien structurée et évolutive pour le code.

Quels sont les avantages de mesurer la complexité du code ?

Avant d’aborder les moyens de mesurer la complexité du code, examinons les avantages associés : 

Code plus lisible -> Productivité accrue des développeurs -> Cycles de développement plus rapides

Lorsque la complexité du code est mesurée, cela peut encourager l’équipe de développement à écrire un code plus propre et plus compréhensible. Cela se traduit par des cycles de développement et de déploiement plus rapides, car l’équipe passe moins de temps à déchiffrer une logique complexe et plus de temps à écrire de nouvelles fonctionnalités.

Toutefois, selon la loi de Goodhart, le fait de trop se concentrer sur des mesures spécifiques peut avoir des conséquences inattendues. Par exemple, si nous mesurons la longueur des fonctions pour éviter les fonctions trop longues, l’équipe de développement peut diviser les fonctions prématurément pour satisfaire à cette mesure. Cela peut augmenter par inadvertance l’indirection et la charge cognitive. Par conséquent, le succès de l’utilisation de ces mesures dépend de la manière dont les gestionnaires les intègrent dans une vision plus large du concept de réussite.

Code plus testable -> Moins de bogues

En surveillant la complexité du code, l’équipe de développement peut écrire un code qui est intrinsèquement plus facile à tester, ce qui se traduit par moins de bogues, une meilleure qualité du code et des clients plus satisfaits. 

Amélioration de la collaboration -> Développeurs plus heureux -> Moins de roulement

Lorsque le code est clair et concis, il favorise une meilleure coopération entre les développeurs et les développeuses. Tout le monde est sur la même longueur d’onde en ce qui concerne les nouvelles fonctionnalités, et les nouveaux venus peuvent rapidement comprendre la base de code existante. Il en résulte une équipe de développement plus heureuse et plus productive, avec des taux de rotation plus faibles. 

Documentation améliorée -> Succès à long terme du projet

Un code clair et concis est autodocumenté. Par ailleurs, lorsque l’équipe de développement utilise des indicateurs pour mesurer la complexité, elle prend davantage conscience de la complexité de leur code. Cette prise de conscience les conduit souvent à ajouter des commentaires et des explications détaillés pour rendre le code plus compréhensible.

Un code bien documenté facilite le transfert de connaissances, ce qui permet à votre projet de s’inscrire dans une perspective de réussite à long terme. 

Une base de code optimisée -> Moins de coûts de maintenance 

Vous pouvez construire une base de code plus simple, plus tolérante aux pannes et plus efficace en donnant la priorité à la réduction de la complexité. Avec moins de bogues et de problèmes d’évolutivité, une telle base de code entraîne des coûts de maintenance moins élevés à long terme.

Métriques d’évaluation de la complexité du code

Comme indiqué ci-dessus, la complexité du code n’est pas seulement un ressenti subjectif ; diverses mesures du code donnent un aperçu quantitatif de la complexité des logiciels. En voici quelques-unes :

Mesures de complexité de Halstead

Les mesures de Halstead, telles que la longueur du programme, la taille du vocabulaire (nombre total d’opérateurs et d’opérandes uniques dans le code) et le niveau du programme (calculé en divisant le nombre d’occurrences de l’opérateur par le nombre d’occurrences de l’opérande), constituent un excellent moyen de quantifier la complexité des algorithmes et des programmes. 

Par exemple, une longueur de programme ou une taille de vocabulaire plus importante indique une grande complexité. Une taille de vocabulaire élevée signifie généralement qu’il y a plus d’éléments uniques à suivre, ce qui augmente la charge cognitive et rend le code plus difficile à comprendre et à maintenir. Cela peut entraîner un plus grand nombre d’erreurs et une courbe d’apprentissage plus raide pour les nouveaux développeurs et les nouvelles développeuses.

Complexité cyclomatique

La métrique de complexité cyclomatique de Mccabe calcule le nombre de chemins d’exécution indépendants dans une section de code. Les chemins d’exécution sont créés par des instructions conditionnelles (if/else, switch/case), des boucles et d’autres points de décision.

Une complexité cyclomatique élevée indique que le code complexe est plus sujet aux erreurs ou aux comportements inattendus, car il suggère davantage de chemins d’exécution possibles au sein du code. Avec un plus grand nombre de chemins à prendre en compte, il y a plus de chances de négliger des cas limites ou des scénarios involontaires au cours du développement et des tests.

La formule de la complexité cyclomatique est la suivante :

M=E−N+2P

Où :

  • M est la complexité cyclomatique
  • E est le nombre de bords (edge) dans le diagramme de flux de contrôle du programme.
  • N est le nombre de nœuds (nodes) dans le diagramme du flux de contrôle du programme.
  • P est le nombre de composants connectés (c’est-à-dire les régions du code qui ne sont pas interconnectées).

Ratio de vitesse de livraison des fonctionnalités

Vous pouvez personnaliser cette métrique pour suivre la relation entre la complexité du code et la vitesse de développement. Pour la calculer, vous pouvez diviser le temps nécessaire à la mise en œuvre des fonctionnalités par les points de récit attribués à chaque fonctionnalité. Les points de récit sont des unités de mesure utilisées dans les contextes agiles pour estimer l’effort. Il s’agit de mesures relatives qui n’ont de sens que pour l’équipe concernée.

Un ralentissement significatif du ratio de vitesse de livraison pour des fonctionnalités de complexité similaire peut indiquer des problèmes sous-jacents de complexité du code. 

Lignes de code par fonction ou classe

Cette métrique de base compte le nombre de lignes d’une fonction ou d’une classe. Bien qu’un LOC (lines of code) élevé puisse indiquer une complexité potentielle, il ne s’agit pas d’une mesure définitive. Parfois, une seule ligne de logique complexe peut être plus problématique que dix lignes de code simple. 

Valeur de l’indice de maintenabilité

Cette métrique composite met en évidence la lisibilité et la maintenabilité d’une base de code en fonction de facteurs tels que les lignes de code, la complexité cyclomatique et les métriques de Halstead. Une valeur plus faible de l’indice de maintenabilité indique une plus grande complexité, car elle reflète la difficulté globale de maintenir et de comprendre la base de code. 

Complexité cognitive

Les métriques de complexité cognitive évaluent le code en fonction de sa lisibilité et de sa capacité d’auto-explication. Ces mesures se concentrent principalement sur l’aspect humain : à quel point est-il mentalement difficile de comprendre et de travailler avec un morceau de code ?

Par exemple, une simple instruction if avec une condition directe est considérée comme peu complexe sur le plan cognitif parce qu’elle est facile à comprendre. En revanche, les boucles imbriquées ou les énoncés conditionnels profondément imbriqués augmentent la complexité cognitive et nécessitent un effort mental plus important pour les suivre et les comprendre.

Mesurer la complexité du code pour optimiser le développement de logiciels

Maintenant, discutons de quelques outils gratuits et à code source libre que vous pouvez utiliser pour mesurer et améliorer la complexité du code :

SonarQube

SonarQube est un outil d’analyse et de qualité du code qui propose différentes mesures de la complexité du code, notamment la complexité cognitive, la complexité cyclomatique, les "odeurs" de code (code smells) et l’évaluation de la maintenabilité. Sa version pour la communauté est gratuite et à code source libre.

PMD

PMD est un outil d’analyse statique interlangues à code source libre que les développeurs peuvent utiliser pour analyser la complexité d’un code écrit dans différents langages. Il prend en charge différentes mesures, notamment la complexité cyclométrique et le couplage étroit.

Lizard

Lizard est un autre analyseur de complexité interlangues qui prend en charge une longue liste de langages de programmation, dont C/C++, Java, TypeScript, Scala et PHP. Il peut compter les lignes de code sans commentaires, la complexité cyclomatique et d’autres mesures.

Radon

Radon est un outil libre et gratuit permettant d’analyser la complexité des programmes Python. Il peut calculer les métriques de Halstead, la complexité cyclomatique du code, l’indice de maintenabilité et d’autres métriques.

Stratégies pour réduire la complexité du code

Maintenant que nous avons identifié les pièges de la complexité, parlons des stratégies pour l’atténuer :

Identifier les domaines où le code est très complexe et y remédier

Dans un premier temps, identifiez les zones de votre base de code qui nécessitent une attention immédiate. Voici comment procéder :

  • Utilisez les outils à code source libre mentionnés ci-dessus pour calculer les valeurs de différentes mesures de complexité, telles que la complexité du code Mccabe.
  • Réusinez ou repensez les parties du code source qui s’avèrent les plus complexes.
  • Réexécutez l’analyse de complexité pour vérifier si vous avez réduit le niveau de complexité.
  • L’équipe de développement doit identifier les logiques trop complexes et suggérer des améliorations lors de la révision du code.

Intégrez l’analyse automatisée de la complexité dans votre flux de travail

Automatisez l’analyse de la complexité et intégrez-la à votre flux de développement. Par exemple, si vous avez déjà un pipeline CI/CD, vous pouvez ajouter une étape qui effectue cette analyse. L’automatisation de l’analyse de la complexité présente des avantages tels que :

  • Détection précoce de problèmes complexes potentiels.
  • Suivi constant de la complexité du code dans l’ensemble de la base de données.
  • Amélioration de l’efficacité de l’équipe de développement, qui peut se concentrer sur la résolution des problèmes de complexité plutôt que sur leur identification.

Adoptez un couplage lâche et d’autres principes de conception essentiels

Les modules doivent être faiblement couplés afin de réduire les interdépendances et la complexité. D’autres principes de conception sont à prendre en compte :

  • Cohésion élevée : Chaque module doit avoir un objectif clair et bien défini.
  • Principe de la responsabilité unique : Concevoir des modules ou des classes n’ayant qu’une seule raison de changer.
  • Ne vous répétez pas (Don’t Repeat Yourself) : Éliminez la duplication du code en écrivant des fonctions réutilisables.

Privilégiez la maintenance continue

Enfin, il faut reconnaître que la complexité du code n’est pas un problème ponctuel, mais un effort permanent. Consacrez du temps à des exercices de réécriture réguliers afin de résoudre les problèmes de complexité et de réduire la dette technique. Vous favorisez une culture de code propre, lisible, bien documenté et bien testé au sein de votre organisation.

Le mot de la fin

La complexité du code a des répercussions sur l’ensemble de l’organisation, et pas seulement sur l’équipe de développement. Un code trop complexe peut retarder la sortie de nouvelles fonctionnalités et la correction de bogues, ce qui peut irriter les clients et les autres parties prenantes. Cet article a couvert tout ce que vous devez savoir pour gérer et réduire la complexité du code ; nous espérons qu’il vous a été utile. 

N’oubliez pas qu’un code propre n’est pas seulement une bonne pratique pour l’équipe de développement ; c’est un avantage stratégique.

Questions fréquentes sur la complexité du code

Quels sont les outils utiles pour mesurer la complexité du code ?

PMD, Lizard, SonarQube et Radon sont des outils permettant de mesurer différents paramètres de complexité du code. 

Quelle est la meilleure métrique pour évaluer la complexité du code ?

L’indice de maintenabilité est l’une des métriques les plus fréquemment utilisées pour évaluer la complexité du code. Il utilise une combinaison de différentes mesures, notamment le volume de Halstead et la complexité cyclomatique.

À quelle fréquence la complexité doit-elle être analysée ?

L’équipe de développement doit effectuer une analyse de la complexité du code avant qu’une modification ne soit intégrée à la branche principale. 

Le réusinage peut-il réduire la complexité du code ?

Oui, le refactoring peut réduire la complexité du code. Par exemple, vous pouvez remanier les noms de variables pour rendre une fonction plus facile à lire et donc moins complexe.

Quelle est la différence entre la complexité cyclomatique et la complexité du code ?

La complexité cyclomatique est une métrique qui mesure la complexité du flux de contrôle d’un programme sur la base du nombre de chemins indépendants à travers le code source. D’autre part, la complexité du code est un concept plus large qui comprend plusieurs aspects, notamment la structure, la lisibilité et l’expressivité d’un programme.