Complexité cyclomatique : définition, formule et seuils
Résumé
La complexité cyclomatique mesure le nombre de chemins d'exécution indépendants dans une fonction. La formule de McCabe : M = E - N + 2P. Les seuils : 1-10 = simple, 11-15 = modéré, 16-20 = élevé, 21+ = critique. Plus votre score est haut, plus vous avez besoin de tests. Réduisez la complexité avec des clauses de garde, l'extraction de fonctions et des tables de correspondance.
La complexité cyclomatique est une métrique de qualité logicielle qui mesure le nombre de chemins d'exécution indépendants dans une fonction ou une méthode. Chaque fois que vous écrivez un if, une boucle while, un case dans un switch ou un opérateur ternaire, vous ajoutez un chemin supplémentaire à votre programme. Ce chiffre vous dit directement combien de tests unitaires vous avez besoin pour couvrir tous les scénarios possibles. Thomas McCabe a inventé cette formule en 1976, et elle reste aujourd'hui l'une des métriques de qualité les plus répandues dans le développement logiciel.
Biscuit l'a déjà cherchée pour vous. Voici tout ce qu'il faut savoir pour l'appliquer dès aujourd'hui.
La définition complète : ce que la complexité cyclomatique mesure vraiment
La complexité cyclomatique ne mesure pas la longueur de votre code, ni le nombre de lignes. Elle mesure la structure de contrôle de votre programme, c'est-à-dire l'ensemble des décisions que votre code peut prendre au moment de son exécution. Imaginez votre programme comme un réseau routier : chaque intersection représente une décision (un if, une boucle, un case). La complexité cyclomatique vous dit combien de routes différentes existent dans ce réseau pour aller du début jusqu'à la fin de la fonction.
Cette métrique s'applique fonction par fonction. Vous calculez la complexité cyclomatique d'une fonction donnée, vous regardez le résultat, et vous décidez si elle est trop complexe. C'est une approche granulaire qui vous permet de cibler exactement les parties de votre codebase qui méritent une attention particulière.
La règle fondamentale est simple : pour tester correctement une fonction, vous devez écrire au moins autant de tests que la valeur de sa complexité cyclomatique. Une fonction avec une complexité de 5 nécessite au moins 5 tests pour couvrir tous les chemins. Une fonction avec une complexité de 25 nécessite au moins 25 tests, ce qui devient rapidement ingérable. La complexité cyclomatique est avant tout une métrique de testabilité.
La formule derrière la complexité cyclomatique
McCabe a défini sa métrique à partir de la théorie des graphes. La formule complète est : M = E - N + 2P. Dans cette formule, M représente la complexité cyclomatique. E désigne le nombre d'arêtes du graphe de flux de contrôle, c'est-à-dire les connexions entre les nœuds. N représente le nombre de nœuds, autrement dit les blocs d'instructions séquentiels distincts. P représente le nombre de composantes connexes, ce qui correspond au nombre de fonctions analysées.
Pour une fonction unique, P vaut toujours 1. La formule se simplifie donc en M = E - N + 2. Vous dessinez le graphe de flux de contrôle de votre fonction, vous comptez les arêtes et les nœuds, et vous appliquez la formule.
En pratique, il existe un raccourci beaucoup plus rapide. La complexité cyclomatique d'une fonction est égale au nombre de points de décision qu'elle contient, plus un. Un point de décision est toute instruction qui crée un branchement : un if, un else if, un while, un for, un do-while, un case dans un switch, un opérateur ternaire. Vous parcourez votre fonction, vous comptez ces structures, vous ajoutez 1, et vous avez votre score.
Par exemple, une fonction sans aucune condition ni boucle a une complexité de 1. Dès que vous ajoutez un if, vous passez à 2. Avec une boucle for en plus, vous arrivez à 3. C'est aussi simple que ça.
Comment calculer la complexité cyclomatique à la main
Prenons une fonction Python réaliste appelée process_order, qui traite une commande dans un système e-commerce. Voici sa logique complète, décrite en prose.
La fonction commence par vérifier si la quantité en stock est suffisante pour honorer la commande. Si le stock est insuffisant, elle retourne une erreur immédiatement. Ensuite, elle examine le statut du client : est-ce un membre premium ou un client standard ? Si le client est premium, la fonction applique une remise de 15 %. Si le client n'est pas premium, la fonction vérifie si une campagne promotionnelle est en cours. Si une promotion est active, elle applique le taux de remise correspondant. Sinon, elle applique le tarif plein. Enfin, elle vérifie que le mode de paiement est valide avant de finaliser la commande.
Comptons les points de décision : vérification du stock (1), statut premium (2), promotion active (3), mode de paiement valide (4). La complexité cyclomatique vaut donc 4 + 1 = 5.
Un score de 5 est raisonnable. Pour couvrir tous les chemins, vous avez besoin de 5 tests unitaires : stock insuffisant, client premium avec paiement valide, client standard avec promotion, client standard sans promotion, paiement invalide. Cinq tests pour cinq chemins, c'est propre et gérable.

Maintenant imaginez qu'on ajoute des vérifications supplémentaires à cette même fonction : validation de l'adresse internationale, calcul de la TVA selon le pays, gestion des codes promo cumulables. Chacune de ces vérifications ajoute des points de décision. La complexité peut rapidement atteindre 15 ou 20, rendant la fonction très difficile à tester et à maintenir. C'est exactement ce que la métrique de McCabe vous aide à prévenir.
Ce que votre score signifie vraiment
Thomas McCabe a défini quatre zones d'interprétation qui sont devenues des références standard dans l'industrie.
Un score compris entre 1 et 10 indique une fonction simple et claire. C'est la zone verte. Votre fonction est facile à tester et un autre développeur pourra la lire sans aide particulière. La grande majorité de vos fonctions devrait se situer dans cette plage.
Un score compris entre 11 et 15 indique une complexité modérée. La fonction reste testable, mais elle commence à accumuler des responsabilités. C'est la zone orange : acceptable dans certains contextes, mais à surveiller. Si beaucoup de fonctions de votre codebase se situent dans cette plage, c'est un signal d'alerte précoce.
Un score compris entre 16 et 20 indique une complexité élevée. La zone rouge. Votre fonction est difficile à tester complètement et difficile à relire. Une refactorisation est recommandée : découpez cette fonction en plusieurs fonctions plus petites et plus spécialisées.
Un score supérieur à 21 indique une complexité critique. Ce type de fonction est une source connue de bugs et de régressions. Elle est pratiquement impossible à tester exhaustivement. McCabe lui-même recommandait de ne jamais dépasser 10 pour des fonctions en production.
Complexité cyclomatique vs complexité cognitive : la vraie différence
Ces deux métriques mesurent des choses différentes et sont souvent confondues. Comprendre la distinction vous aidera à choisir la bonne métrique selon l'objectif poursuivi.
La complexité cyclomatique mesure le nombre de chemins d'exécution indépendants. C'est une métrique orientée testabilité. Elle traite chaque structure de contrôle de manière uniforme, qu'elle soit imbriquée profondément ou au niveau racine. Un if au niveau racine et un if imbriqué dans une boucle contribuent chacun de manière identique au score.
La complexité cognitive, introduite par SonarSource en 2016, mesure à quel point votre code est difficile à lire pour un être humain. Elle prend en compte la profondeur des imbrications et ajoute des points supplémentaires à mesure que les structures s'emboîtent les unes dans les autres. Un if imbriqué dans une boucle coûte plus cher qu'un if au niveau racine.
La différence se révèle dans des cas concrets. Un switch avec 20 cases a une complexité cyclomatique de 20 mais une complexité cognitive faible : c'est une structure plate et facile à lire. À l'inverse, une série de if imbriqués sur 4 niveaux peut avoir une complexité cyclomatique modérée mais une complexité cognitive très élevée, car votre cerveau doit maintenir en mémoire tous les contextes d'imbrication. Les deux métriques se complètent : la première planifie vos tests, la seconde guide vos revues de code.

Les formules Excel imbriquées ont exactement le même problème
Si vous travaillez quotidiennement avec des tableurs, vous avez probablement rencontré la même problématique sous une forme différente. Une formule Excel avec plusieurs IF imbriqués est exactement analogue à une fonction de code avec une complexité cyclomatique élevée. Chaque IF imbriqué représente un point de décision supplémentaire.
Prenons un exemple concret. Vous construisez une formule qui calcule la remise selon le statut du client et le montant de la commande. Si le client est gold et la commande dépasse 1 000 euros, la remise est de 20 %. Si le client est gold mais entre 500 et 1 000 euros, la remise est de 15 %. Si le client est silver et dépasse 500 euros, la remise est de 10 %. Sinon, aucune remise. Cette logique implique 3 points de décision, pour une complexité équivalente de 4.
Cette formule s'écrit avec des IF imbriqués qui deviennent rapidement illisibles. Elle fonctionne, mais si vous devez la modifier six mois plus tard pour ajouter un niveau platinum, vous passerez beaucoup de temps à reconstituer la logique avant de toucher quoi que ce soit.
La solution : décomposez votre logique. Utilisez des colonnes intermédiaires pour calculer des valeurs partielles lisibles. Remplacez les IF imbriqués par des tables de correspondance exploitées avec XLOOKUP ou INDEX + EQUIV. Utilisez SWITCH si vous êtes sur Excel 365. Ces techniques réduisent la complexité de vos formules de la même manière que la refactorisation réduit la complexité du code.
Cinq façons de réduire votre complexité cyclomatique
La bonne nouvelle : la complexité cyclomatique peut presque toujours être réduite sans modifier le comportement de votre code. Voici cinq techniques éprouvées.
La première technique est l'utilisation de clauses de garde. Au lieu d'imbriquer votre logique principale dans des conditions de validation successives, vous effectuez vos validations en début de fonction et vous sortez immédiatement si les conditions ne sont pas remplies. Votre code principal se retrouve ainsi à plat, sans imbrication inutile, et beaucoup plus facile à lire. Cette technique est particulièrement efficace pour les fonctions qui doivent gérer plusieurs cas d'erreur en début de traitement. Elle réduit à la fois la complexité cyclomatique et la complexité cognitive.
La deuxième technique est l'extraction de fonctions. Si votre fonction fait trop de choses à la fois, découpez-la en plusieurs fonctions plus petites, chacune avec une seule responsabilité. Une fonction qui vérifie les conditions, calcule les remises et effectue le paiement en fait trop. Extrayez chaque groupe de logique dans une fonction dédiée. Chacune aura une complexité bien plus faible, et l'ensemble sera plus facile à tester individuellement sans risque d'effet de bord.
La troisième technique consiste à remplacer les structures conditionnelles complexes par des tables de correspondance ou des dictionnaires. Lorsque vous avez un switch avec de nombreux cases associant une valeur d'entrée à un traitement, vous pouvez remplacer toute cette structure par un dictionnaire. Un accès à un dictionnaire n'est pas un point de décision pour la métrique de McCabe. Pour ajouter un nouveau cas, il suffit d'ajouter une entrée sans toucher à la logique de contrôle existante.
La quatrième technique est la séparation des préoccupations. Si votre fonction mélange logique métier, accès aux données, gestion d'erreurs et mise en forme des sorties, vous avez un problème de conception plus profond. Séparez ces préoccupations en couches distinctes. Votre logique métier doit être pure et indépendante des détails d'implémentation. Ce découpage réduira naturellement la complexité cyclomatique de chaque fonction, car chacune n'aura plus qu'un seul type de décisions à prendre.
La cinquième technique est l'utilisation des opérations fonctionnelles sur les collections. Des fonctions comme filter, map et reduce, ou leurs équivalents selon votre langage, permettent souvent d'exprimer des boucles avec des conditions internes de manière plus concise et sans augmenter la complexité cyclomatique. Une boucle for qui filtre et transforme des éléments peut être remplacée par une chaîne d'opérations déclaratives qui délèguent le filtrage à des fonctions dédiées plutôt qu'à des structures de contrôle explicites.
Les outils qui la calculent automatiquement
Vous n'avez pas à calculer la complexité cyclomatique à la main. Des outils automatisent cette analyse et vous fournissent un rapport complet en quelques secondes.
SonarQube et SonarCloud sont les références du secteur. Ils analysent votre code de manière continue, calculent la complexité de chaque fonction et vous alertent lorsqu'un seuil est dépassé. SonarCloud s'intègre dans vos pipelines d'intégration continue et signale les problèmes avant qu'ils n'atteignent la branche principale.
Radon est un outil en ligne de commande dédié à Python. Il calcule la complexité cyclomatique et attribue une note de A à F à chaque fonction. C'est l'outil idéal pour un projet Python qui souhaite intégrer une vérification de complexité dans son workflow de validation.
Pour JavaScript et TypeScript, la règle complexity intégrée dans ESLint vous donne des alertes directement dans votre éditeur pendant que vous tapez. Configurez un seuil maximum dans votre fichier ESLint et recevez un avertissement en temps réel à chaque dépassement.
Lizard est un outil multilangage qui supporte Python, Java, C++, JavaScript, Swift et Go. Il calcule la complexité cyclomatique, s'intègre dans les pipelines CI et génère des rapports dans plusieurs formats. C'est le bon choix si vous travaillez sur un projet qui mélange plusieurs langages.
CodeClimate est une plateforme en ligne qui présente la complexité et d'autres métriques dans un tableau de bord avec l'historique d'évolution. Elle s'intègre avec GitHub et GitLab pour afficher les indicateurs de qualité directement dans vos pull requests, ce qui facilite les revues de code axées sur la qualité structurelle.