Complexité cyclomatique : quoi refactoriser avant de shipper

Résumé

La complexité cyclomatique est une métrique de 1976 qui compte les chemins d'exécution indépendants. Scores au-delà de 10 demandent des tests ; au-delà de 20, refactorisez avant de shipper ; au-delà de 50, réécrivez. Utilisée comme carte de décision plutôt que note de qualité, elle donne votre queue de priorités : les fonctions CC élevées que touche votre feature suivante sont votre vrai sprint.

Code complexity visualization with decision tree

Complexité cyclomatique : quoi refactoriser avant de rien construire

Vous ouvrez le dépôt à 22h00. Vous savez que vous voulez shipper la feature suivante. Vingt minutes plus tard, vous debuggez quelque chose sans rapport. La fonction sur laquelle vous travaillez fait 300 lignes, gère six cas différents, et personne , ni vous-même , ne se souvient pourquoi elle a grandi comme ça.

La complexité cyclomatique (CC) aurait signalé cette fonction il y a des mois. Elle compte les chemins d'exécution indépendants dans votre code. Les fonctions au-delà de CC 10 ont besoin de tests avant qu'on les touche. Au-delà de CC 20, on refactorise avant d'ajouter quoi que ce soit. Au-delà de CC 50, on réécrit. C'est l'arbre de décision. La suite de cet article explique comment exécuter ce scan sur votre projet dès aujourd'hui et transformer le résultat en sprint.

Ce que la complexité cyclomatique mesure vraiment

Thomas McCabe a publié cette métrique en 1976. La formule est M = E - N + 2P, où E représente les arêtes, N les nœuds, et P les composantes connexes du graphe de flux de contrôle d'une fonction. En pratique : vous commencez à 1, vous ajoutez 1 pour chaque if, else if, for, while, case, catch, && ou ||. C'est le score.

Une fonction sans branchement a CC = 1. Une fonction qui vérifie dix conditions, itère sur une liste et gère trois types d'erreurs atterrira autour de CC 15-20. Une fonction écrite par six devs qui se sont patchés mutuellement leur logique sur deux ans peut atteindre CC 60.

La métrique ne capture pas tout. Elle ne mesure pas la profondeur d'imbrication, la qualité des noms, ou la clarté de l'abstraction. Une fonction peut avoir un CC faible et rester pénible à lire. Mais une fonction avec CC au-delà de 25 est presque toujours pénible à lire. La corrélation dans une direction est assez stable pour servir de filtre de priorité.

Les seuils qui décident votre prochain pas

La plupart des styles guides et outils d'analyse statique convergent sur des plages similaires :

La recommandation originale du NIST était un plafond de 10 par fonction. En pratique, la plupart des engineers considèrent 15 comme une limite raisonnable pour le code en production, et tout ce qui dépasse 20 comme un bloqueur pour tout travail nouveau sur cette fonction.

Le tableau est votre arbre de décision pour ce qu'il faut construire ensuite. Quand votre backlog déborde et que vous ne savez pas si ajouter une feature ou corriger un module existant, lancez d'abord un scan de complexité. Les fonctions au-delà de 20 sont votre arrêt obligatoire avant que quelque chose de nouveau les touche.

Lancer votre premier scan en 5 minutes

Chaque grand écosystème de langage a un outil CLI prêt à l'emploi. Vous n'avez pas besoin d'un dashboard SaaS pour cette première étape :

Python (Radon) :

pip install radon
radon cc -s -a ./src

JavaScript / TypeScript (ESLint) :

// .eslintrc
{ "rules": { "complexity": ["error", 10] } }

Puis lancez eslint ./src --ext .ts,.js dans votre pipeline.

Go (gocyclo) :

go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
gocyclo -over 10 ./...

Java / Kotlin (PMD) :

pmd check -d ./src -R rulesets/java/quickstart.xml | grep CyclomaticComplexity

Le résultat énumère chaque fonction triée par score CC. Ce que vous cherchez : les 10 fonctions avec les plus hauts scores du projet. Sauvegardez cette liste. C'est votre queue de priorités pour le prochain sprint.

Abstract visualization of code branching paths with complexity heatmap

Lire le rapport comme une carte de décision

Les fonctions à CC élevé se regroupent en endroits prévisibles. Sur une API de recommandation sur laquelle j'ai travaillé , environ 8 000 lignes , lancer radon cc -s a surfacé trois fonctions avec CC au-delà de 30. Les trois étaient dans la couche de normalisation des données. Cette couche s'était fait patcher progressivement sur six mois, chaque fois pour un cas de boîte noire d'une source de données légèrement différente. Personne n'avait jamais regardé les dégâts cumulés.

Chaque nouvelle feature touchant la couche de normalisation prenait 40% plus longtemps à shipper. Nous avons passé deux jours avant le prochain sprint à refactoriser ces trois fonctions, ramenant les scores CC de 32, 28 et 31 à 7, 5 et 8. Le sprint suivant a été le plus rapide en quatre mois.

Trois fonctions. Deux jours. Quatre mois de drag, expliqués.

Le pattern est constant dans les codebases : environ 10-15% des fonctions portent 70-80% de la complexité dans n'importe quel projet qui a grandi organiquement. Cette concentration est votre vraie roadmap. Avant de vous engager sur une nouvelle feature, lancez le scan. Si votre feature touche une fonction au-delà de CC 15, vous avez un choix concret : la shipper lentement dans du code risqué maintenant, ou passer deux jours à refactoriser et la shipper correctement la semaine d'après. Les maths favorisent généralement le refactoring.

Pour les side projects particulièrement, c'est plus critique que dans les settings en équipe. Vous ne pouvez pas passer une fonction gnarly à un collègue. Le coût cognitif complet de re-comprendre du code CC 30 tombe sur vous, généralement à 22h00, six mois après l'avoir écrit.

Le deuxième truc que les rapports de complexité font bien : ils vous donnent une réponse concrète quand vous êtes paralysé entre « ajouter une feature » et « d'abord nettoyer ». Le scan enlève l'ambiguïté. Si la feature touche du code CC élevé, vous nettoyez d'abord. Si ce n'est pas le cas, vous shippez. C'est une décision, pas un débat.

Quand la complexité élevée est le bon appel

Pas toute fonction CC élevée est un problème.

Une machine à états de paiement gérant 12 états de transaction doit vraiment gérer 12 états. Un parser traitant 15 règles de grammaire a 15 vrais cas. Aplatir ça en 15 fonctions helper avec CC 1 chacune ne réduit pas la complexité , elle la dissémine dans des fichiers maintenant plus pénibles à naviguer ensemble.

La question utile n'est pas « ce CC est-il trop élevé ? » mais « ce CC est-il plus élevé que le domaine l'exige ? » Une fonction de routing gérant 15 patterns URL avec CC = 18 est probablement appropriée. Une fonction de mise à jour de profil utilisateur avec CC = 18 qui s'est gonflée par patches accumulés pour gérer des cas limites personne n'avait planifiés, c'est un autre problème.

Gardez CC sous 15 sur vos chemins critiques. Laissez-le monter plus haut dans les domaines vraiment complexes, et documentez la raison en ligne. Les fonctions qui vous surprennent plus tard sont toujours celles sans justification pour leur complexité, juste des fixes accumulés.

Developer analyzing code quality metrics on dual monitors in a dark workspace

Ajouter des vérifications de complexité au CI et arrêter de debugger dans le flou

Lancer le scan manuellement une fois est utile. L'accrocher au CI, c'est ce qui change vraiment le comportement au fil du temps.

Le setup pratique : fixez un seuil dans votre config de linter, faillez le build si une nouvelle fonction le dépasse, et sauvegardez le rapport de complexité comme artifact CI à chaque run. La liste existe à chaque build, sans que personne ait besoin de se souvenir de la générer.

Pour GitHub Actions avec un projet Python :

- name: Complexity check
  run: |
    pip install radon
    radon cc -n C -s ./src
    if [ $? -ne 0 ]; then exit 1; fi

Pour JavaScript avec ESLint déjà dans le pipeline, ajoutez "complexity": ["error", 12] à vos règles. N'importe quel PR qui pousse une fonction au-delà de 12 faillez automatiquement.

Un standard pratique en équipe : CC <= 15 comme block CI dur, CC 11-15 comme avertissement demandant un commentaire inline expliquant la raison métier. Ce deuxième tier force la conversation sans faire chaque code review une négociation sur les exceptions de seuil.

Trois outils à connaître pour le suivi en continu

Les outils CLI ci-dessus vous donnent un snapshot une fois. Pour la visibilité niveau projet sur le temps, trois outils ressortent :

SonarQube

SonarQube est l'option la plus complète pour le suivi de complexité au-delà du projet. L'édition communautaire couvre complexité cyclomatique, complexité cognitive, et couverture de test dans un dashboard. Les quality gates peuvent bloquer une fusion si du code nouveau pousse la complexité au-delà d'un seuil. C'est l'outil à atteindre en premier quand un projet a plus de deux contributeurs et un vrai processus de review.

CodeScene

CodeScene corrèle les scores de complexité avec l'historique des commits, surfaçant les fonctions qui sont à la fois complexes et modifiées fréquemment. Ces intersections sont votre vrai technical debt , pas juste pénible à comprendre, mais activement coûteux sur chaque sprint. Si vous avez besoin de faire un argument pour un sprint de refactoring sur la roadmap d'une équipe, le résultat de CodeScene est l'argument.

Code Climate Quality

Code Climate Quality est l'option plus légère, adaptée aux side projects et repos open-source. L'intégration GitHub fonctionne out-of-the-box, les défauts sont sensés, et le tier gratuit pour les repos publics est utilisable. Si vous managez un side project et voulez un drift de complexité suivi sans standing up infrastructure, commencez ici.

La décision que la complexité cyclomatique force vraiment

La complexité cyclomatique ne dit pas si votre code est bon. Elle dit où le risque est concentré. C'est une information plus utile.

Lancez le scan sur votre projet courant avant votre prochain sprint. Regardez les 10 fonctions top par score. Demandez-vous lesquelles votre feature suivante touche. Si la réponse est « trois d'entre elles, toutes au-delà de CC 20 », votre sprint est clair : ramenez ces trois fonctions sous CC 10, puis shippez la feature. Ça prendra la moitié du temps que ça aurait pris autrement.

Le corollaire pour les builders : si vous cherchez votre prochain side project et que vous travaillez dans des codebases réels, votre rapport de complexité est une spec. Le module le plus complexe bloquant la vélocité , un qui est complexe et touché constamment , est un outil qu'il vaut la peine de builder autour. Un dashboard léger de complexité qui corrèle les scores CC avec les données git blame et la fréquence de fichiers changés, priçé pour une équipe de deux plutôt qu'un contrat enterprise, est un projet qui n'existe pas vraiment encore. Le gap de tooling est réel.

C'est pas un pitch. C'est un pattern : les outils que vous voulez mais que vous trouvez pas, c'est souvent les side projects qui valent le coup de shipper.

Questions fréquentes

Quel est un bon score de complexité cyclomatique ?
Un CC entre 1 et 10 est généralement acceptable. La recommandation originale du NIST était un plafond de 10 par fonction. Les scores entre 11 et 20 méritent une review et une couverture de test avant modification. Tout au-delà de 20 est un candidat fort pour refactoring avant que du travail de feature neuf ne touche le même code.
Comment calculez-vous la complexité cyclomatique ?
La formule est M = E - N + 2P, où E représente les arêtes, N les nœuds, et P les composantes connexes du graphe de flux de contrôle. En pratique, commencez à 1 et ajoutez 1 pour chaque if, else if, for, while, case, catch, et opérateurs logiques && et ||. Chaque outil d'analyse statique majeur le fait automatiquement.
Comment la complexité cyclomatique affecte-t-elle les side projects ?
Dans les builds solo, le CC élevé s'accumule vite. Vous n'avez pas d'équipe avec qui partager le contexte. Une fonction écrite à CC 30 quatre mois ago va vous coûter un jour de re-compréhension avant de pouvoir la modifier en sécurité. Garder les fonctions de chemin critique sous CC 15 est un des investissements au plus haut levier pour la vélocité de shipping future.
Est-ce que la complexité cyclomatique est la même chose que la complexité cognitive ?
Non. La complexité cyclomatique compte les chemins indépendants dans le flux de contrôle, ce qui mappe directement au nombre minimum de cas de test nécessaires. La complexité cognitive, développée par SonarSource, pèse la profondeur d'imbrication et les cassures de flux de contrôle , elle reflète mieux l'effort mental de lire du code. Les deux sont utiles ; le CC est plus universellement supporté dans la tooling.
Quand devrais-je récrire au lieu de refactoriser ?
Quand une fonction a CC au-delà de 50 et pas de couverture de test significative, refactoriser en sécurité demande des tests que vous ne pouvez pas écrire parce que le code est trop enchevêtré pour être testé en isolation. Une réécriture avec tests à partir de zéro est souvent plus rapide et sûre. La plupart des engineers mettent le point d'inflexion pratique entre CC 40 et 60.
Quels outils mesurent la complexité cyclomatique ?
Radon pour Python, la règle de complexité ESLint pour JavaScript et TypeScript, gocyclo pour Go, PMD pour Java, SonarQube pour les projets enterprise multi-langage, Code Climate Quality pour une option plus légère multi-langage, et CodeScene pour la complexité corrélée avec l'historique git. La plupart des plateformes CI s'intègrent à au moins un de ceux-là.
Est-ce que la complexité cyclomatique peut être artificiellement trop basse ?
Oui. Extraire chaque branche dans un helper une ligne baisse le CC de la fonction originale sans réduire la complexité réelle , elle la dissémine dans les fichiers. Une fonction avec CC 1 qui délègue tout branchement à des helpers opaques est trompeuse. Utilisez le CC comme un signal parmi d'autres aux côtés d'une review de lisibilité, pas comme un nombre à minimiser à tout prix.