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.
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 :
CC 1-10 (Acceptable) : shippez
CC 11-20 (Attention requise) : ajoutez des tests avant de modifier
CC 21-50 (Risque élevé) : refactorisez avant d'ajouter des features
CC > 50 (Non testable) : réécrivez, ne patchchez pas
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 ./srcJavaScript / 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 CyclomaticComplexityLe 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.

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.

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; fiPour 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.