Dans cet article, nous explorons pourquoi l’architecture C procédurale d’EDG repose sur des dispatchers monolithiques et des fichiers d’en-tête interconnectés, et pourquoi tenter de les remanier nuirait en réalité aux performances et à la maintenabilité.
Explorons la Smart Code City de CppDepend pour le front-end EDG :
Ce que les motifs visuels révèlent dans le front-end EDG
En observant la vue d’ensemble de la ville d’EDG, plusieurs traits macro-architecturaux deviennent immédiatement visibles :
- Les dômes signalent un couplage élevé dans les quartiers centraux : remarquez les groupes de bâtiments teintés de rouge coiffés de dômes. Dans la 3D City de CppDepend, un dôme indique précisément une fonction à fort couplage, c’est-à-dire qu’elle dépend fortement de nombreuses autres parties de la base de code ou interagit avec elles. Dans EDG, les tâches centrales comme l’évaluation d’expressions, la résolution de types et le traitement des déclarations sollicitent constamment les tables d’état globales et les structures de l’AST. Cette forte interdépendance (couplage efférent) fait apparaître des dômes dans les principaux quartiers du code.
- Piliers de dispatch monolithiques vs quartiers plats : plusieurs monolithes hauts et distincts, à l’empreinte large, dominent les blocs plats environnants. Ils correspondent à des fonctions comme
process_expr_work: de grandes boucles switch procédurales en C de plusieurs milliers de lignes, à la complexité cyclomatique élevée. - Regroupement des couleurs (dominance rouge/orange dans les modules centraux) : tandis que les modules utilitaires affichent des surfaces vertes/bleu-vert plus froides, le quartier central du front-end C++ (
cpfe) présente des couleurs chaudes très répandues. Dans un logiciel classique, un rouge omniprésent signale un besoin urgent de remaniement ; dans un compilateur C industriel, cela prouve visuellement à quel point l’ensemble du pipeline d’analyse et d’évaluation doit être étroitement intégré pour traiter efficacement la sémantique complexe du C++. - Des contours verts et bleus témoignent d’une bonne santé des modules : remarquez que les bordures entourant les zones des modules principaux restent vertes et bleues. Alors que les fonctions individuelles sont très complexes, les contours de CppDepend reflètent la santé architecturale de haut niveau, la conformité aux règles et des limites de modules propres.
Le noyau monolithique : process_expr_work
Dans la Smart Code City de CppDepend, la fonction process_expr_work dans src/interpret.c se distingue comme l’une des plus grandes structures de toute la base de code EDG :
- Lignes de code (LOC) : 2 911
- Complexité cyclomatique (CC) : 1 229
- Couplage efférent (EC) : 506
Pour un outil d’analyse statique classique, ces métriques déclenchent immédiatement des alertes de remaniement hautement prioritaires. Pourtant, la compréhension de la conception C procédurale d’EDG révèle pourquoi cette structure est à la fois intentionnelle et efficace.
Pourquoi process_expr_work est si longue : la boucle de dispatch procédurale en C
Parce qu’EDG a été historiquement développé en C procédural, il n’utilise pas les fonctionnalités orientées objet du C++ comme l’héritage de classes ou les tables de fonctions virtuelles.
À la place, le parcours de l’AST (arbre syntaxique abstrait) et l’évaluation des expressions reposent sur un dispatch explicite, basé sur des étiquettes :
- Discriminants par étiquettes : les nœuds de l’AST sont de simples pointeurs de structs C étiquetés par des identifiants d’énumération représentant les catégories d’expressions (par ex.
EXPR_BINARY,EXPR_CAST,EXPR_CALL). - Switch procédural centralisé :
process_expr_workjoue le rôle de boucle de dispatch principale de l’interpréteur. Elle exécute une unique et massive instruction switch en C qui se branche sur des centaines de catégories de nœuds de l’AST. - Allocation locale de registres & zéro surcoût d’appel : conserver les étapes d’évaluation des expressions dans la portée d’une seule fonction évite d’empiler des cadres de pile ou de réaliser des appels de fonctions indirects pendant les parcours récursifs de l’AST. Cela permet aussi aux compilateurs C classiques d’optimiser l’allocation des registres sur les variables temporaires locales.
Comment Clang implémente le même comportement
Clang analyse et évalue les expressions pour exactement la même spécification du langage C++, mais adopte une approche C++ orientée objet. Comparer le dispatch C procédural d’EDG à Clang montre comment les paradigmes de langage modifient la signature de l’analyse statique tout en préservant les exigences architecturales sous-jacentes :
| Métrique / Caractéristique | EDG (process_expr_work) | Clang (ExprConstant.cpp) |
|---|---|---|
| Paradigme de langage | C procédural | C++ moderne (orienté objet) |
| Modèle d’architecture | Boucle de dispatch centralisée à switch étiqueté | Patron Visiteur d’AST (ConstStmtVisitor) |
| Structure du code | Fonction monolithique unique (2 900+ LOC) | Distribuée dans une hiérarchie de classes (VisitExpr, VisitCast, etc.) |
| Signature d’analyse statique | Complexité verticale : CC élevée (1 229) dans une seule fonction | Complexité horizontale : CC plus faible par méthode, mais plus de classes |
| Pile d’appels & mémoire | Faible surcoût de pile ; réutilisation optimisée des registres | Appels de méthodes virtuelles/surchargées à travers les hiérarchies de classes |
| Passage de contexte | Accès direct à la portée locale de la fonction & à l’état | Objets de contexte explicites (EvalInfo&) passés aux méthodes |
Où va la complexité
Dans EDG : la complexité est verticale et localisée. Elle s’accumule dans process_expr_work, créant une complexité cyclomatique élevée (1 229) dans une seule unité de traduction.
Dans Clang : la complexité est horizontale et distribuée. Clang répartit la logique d’évaluation entre des méthodes Visit* individuelles réparties sur plusieurs classes d’évaluateurs (IntExprEvaluator, FloatExprEvaluator, LValueExprEvaluator).
Si Clang évite les fonctions uniques de 3 000 lignes, sa complexité systémique totale reste identique, car évaluer des expressions C++ — avec toutes les conversions implicites, surcharges d’opérateurs, instanciations de templates et cas limites de dialectes — exige de prendre en compte exactement le même ensemble de règles.
Qu’elle soit représentée par un switch C procédural de 2 900 lignes dans EDG ou par 50 méthodes visiteurs réparties dans une hiérarchie de classes dans Clang, la complexité métier sous-jacente reste inchangée.
Cycles de dépendances au niveau fichier : lire la matrice (DSM)
Le passage de la Smart Code City à la Dependency Structure Matrix (DSM) de CppDepend révèle une autre signature majeure de l’analyse statique : des cycles de dépendances denses au niveau fichier entre les unités de traduction centrales.
En observant la grille DSM, des fichiers comme src/class_decl.c, src/declarator.c, src/decls.c et src/expr.c forment une composante fortement connexe (SCC) bien visible :
- Cellules bleues : indiquent des dépendances unidirectionnelles et en couches (le fichier A dépend du fichier B, mais pas l’inverse).
- Cellules rouges : marquent des dépendances bidirectionnelles (cycles), où deux fichiers reposent directement ou indirectement sur les symboles et définitions l’un de l’autre.
- Nombres dans les cellules : indiquent le poids exact du couplage des membres (par ex.
declarator.cutilisant 31 membres dedecls.het 5 membres dedeclarator.h).
L’interdépendance class_decl.c && declarator.c
Dans l’architecture C/C++ d’entreprise traditionnelle, les dépendances circulaires entre unités de traduction sont considérées comme un défaut de conception grave.
Dissection du cycle : récursion mutuelle pragmatique vs couplage accidentel
Avec Code Quest dans CppDepend, nous pouvons inspecter les appels de fonctions précis qui alimentent le cycle bidirectionnel entre src/class_decl.c et src/declarator.c.
En interrogeant les méthodes de class_decl.c utilisées par declarator.c et inversement, nous révélons deux catégories distinctes de couplage : les cycles pragmatiques, qui reflètent des règles fondamentales du langage, et les cycles accidentels, qui peuvent être facilement remaniés.
Fonctions de declarator.c utilisées par class_decl.c :
Fonctions de class_decl.c utilisées par declarator.c :
L’intention du cycle de dépendances class_decl.c ↔ declarator.c
1. Récursion mutuelle pragmatique
(À conserver comme conception intentionnelle)
declarator()scan_lambda_declarator()abstract_class_diagnostic()
2. Couplage accidentel / cibles de remaniement
(Candidats au découplage)
f_consume_any_stray_microsoft_rparen()in_cli_property_or_event_definition()in_static_cli_property_or_event_definition()
1. Le côté pragmatique : une récursion mutuelle inhérente au langage
Lors de l’analyse du C++ moderne, les définitions de classes et l’analyse des déclarateurs sont intrinsèquement liées. Les requêtes Code Quest font remonter les fonctions centrales qui justifient la dépendance :
A. declarator.c → class_decl.c
abstract_class_diagnostic(...) : appelée depuis declarator.c lorsqu’une déclaration de fonction ou de variable tente d’instancier ou de retourner une classe abstraite. L’analyseur de déclarateurs doit interroger les métadonnées de disposition des classes depuis class_decl.c pour évaluer les fonctions virtuelles pures et émettre des diagnostics.
B. class_decl.c → declarator.c
declarator(...) & scan_lambda_declarator(...) : appelées depuis class_decl.c chaque fois qu’une définition de classe rencontre des déclarateurs de fonctions membres, des pointeurs vers membres ou des lambdas imbriquées.
delayed_scan_of_exception_spec(...) : les spécifications d’exceptions des fonctions membres ne peuvent être traitées qu’après l’analyse des contextes de classes concernés.
Tenter d’éliminer ces appels centraux par une sur-abstraction introduirait des couches d’encapsulation artificielles et des pénalités de performance à l’exécution. Dans l’ingénierie des compilateurs, cette récursion mutuelle est une conception pragmatique, guidée par le domaine.
2. Les opportunités de remaniement : le couplage utilitaire accidentel
Cependant, Code Quest isole aussi des fonctions du cycle qui ont peu à voir avec la grammaire d’analyse C++ centrale et représentent un couplage architectural accidentel :
En observant les 6 méthodes retournées dans class_decl.c :
- Assistants de dialecte/parseur :
f_consume_any_stray_microsoft_rparen() - Assistants CLI/extensions :
in_cli_property_or_event_definition()etin_static_cli_property_or_event_definition()
Pourquoi elles créent des cycles inutiles
f_consume_any_stray_microsoft_rparen() est un utilitaire de récupération du parseur pour gérer les particularités syntaxiques spécifiques à MSVC. Le placer dans class_decl.c oblige declarator.c à dépendre de toute l’unité de déclaration de classes… juste pour consommer un jeton !
Comment éliminer le cycle accidentel
- Extraire les utilitaires : déplacer les routines de récupération de dialecte (
f_consume_...) et les vérifications d’état CLI vers un module utilitaire dédié (par ex.src/parser_utils.cousrc/lex_helpers.c) rompt le lien artificiel. - Résultat : l’empreinte de couplage des 6 méthodes se réduit, éliminant le cycle accidentel tout en préservant les boucles de dispatch de la grammaire C++, nécessaires et performantes.
Point clé pour les architectes
Les avertissements des outils d’analyse statique ne doivent jamais être traités avec une règle générale du type « corriger tous les cycles ».
Grâce à des outils comme CppDepend et Code Quest, les équipes peuvent distinguer les cycles essentiels au domaine (comme declarator() ↔ abstract_class_diagnostic()), qui ont leur place dans un moteur d’analyse C++, des utilitaires placés par accident (comme les assistants de récupération de jetons MSVC), qui peuvent être proprement extraits par remaniement.
Parce que le C++ autorise les définitions de classes en ligne, les fonctions membres, les classes imbriquées et les types de retour suffixés, l’analyse des définitions de classes et celle des déclarateurs sont fondamentalement mutuellement récursives.
Pourquoi la mécanique des en-têtes C aggrave les cycles
Parce qu’EDG est conçu en C procédural, il repose sur des définitions d’en-têtes partagées (class_decl.h, declarator.h, decls.h).
- Types C partagés : les structures de données comme
a_type_ptr,a_decl_ptreta_declaratordoivent être visibles dans les deux unités de traduction. - Inclusions croisées & déclarations anticipées : là où un C++ propre pourrait utiliser des interfaces ou des motifs Pimpl pour découpler les en-têtes, les bases de code C reposent sur des en-têtes qui s’incluent mutuellement et sur des pointeurs de structs bruts déclarés en avant. Dans la DSM, cela se traduit par des grappes rouges denses entre
class_decl.c,declarator.cetdecls.c.
Point clé pour l’analyse de la matrice
Dans les architectures logicielles classiques, un carré rouge dans une DSM signifie « découpez cela avec une abstraction d’interface ».
Dans un front-end de compilateur industriel, une grappe rouge dans la matrice entre class_decl, declarator, decls et expr reflète simplement le fait que les règles de grammaire C++ ne peuvent pas être proprement organisées en couches formant un graphe acyclique dirigé (DAG) pur. Les cycles ne sont pas une dérive architecturale accidentelle — ils reflètent la récursion mutuelle intégrée dans la spécification du langage C++ elle-même.
Le tableau de bord qualité : décrypter plus de 23 000 problèmes mineurs
En examinant le tableau de bord qualité global de CppDepend pour EDG, un paradoxe statistique frappant apparaît :
Le tableau de bord qualité d’EDG en un coup d’œil
| Métrique / Catégorie | Valeur / Détails |
|---|---|
| Lignes de code totales (LOC) | 372 391 |
| Problèmes totaux | 23 985 |
| Problèmes de faible sévérité | 22 620 (~94 % du total) |
| Problèmes de haute sévérité | 1 344 |
| Règles critiques enfreintes | 0 |
| Statut des quality gates | 5/5 RÉUSSIS |
À première vue, voir 23 985 problèmes au total peut sembler alarmant. Pourtant, l’examen de la répartition révèle pourquoi EDG obtient une note globale B et réussit les 5 quality gates sans déclencher un seul statut d’échec ou d’avertissement.
1. Répartition par sévérité : bruit vs risque critique
Le tableau de bord classe les problèmes en niveaux de sévérité distincts :
- Problèmes critiques : 0 (aucune fuite mémoire grave, comportement indéfini ou défaut destructeur pour le système).
- Problèmes de haute sévérité : 1 344 (principalement concentrés dans les fonctions à forte complexité cyclomatique comme
process_expr_work). - Problèmes de sévérité moyenne : 21
- Problèmes de faible sévérité : 22 620 (~94 % de tous les problèmes signalés)
L’immense majorité du nombre total de problèmes provient de directives de style de code de faible sévérité, comme les vérifications d’analyse statique MISRA C/C++.
2. L’effet MISRA C : les règles de formatage évoluent avec les LOC
Parce qu’EDG est écrit en C procédural, les règles d’analyse statique comme MISRA C imposent des directives syntaxiques strictes sur l’ensemble des 372 391 lignes de code. Un exemple classique :
« Une construction if(condition) doit être suivie d’une instruction composée (accolades {}). »
En C procédural classique, les instructions if sur une seule ligne sans accolades explicites sont courantes :
// Déclenche une violation de style MISRA C de faible sévérité à chaque occurrence
if (expr == NULL) return;
// Forme conforme à MISRA C
if (expr == NULL) {
return;
}
Lorsqu’une base de code de plus de 370 000 LOC omet les accolades optionnelles ou utilise des motifs de style macro dans des milliers de cas de switch, CppDepend signale chaque occurrence comme un problème.
Si ces règles génèrent des dizaines de milliers d’avertissements individuels, leur impact en dette technique par occurrence est minimal, c’est pourquoi Code Implementation affiche 33 280 problèmes tandis que Code Safety en affiche 0.
3. Une densité de commentaires élevée reflète une documentation solide
Une autre métrique remarquable du tableau de bord est le taux de commentaires :
- Pourcentage de commentaires : 48,44 %
- Lignes de commentaires : 349 808
Près de 50 % de toute la base de code EDG est constituée de commentaires. Pour chaque ligne de code C procédural exécutable, il y a presque une ligne complète de documentation expliquant les subtilités de conformité au standard C++, les options de compilation et les cas limites des dialectes.
Cette densité de commentaires exceptionnelle explique pourquoi, malgré la complexité structurelle des dispatchers de parcours d’AST, la base de code reste hautement maintenable pour son domaine.
Point clé
Le tableau de bord qualité démontre pourquoi les nombres bruts de problèmes peuvent être trompeurs sans classification par sévérité.
Les 22 620 problèmes de faible sévérité d’EDG sont des violations cosmétiques de style et de formatage (comme la conformité des accolades MISRA). Parce que le moteur maintient zéro violation de règle critique et une densité de commentaires de 48 %, il satisfait les quality gates de plus haut niveau de CppDepend tout en assurant une analyse C++ de qualité industrielle.
Conclusion : équilibrer métriques statiques et architecture du domaine
Analyser une base de code mature de qualité industrielle comme le front-end C/C++ EDG avec CppDepend offre une leçon précieuse en architecture logicielle : les métriques d’analyse statique sont des indicateurs de diagnostic, pas des dogmes absolus.
Les leçons de l’analyse d’EDG
| Point clé | Enseignement architectural |
|---|---|
| 1. Le contexte avant le dogme des métriques | Une fonction de 2 900 lignes avec une CC de 1 229 n’est pas toujours une dette technique — dans un interpréteur d’AST écrit en C, elle évite l’allocation de la pile d’appels et le surcoût des registres. |
| 2. Distinguer les types de cycles | La récursion mutuelle dans l’analyse grammaticale (declarator ↔ class_decl) est pragmatique, tandis que les assistants de jetons MSVC égarés sont une dette technique traitable. |
| 3. Santé macro vs micro | Malgré une complexité locale élevée des fonctions, les contours verts et une densité de commentaires de 48 % prouvent une ingénierie disciplinée au niveau système. |
Enseignements clés pour l’ingénierie
- Une complexité élevée peut être une conception intentionnelle : des fonctions comme
process_expr_workatteignent des scores de complexité cyclomatique astronomiques (1 229) parce qu’elles centralisent l’évaluation des expressions de l’AST C++ dans des boucles de dispatch procédurales C à haut débit. Découper chaque branche en fonctions séparées augmenterait le passage de paramètres, le surcoût des cadres de pile et les défauts de cache, sans réduire la complexité inhérente de la spécification du langage C++. - Tous les cycles de dépendances ne se valent pas : grâce à la visualisation DSM et aux requêtes Code Quest, nous avons vu que les cycles de dépendances au niveau fichier ont souvent une double nature :
- Cycles guidés par le domaine : la récursion mutuelle entre
class_decl.cetdeclarator.creflète directement les règles récursives de la grammaire C++ (les définitions de classes contiennent des déclarateurs ; les déclarateurs requièrent le contexte des classes). - Cycles accidentels : placer des assistants d’analyse de jetons (comme
f_consume_any_stray_microsoft_rparen()) dans des modules de classes centraux crée un couplage inter-fichiers inutile qui peut être proprement remanié.
- Cycles guidés par le domaine : la récursion mutuelle entre
- La classification par sévérité évite la panique des métriques : le tableau de bord qualité d’EDG enregistre près de 24 000 problèmes au total. Pourtant, avec 0 règle critique enfreinte, 5/5 quality gates réussis et ~94 % d’avertissements de faible sévérité issus de vérifications de style strictes (comme la conformité des accolades MISRA C), le projet obtient une solide note B. Combinée à une densité de commentaires de 48,44 %, la base de code démontre une hygiène de maintenance exceptionnelle malgré son échelle structurelle brute.
Le mot de la fin pour les architectes logiciels
Les outils d’analyse statique comme CppDepend sont inestimables pour exposer les cartes de chaleur structurelles, les frontières architecturales et le couplage caché. Cependant, l’objectif de l’analyse statique dans les domaines d’ingénierie complexes n’est pas de forcer toutes les métriques au vert à tout prix.
En combinant la visualisation automatisée avec une compréhension profonde des contraintes du domaine — qu’il s’agisse de construire un front-end de compilateur, un moteur de jeu ou un OS temps réel — les architectes peuvent distinguer la vraie dette technique qui s’aggrave des décisions de conception pragmatiques et performantes.
