Chaque fonction que vous écrivez possède un score de complexité. Si la fonction ne contient aucun point de décision, aucun score n'est attribué. if, non else, pas de boucle, pas switchSa complexité cyclomatique est de 1 : il existe exactement un chemin à travers le code. Ajoutez un if Ajoutez une instruction et elle devient 2. Ajoutez-en une autre et elle devient 3. Lorsqu'une fonction a accumulé une douzaine de branches conditionnelles sur plusieurs niveaux imbriqués, sa complexité cyclomatique peut être de 15 ou 20, et la tester complètement nécessite un nombre correspondant de cas de test, chacun couvrant un chemin d'exécution distinct.
La complexité cyclomatique (CC) a été introduite par Thomas J. McCabe en 1976 comme mesure quantitative de la complexité logique du flux de contrôle d'un programme. Elle demeure l'une des métriques de qualité de code les plus utiles en pratique, car ses implications sont concrètes et exploitables : un score de complexité indique le nombre minimal de cas de test nécessaires pour une couverture complète du code, prédit la difficulté de compréhension et de modification de ce dernier, et identifie les fonctions les plus susceptibles de contenir des défauts non détectés. Ce guide présente la formule, les seuils, des exemples spécifiques à chaque langage, les techniques de refactorisation permettant de réduire la complexité, ainsi que la méthode pour la mesurer et la suivre automatiquement.
SMART TS XL
Vous aide à maîtriser la complexité cyclomatique, à optimiser les performances et à prévenir les bugs cachés
EN SAVOIR PLUS…Qu'est-ce que la complexité cyclomatique ?
La complexité cyclomatique mesure le nombre de chemins linéairement indépendants dans le code source d'un programme. Thomas J. McCabe l'a dérivée de la théorie des graphes : tout programme peut être représenté par un graphe de flux de contrôle où les nœuds sont des instructions et les arêtes les flux possibles entre elles. La formule est :
CC = E - N + 2P
Où? :
- E = nombre d'arêtes dans le graphe de flux de contrôle
- N = nombre de nœuds
- P = nombre de composantes connexes (généralement 1 pour une seule fonction)
Pour des calculs pratiques, il existe un équivalent plus simple : CC = nombre de points de décision + 1. . Every Chaque if, else if, while, for, case, catch, && et || ajoute un point de décision. Le coefficient de corrélation initial de toute fonction est 1.
Java
// CC = 1: no decision points
public String greet(String name) {
return "Hello, " + name;
}
// CC = 3: two decision points (two if statements)
public String classify(int score) {
if (score >= 90) return "Excellent";
if (score >= 70) return "Satisfactory";
return "Needs improvement";
}
// CC = 5: four decision points (three conditions + one loop)
public double calculateTotal(List<Item> items, boolean isMember, boolean isHoliday) {
double total = 0;
for (Item item : items) { // +1
total += item.getPrice();
}
if (isMember) total *= 0.9; // +1
if (isHoliday) total *= 0.95; // +1
if (total > 100) total -= 5; // +1
return total;
}
Seuils : Quel score est acceptable ?
Les directives initiales de McCabe, qui restent les plus citées, définissent quatre niveaux de risque :
| Score CC | Niveau de risque | Interprétation |
|---|---|---|
| 1 – 10 | Low | Simple, bien structuré, facile à tester |
| 11 – 20 | Modérée | Plus complexe ; efforts de test accrus requis |
| 21 – 50 | Haute | Complexe et difficile à tester ; une refactorisation est recommandée. |
| > 50 | Très élevé | Impossible à tester en pratique ; risque de qualité important |
Le seuil de 10 est la limite la plus couramment appliquée dans les contrôles qualité CI/CD. Le seuil de complexité cognitive par défaut de SonarQube est de 15 (une métrique apparentée, mais distincte). Les recommandations du NIST pour les systèmes critiques de sécurité préconisent un maximum de 10 par module.
Une nuance importante : le coefficient de complexité (CC) mesure la complexité structurelle, et non la complexité sémantique. Une fonction avec un CC de 8 qui effectue un calcul financier complexe peut être plus difficile à comprendre qu’une fonction avec un CC de 15 qui se compose de simples vérifications défensives. Utilisez le CC comme un signal d’alarme, et non comme un verdict définitif.
Complexité cyclomatique dans différentes langues
Python
Les mécanismes de décision de Python qui contribuent à CC : if, elif, else (non comptabilisé, il n'a pas de condition), for, while, try/except (chaque except comptes), with (ne compte pas), et les opérateurs booléens and/or dans des conditions.
python
# CC = 1
def format_name(first: str, last: str) -> str:
return f"{first} {last}"
# CC = 4: three decision points
def calculate_discount(price: float, is_member: bool, is_holiday: bool) -> float:
discount = 0.0
if is_member: # +1
discount += 0.10
if is_holiday: # +1
discount += 0.05
if price > 100: # +1
discount += 0.02
return price * (1 - discount)
# CC = 6: five decision points (list comprehension counts as a loop)
def process_orders(orders: list[dict]) -> list[dict]:
return [
{**order, "total": order["qty"] * order["price"]} # +1 (comprehension)
for order in orders
if order["qty"] > 0 # +1 (filter condition)
if order["price"] > 0 # +1 (second filter)
]
Outils pour la mesure du CC en Python : radon (radon cc src/ -s), flake8-cognitive-complexity, pylint avec le plugin de complexité, analyse Python de SonarQube.
Java
Java
// CC = 7: complex authentication with multiple conditions
public AuthResult authenticate(String userId, String password, boolean isMfa) {
if (userId == null || password == null) return AuthResult.INVALID; // +2 (||)
User user = userRepository.findById(userId);
if (user == null) return AuthResult.NOT_FOUND; // +1
if (!user.checkPassword(password)) return AuthResult.WRONG_PASSWORD;// +1
if (isMfa && !user.hasMfaEnabled()) return AuthResult.MFA_REQUIRED; // +2 (&&)
return AuthResult.SUCCESS;
}
// CC = 1 + 2 + 1 + 1 + 2 = 7
Le réduire :
Java
// After refactoring: CC = 3 (main method) + small helpers with CC = 2 each
public AuthResult authenticate(String userId, String password, boolean isMfa) {
if (hasInvalidInputs(userId, password)) return AuthResult.INVALID;
User user = findVerifiedUser(userId, password);
if (user == null) return AuthResult.WRONG_PASSWORD;
if (requiresMfa(user, isMfa)) return AuthResult.MFA_REQUIRED;
return AuthResult.SUCCESS;
}
private boolean hasInvalidInputs(String userId, String password) {
return userId == null || password == null; // CC = 2
}
private boolean requiresMfa(User user, boolean isMfa) {
return isMfa && !user.hasMfaEnabled(); // CC = 2
}
Outils pour Java CC : Checkstyle, PMD, SonarQube, intégré à IntelliJ IDEA, SMART TS XL.
C# et TypeScript
C# et TypeScript suivent les mêmes règles que Java. La principale différence réside dans l'ajout de points de décision : les clauses d'expression LINQ en C# et les chaînes ternaires en TypeScript.
tranchant
// CC = 5: switch with four cases
public decimal GetShippingCost(string zone) => zone switch {
"domestic" => 5.99m, // +1
"eu" => 15.99m, // +1
"international" => 29.99m, // +1
"express" => 49.99m, // +1
_ => throw new ArgumentException($"Unknown zone: {zone}")
};
COBOL
Les structures de décision de COBOL qui contribuent à la CC : IF/ELSE, EVALUATE WHEN (chaque clause WHEN), PERFORM UNTIL, PERFORM VARYING ... WITH TEST BEFORE/AFTER, AT END, ON EXCEPTION, NOT ON EXCEPTION, ON SIZE ERROR.
Cobol
CALCULATE-DISCOUNT.
IF WS-CUSTOMER-TYPE = 'GOLD' *> +1
IF WS-PURCHASE-AMT > 1000 *> +1
COMPUTE WS-DISCOUNT = 0.20
ELSE *> (no increment)
COMPUTE WS-DISCOUNT = 0.15
ELSE IF WS-CUSTOMER-TYPE = 'SILVER' *> +1
COMPUTE WS-DISCOUNT = 0.10
ELSE *> (no increment)
COMPUTE WS-DISCOUNT = 0.05
END-IF
EVALUATE TRUE
WHEN WS-REGION = 'NORTH' PERFORM APPLY-REGIONAL-RATE *> +1
WHEN WS-REGION = 'SOUTH' PERFORM APPLY-SOUTHERN-RATE *> +1
END-EVALUATE.
*> Total CC = 1 + 5 = 6
La syntaxe verbeuse du COBOL implique que les paragraphes sont généralement plus longs que les fonctions équivalentes dans les langages modernes. Les programmes COBOL avec plus de 50 caractères par paragraphe sont fréquents dans les bases de code existantes et constituent les cibles prioritaires pour la refactorisation et la modernisation.
Comment calculer la complexité cyclomatique : trois méthodes
Méthode 1 : Compter les points de décision + 1 La méthode manuelle la plus rapide. Comptez chaque if, else if, while, for, case, catch, &&, || Dans la fonction. Ajoutez 1 pour la fonction elle-même.
Méthode 2 : Graphique de flux de contrôle Représentez la fonction sous forme de graphe : un nœud par instruction ou bloc, des arêtes pour chaque flux de contrôle. Appliquez CC = E - N + 2.
Méthode 3 : Outil automatisé. C’est la seule méthode pratique pour tout ce qui dépasse une fonction triviale. La plupart des outils d’analyse statique calculent automatiquement le coefficient de corrélation (CC) et l’intègrent aux pipelines CI/CD.
Techniques de refactorisation qui réduisent réellement la complexité
Clauses de protection (Retours anticipés)
Les clauses de garde interrompent la fonction prématurément lorsque les préconditions échouent, éliminant ainsi les branches else et réduisant la profondeur d'imbrication.
python
# Before: deeply nested, CC = 5
def process_order(order):
if order is not None:
if order.is_valid():
if order.has_stock():
if order.payment_cleared():
return fulfill_order(order)
else:
return "Payment failed"
else:
return "Out of stock"
else:
return "Invalid order"
else:
return "No order"
# After: flat, CC = 5 (same complexity, dramatically better readability)
def process_order(order):
if order is None: return "No order"
if not order.is_valid(): return "Invalid order"
if not order.has_stock(): return "Out of stock"
if not order.payment_cleared(): return "Payment failed"
return fulfill_order(order)
Le coût de la complexité (CC) ne diminue pas, les mêmes points de décision existent, mais le code devient beaucoup plus facile à lire et à tester. Une véritable réduction du CC nécessite l'élimination de points de décision, et non leur simple réorganisation.
Méthodes d'extraction
Le fait de déplacer des groupes logiques de décisions dans des méthodes nommées réduit la complexité de la fonction appelante tout en répartissant cette complexité sur des unités plus petites et testables.
Java
// Before: one method doing everything, CC = 9
public double calculateInvoiceTotal(Invoice invoice, Customer customer) {
double subtotal = 0;
for (LineItem item : invoice.getItems()) {
subtotal += item.getQuantity() * item.getUnitPrice();
if (item.isTaxable()) subtotal += item.getPrice() * 0.1;
}
if (customer.isMember()) subtotal *= 0.9;
if (customer.hasVoucher()) subtotal -= customer.getVoucherValue();
if (subtotal < 0) subtotal = 0;
return subtotal;
}
// After: main method CC = 4, helpers have CC = 2-3 each
public double calculateInvoiceTotal(Invoice invoice, Customer customer) {
double subtotal = computeLineItemTotal(invoice.getItems());
subtotal = applyCustomerDiscounts(subtotal, customer);
return Math.max(0, subtotal);
}
Remplacer les conditionnelles par le polymorphisme
Lorsqu'une fonction se divise en fonction de son type ou de son état, le polymorphisme élimine complètement cette division.
Java
// Before: switch on payment type, CC grows with each new type
public void processPayment(String type, double amount) {
switch (type) {
case "CREDIT": processCreditCard(amount); break;
case "PAYPAL": processPayPal(amount); break;
case "CRYPTO": processCrypto(amount); break;
default: throw new IllegalArgumentException("Unknown type: " + type);
}
}
// After: new payment types require no changes to this method, CC = 1
public interface PaymentProcessor {
void process(double amount);
}
public void processPayment(PaymentProcessor processor, double amount) {
processor.process(amount); // no branching
}
Décomposition des conditionnelles complexes
Extraire les expressions booléennes complexes dans des méthodes nommées qui révèlent leur intention.
python
# Before: dense boolean logic, hard to understand, easy to mis-test
if user.age >= 18 and user.country in ALLOWED_COUNTRIES and not user.is_banned and user.verified:
grant_access()
# After: named predicate, self-documenting, unit-testable independently
def is_eligible_for_access(user: User) -> bool:
return (
user.age >= 18
and user.country in ALLOWED_COUNTRIES
and not user.is_banned
and user.verified
)
if is_eligible_for_access(user):
grant_access()
Complexité cyclomatique dans les pipelines CI/CD
L'application automatisée des règles de contrôle de code dans les pipelines CI/CD empêche la complexité de s'accumuler de manière invisible entre les revues de code.
yaml
# GitHub Actions: fail PR if any function exceeds CC threshold
name: Code Quality
on: [pull_request]
jobs:
complexity-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install radon (Python CC tool)
run: pip install radon
- name: Check cyclomatic complexity
run: |
radon cc src/ --min C --show-complexity
# Fails if any function has CC grade C (11-15) or worse
radon cc src/ --min C --total-average | grep -q "Average complexity" \
&& echo "Complexity check passed" \
|| (echo "Functions with high complexity found" && exit 1)
Pour Java avec SonarQube :
yaml
# SonarQube quality gate blocks merge if CC exceeds threshold
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: |
-Dsonar.qualitygate.wait=true
-Dsonar.java.complexity.Function.threshold=10
Le contrôle qualité devrait bloquer le nouveau code à haute complexité, et non l'ensemble du code existant, qui peut déjà présenter un taux de complexité élevé traité séparément.
Complexité cyclomatique dans COBOL et les bases de code héritées
Pour les systèmes d'entreprise où les programmes COBOL peuvent comporter des paragraphes dont la complexité cyclomatique dépasse 50, voire 100, l'analyse de la complexité cyclomatique remplit une fonction principale différente de celle qu'elle remplit pour les bases de code modernes. La question n'est pas « faut-il refactoriser cette fonction ? », mais plutôt « quel est le risque de migration de ce programme, et dans quel ordre faut-il le moderniser ? »
Un programme COBOL dont le paragraphe principal présente un score CC de 80 possède 80 chemins d'exécution indépendants, chacun nécessitant un cas de test pour être validé. Si le programme ne comporte pas de cas de test, comme c'est le cas pour la plupart des programmes COBOL existants, le score CC est le principal indicateur du nombre de scénarios de validation à mettre en place avant de pouvoir considérer toute conversion comme sûre.
SMART TS XL's analyse de code statique Il calcule la complexité cyclomatique pour COBOL, JCL, RPG, PL/I et tous les langages modernes simultanément, produisant ainsi la distribution de la complexité cyclomatique au niveau du portefeuille qui permet de fonder les décisions de séquencement de la modernisation sur des données probantes. Les programmes ayant les scores de complexité cyclomatique les plus élevés et le plus grand nombre d'appelants (forte interconnexion) sont les cibles de migration les plus à risque, comme décrit dans le contexte de Établissement de métriques d'indice de maintenabilité pour les applications COBOLCC est un élément d'un tableau de qualité plus large qui inclut également le volume Halstead et les lignes de code.
La capacité d'analyse d'impact utilise la classification de complexité basée sur le CC pour définir ce qui doit être validé lors de toute modification d'un programme à haute complexité : un CC plus élevé signifie plus de chemins d'exécution, ce qui signifie plus de scénarios de test qui doivent être vérifiés pour confirmer l'équivalence comportementale avant et après toute modification.
Pour les équipes planifiant des programmes de modernisation des systèmes existants , la répartition des CC au sein du portefeuille est l'élément déterminant du séquençage des vagues de migration : les programmes à faible CC avec peu d'utilisateurs migrent en premier ; les programmes à CC élevé avec de nombreux utilisateurs migrent en dernier, une fois que l'équipe a acquis une expertise sur les composants les plus simples et que l'infrastructure de test est en place pour valider les composants complexes.
La complexité n'est pas l'ennemie, c'est la complexité invisible qui l'est.
La complexité cyclomatique est l'une des rares métriques de qualité du code directement liée à la testabilité : le nombre de cas de test requis pour une couverture complète des chemins d'exécution est, par définition, au moins égal au score de complexité cyclomatique. Ce lien la rend concrètement exploitable, contrairement à de nombreuses autres métriques de qualité.
La gestion du changement climatique exige de comprendre que la complexité s'accumule progressivement. if L'ajout d'une instruction à une fonction en croissance est, individuellement, justifié. Après trois ans de décisions prises de manière tout aussi justifiée, on peut se retrouver avec une fonction dont la complexité (CC) atteint 40, une fonction que personne n'ose modifier car elle est tout simplement trop complexe pour être analysée en toute sécurité. Les outils et techniques présentés dans ce guide – clauses de garde, extraction de méthodes, polymorphisme, décomposition conditionnelle et contrôles qualité CI/CD – visent à prévenir cette accumulation insidieuse et à la traiter systématiquement lorsqu'elle s'est déjà produite.