Les anomalies de code ne sont pas des bogues. Un programme bogué plante, renvoie des résultats erronés ou échoue à un test. Un programme présentant des anomalies de code peut fonctionner parfaitement pendant des années, et pourtant chaque modification coûte plus cher que nécessaire, chaque nouvelle fonctionnalité comporte des risques imprévus et chaque tentative de refactorisation révèle des dépendances insoupçonnées. Les anomalies de code sont les caractéristiques structurelles du code qui prédisent des problèmes futurs : elles ne provoquent pas de défaillance immédiate, mais elles rendent chaque modification ultérieure plus difficile, plus lente et plus risquée qu'elle ne devrait l'être.
Le terme a été popularisé par Martin Fowler et Kent Beck dans l'ouvrage de Fowler. Refactoring : Améliorer la conception du code existant (1999), qui a répertorié 22 anomalies de code et les a associées à une technique de refactoring correspondante. Ce catalogue demeure la référence incontournable, et les anomalies identifiées par Fowler – Méthodes longues, Classes omniprésentes, Code dupliqué, Envie de fonctionnalités, Changements divergents, Refactorisation à la volée, etc. – sont aujourd'hui présentes dans les règles SonarQube, les outils d'analyse statique et les listes de contrôle de revue de code utilisés dans l'ensemble du secteur.
Nettoyer les odeurs de code
SMART TS XL aide à les cartographier et à les corriger dans des systèmes complexes.
En savoir plusQu'est-ce qu'une odeur codée ?
Une « mauvaise odeur de code » est une caractéristique superficielle du code source qui suggère un problème structurel ou de conception plus profond. Le code compile, réussit les tests et produit un résultat correct, mais un élément de sa structure le rend plus difficile à lire, à étendre ou à modifier en toute sécurité qu'il ne devrait l'être. Définition de Fowler : « une indication superficielle qui correspond généralement à un problème plus profond dans le système ».
Les anomalies de conception ne constituent pas des violations au même titre qu'une erreur de syntaxe ou une assertion échouée. Ce sont des indicateurs, des schémas que les développeurs expérimentés reconnaissent comme des signaux d'alarme, même en l'absence de défaillance immédiate. Le danger réside dans leur cumul : une seule méthode longue dans un code de 10 000 lignes représente un inconvénient mineur. En revanche, des centaines de méthodes longues, une logique dupliquée répartie sur des dizaines de modules et des classes centrales au cœur du graphe de dépendances constituent un système devenu extrêmement difficile à modifier en toute sécurité.
Mauvaises pratiques de codage vs. Bugs vs. Dette technique
Ces trois concepts sont liés mais distincts, et les confondre conduit à une mauvaise hiérarchisation :
| Concept | Définition | Échec immédiat ? | Comment trouver |
|---|---|---|---|
| Punaise | Code produisant un comportement incorrect | Oui, les tests échouent, les utilisateurs signalent des erreurs | Tests, surveillance, journaux d'erreurs |
| Odeur de code | Modèle structurel permettant de prédire les problèmes futurs | Non, le code s'exécute correctement. | Revue de code, analyse statique |
| Dette technique | Le coût cumulé des raccourcis et des mauvaises décisions du passé | Non, mais les composés s'accumulent avec le temps. | Métriques, analyse de la complexité, estimations de l'effort de refactorisation |
Les anomalies de conception sont le mécanisme par lequel la dette technique s'accumule. Chaque méthode longue ajoutée au code source représente une unité de dette technique ; son coût est le temps supplémentaire que chaque développeur passera à la comprendre, et chaque modification ultérieure à éviter les effets secondaires liés à sa taille.
Qu'est-ce qu'une « odeur de code » dans SonarQube ?
SonarQube classe les problèmes de code en trois catégories : les bogues (erreurs avérées), les vulnérabilités (problèmes de sécurité) et… le code sent (Problèmes de maintenabilité). Les indicateurs de mauvaise qualité de code de SonarQube correspondent directement au catalogue de Fowler et incluent des règles concernant les méthodes longues (au-delà de seuils de lignes configurables), les blocs dupliqués, un nombre excessif de paramètres, des scores de complexité cognitive élevés, l'absence de gestion des erreurs et les violations de couplage architectural. Les règles d'indicateurs de mauvaise qualité de code de SonarQube constituent l'application automatisée la plus répandue de la taxonomie originale de Fowler.
Le code des odeurs de Martin Fowler : la taxonomie classique
Les 22 anomalies de code identifiées par Fowler, classées par catégorie, demeurent la référence. Tous les principaux outils d'analyse statique s'appuient sur cette taxonomie pour définir leurs règles.
| Catégories | Les odeurs de code |
|---|---|
| ballonnements, un code qui a atteint une taille ingérable | Méthode longue, classe volumineuse, obsession pour les primitives, longue liste de paramètres, amas de données |
| Abus de l'orientation objet, mauvaise utilisation des principes de la programmation orientée objet | Instructions switch, champ temporaire, legs refusé, classes alternatives avec interfaces différentes |
| Les freins au changement, rendre le changement difficile | Changement divergent, chirurgie à l'aveugle, hiérarchies d'hérédité parallèles |
| consommables, code inutile | Commentaires (excessifs), code dupliqué, classe paresseuse, classe de données, code mort, généralité spéculative |
| Coupleurs, couplage excessif | Jalousie des fonctionnalités, intimité inappropriée, chaînes de messages, intermédiaire |
Comprendre à quelle catégorie appartient une odeur permet de prioriser les mesures correctives : les odeurs gênantes et les facteurs empêchant le changement sont directement corrélés à des coûts de refactorisation élevés ; les odeurs gênantes sont directement corrélées à une fragilité architecturale ; les odeurs superflues sont les plus sûres à éliminer.
Les codes les plus souvent suspects : guide rapide
| Code Odeur | À quoi il ressemble | Risque primaire |
|---|---|---|
| Code dupliqué | La même logique apparaît à plusieurs endroits. | Les correctifs de bogues doivent être appliqués partout ; les copies divergent avec le temps |
| Méthode longue | Méthodes comportant plus de 20 à 30 lignes et de multiples responsabilités | Charge cognitive élevée ; difficile de tester les comportements isolés |
| Classe divine / Grande classe | Une seule classe qui fait tout | Chaque modification de fonctionnalité affecte la même classe ; conflits de fusion, fragilité |
| Liste de paramètres longue | Méthodes nécessitant 4 paramètres ou plus | Il est facile de transmettre des valeurs erronées ; les sites d'appel sont difficiles à lire |
| Envie de fonctionnalité | Une méthode qui utilise davantage les données d'une autre classe que les siennes propres. | Couplage étroit ; toute modification dans une classe entraîne la rupture de l’autre. |
| Changement divergent | Une classe modifiée pour de nombreuses raisons différentes | Violation du principe de responsabilité unique ; effets secondaires imprévisibles |
| Chirurgie du fusil de chasse | Une seule modification nécessite des modifications dans de nombreuses classes. | Coût de modification élevé ; il est facile de manquer une instance |
| Code mort | Code qui n'est jamais appelé ni atteint | Cela perturbe les développeurs ; s’accumule au fil des ans ; complique la migration |
| Obsession primitive | Utiliser des types de base (chaînes de caractères, entiers) au lieu d'objets de domaine | Validation dispersée ; faible expressivité |
| Blocs de données | Le même groupe de champs est transmis ensemble de manière répétée | Devrait être un objet de domaine ; signale un manque d'abstraction |
| Généralité spéculative | Code écrit pour des besoins futurs imaginés | Complexité inutile ; personne ne comprend pourquoi elle est là. |
| Gestion des erreurs incohérente | Interceptions silencieuses, stratégies d'exception variables | Les pannes passent inaperçues ; le débogage prend beaucoup plus de temps. |
Définitions et exemples de codes d'odeur
Code dupliqué
Le problème de conception le plus fréquent et le plus coûteux dans les grands systèmes. La duplication résulte du développement par copier-coller, des contraintes de temps et du travail en silos d'équipes qui résolvent indépendamment le même problème. La conséquence immédiate est un coût de maintenance important : toute modification de la logique partagée doit être appliquée à chaque copie.
Java
// ServiceA -- discount calculation
double calculateDiscount(double amount) {
if (amount > 1000) return amount * 0.1;
return 0;
}
// ServiceB -- same logic, copied and forgotten
double computeDiscount(double value) {
if (value > 1000) return value * 0.1;
return 0;
}
Lorsqu'une règle métier change (le seuil passe à 1500, le taux à 12 %), une copie est mise à jour et l'autre non. Deux modules se retrouvent alors en désaccord sur une logique métier fondamentale, et cette divergence apparaît en production lors d'un audit plutôt qu'en phase de test.
FixerExtraire la logique partagée dans une seule fonction, classe utilitaire ou bibliothèque partagée référencée par les deux appelants.
Méthode longue
Une méthode qui a dépassé son objectif initial en intégrant progressivement de nouvelles responsabilités. La charge cognitive liée à la lecture d'une méthode de 200 lignes est qualitativement différente de celle de la lecture de vingt méthodes de 10 lignes, et pas seulement quantitativement. Les méthodes longues sont difficiles à tester car elles effectuent trop d'opérations pour être testées isolément, et difficiles à comprendre car le lecteur doit maintenir en mémoire de travail l'ensemble du contexte d'exécution.
Seuil de détectionLes méthodes de plus de 20 à 30 lignes nécessitent une révision ; au-delà de 50 lignes, une refactorisation est presque toujours justifiée. En COBOL, un paragraphe de plus de 100 instructions est équivalent.
python
class OrderProcessor:
def process_order(self, order):
# Validate order -- 40 lines
# Calculate discounts -- 30 lines
# Update inventory -- 25 lines
# Send notification emails -- 20 lines
# Generate invoice -- 35 lines
# 150+ lines total
pass
Dans cette méthode, chaque responsabilité doit être implémentée dans une classe ou une fonction distincte. Les regrouper risque de déstabiliser l'ensemble du processus de traitement des commandes à chaque mise à jour ultérieure de la facturation, des stocks ou des notifications.
Classe divine
Une classe qui a accumulé des responsabilités dans de multiples domaines, violant si gravement le principe de responsabilité unique qu'elle devient le centre de gravité d'une base de code : tout en dépend, et modifier quoi que ce soit nécessite de tout comprendre à son sujet.
Signal de détection: Une classe comportant plus de 20 à 30 méthodes publiques, ou une classe dont le nom contient « Manager », « Processor », « Handler », « Utils » ou « Helper » appliqués à plusieurs domaines non liés.
Changement divergent
Cette classe est modifiée pour de nombreuses raisons différentes et sans lien entre elles. À chaque changement de schéma de base de données, à chaque modification des règles de tarification, à chaque changement de format de notification, vous devez la modifier. Cette classe supporte trop de responsabilités et devrait être scindée.
DéfinitionUne classe qui évolue constamment pour diverses raisons. L'inverse de la chirurgie à l'arme blanche.
Chirurgie du fusil de chasse
Un simple changement conceptuel nécessite des modifications dans de nombreuses classes différentes. Modifier un taux d'imposition implique de modifier un calcul côté serveur, une validation côté client, un déclencheur de base de données, un traitement par lots et une requête de reporting, à cinq endroits différents. Omettre une seule modification entraîne un comportement incohérent.
sql
-- Tax logic duplicated across queries
SELECT amount * 0.05 FROM invoices;
SELECT amount * 0.05 FROM payments;
SELECT amount * 0.05 FROM reports;
Le passage de 0.05 à 0.07 nécessite désormais de rechercher chaque occurrence dans les fichiers SQL, les procédures stockées et le code de l'application.
Envie de fonctionnalité
Une méthode qui utilise davantage les données et les méthodes d'une autre classe que les siennes propres indique que ce comportement relève probablement de l'autre classe.
Java
// In ReportGenerator -- envious of Customer's data
double calculateCustomerRating(Customer customer) {
return customer.getOrderCount() * customer.getAverageOrderValue()
/ customer.getDaysSinceRegistration();
}
// This logic belongs in Customer, not ReportGenerator
Code mort
Le code mort, présent dans le dépôt mais jamais exécuté en production, s'accumule au fil des ans : les fonctionnalités sont supprimées, remplacées ou restructurées sans que l'ancien code ne soit effacé. Il alourdit les revues de code, perturbe l'intégration des développeurs, complique l'analyse des migrations et peut parfois être réactivé accidentellement.
Détection: Outils d'analyse statique, notamment SonarQube, Knip (pour TypeScript/JavaScript), et SMART TS XL Identifier les fonctions inaccessibles, les méthodes non appelées et les variables inutilisées dans l'ensemble du code source.
Violations du principe DRY
Le principe DRY (Don't Repeat Yourself) stipule que chaque information doit avoir une représentation unique et non ambiguë au sein d'un système. Les violations du principe DRY sont à l'origine du code dupliqué, des amas de données et de nombreuses situations de solutions improvisées. Lorsque la logique métier est représentée à plusieurs endroits, ces représentations divergent inévitablement. DRY est le principe ; le code dupliqué est le signe révélateur de sa violation.
python
# DRY violation: same validation logic in three places
def validate_email_in_registration(email):
return "@" in email and "." in email
def validate_email_in_profile_update(email):
return "@" in email and "." in email
def validate_email_in_checkout(email):
return "@" in email and "." in email
# DRY-compliant: one function, three callers
def is_valid_email(email):
return "@" in email and "." in email
Seuil de détection : à quel moment un code devient-il une odeur suspecte ?
La détection des anomalies de code nécessite des seuils mesurables. Vous trouverez ci-dessous les indicateurs couramment utilisés et les valeurs qui signalent une anomalie nécessitant une attention particulière :
| Métrique | Ce qu'il mesure | Seuil d'avertissement | Seuil critique |
|---|---|---|---|
| Complexité cyclomatique | Nombre de branches de décision dans une méthode | Ci-dessus 10 | Ci-dessus 20 |
| Longueur de la méthode (lignes) | Nombre de lignes dans une méthode/fonction | Ci-dessus 20 | Ci-dessus 50 |
| Nombre de paramètres | Nombre de paramètres acceptés par une méthode | Ci-dessus 4 | Ci-dessus 7 |
| Durée du cours | Nombre de lignes dans une classe | Ci-dessus 200 | Ci-dessus 500 |
| Taux de duplication | Pourcentage de code dupliqué | Au-dessus de 3% | Au-dessus de 10% |
| Complexité cognitive | La difficulté à comprendre ce code | Ci-dessus 15 | Ci-dessus 25 |
| Couplage afférent (Ca) | Nombre de classes qui dépendent de cette classe | Ci-dessus 15 | Ci-dessus 30 |
| Couplage efférent (Ce) | Nombre de classes dont dépend cette classe | Ci-dessus 15 | Ci-dessus 30 |
Ces seuils sont configurables dans SonarQube, et la plupart des plateformes d'analyse statique permettent de définir des règles personnalisées basées sur ces métriques. Les classes et méthodes présentant des seuils critiques constituent les cibles de refactorisation prioritaires : elles représentent les sources les plus probables de défauts futurs et les composants les plus coûteux à maintenir.
Outils de détection des anomalies de code
La détection automatisée est la seule approche évolutive pour identifier les anomalies de code dans les vastes bases de code. L'analyse manuelle ne détecte qu'une fraction des anomalies repérées par les outils automatisés et n'est pas applicable aux systèmes anciens comportant des millions de lignes de code.
| Outil | Langue principale | Ce qu'il détecte |
|---|---|---|
| SonarQube / SonarCloud | Java, Python, JS/TS, C# et bien plus encore | Taxonomie olfactive de Fowler complète, points chauds de sécurité, duplications |
| Checkstyle + PMD | Java | Violations de style, duplications, métriques de complexité |
| ESLint + typescript-eslint | JavaScript, TypeScript | Fonctions longues, complexité, code inutilisé |
| Pylint + Radon | Python | Indice de complexité, de style et de maintenabilité |
| ReSharper / Rider | C# | Code redondant, méthodes longues, problèmes de couplage |
| Clippy | Se reposer | Violations idiomatiques, schémas courants considérés comme des anomalies de codage en Rust |
| CodeClimat | Multi-langue | Score de complexité, de duplication et de maintenabilité |
| SMART TS XL | COBOL, JCL, Java, Python, RPG, SQL, .NET | Duplication interlangages, code mort, couplage, dérive des dépendances |
Mauvaises odeurs de code dans Rust sont principalement détectées par Clippy, qui applique les conventions de programmation Rust. Les anomalies les plus courantes spécifiques à Rust incluent le clonage inutile et la mauvaise utilisation de unwrap() Dans les chemins de production, les expressions de correspondance trop imbriquées et les fonctions qui devraient renvoyer Result mais utilisez plutôt des paniques.
Code défectueux et dette technique : le lien
La dette technique représente le coût cumulé des décisions passées ayant privilégié la rapidité au détriment de la qualité. Les anomalies de code sont le mécanisme par lequel cette dette se manifeste dans la structure du code. La relation est directe : chaque anomalie non corrigée constitue une unité de dette technique, et son taux d’intérêt correspond au temps supplémentaire que chaque modification future devra consacrer à la contourner.
Comme décrit dans le contexte de analyse d'impact pour la gestion des changements logicielsLes problèmes structurels que les anomalies de code indiquent (couplage excessif, logique dupliquée, accumulation de code mort) augmentent directement la portée de chaque modification car ils rendent plus difficile l'isolement de ce qu'une modification donnée affectera.
Expliquez la dette technique en termes de défauts de code.Si un code source contient 40 % de duplication, chaque correction de bug coûte 1.4 fois plus cher que prévu. Si la classe de traitement principale est une classe omnipotente dont dépend tout le reste, chaque ajout de fonctionnalité nécessite de comprendre et de tester l'intégralité de cette classe. Si la gestion des erreurs est incohérente, chaque incident en production exige davantage de temps d'investigation, car les signaux de défaillance sont peu fiables. La dette technique n'est pas un concept abstrait : elle résulte de l'accumulation de ces inefficacités.
Les recherches du CISQ montrent systématiquement que les développeurs consacrent 30 à 40 % de leur temps à résoudre la dette technique plutôt qu'à développer de nouvelles fonctionnalités. La densité des anomalies de code est la mesure la plus directe de l'ampleur de cette dette.
Comment SMART TS XL Détecte les anomalies de code à l'échelle de l'entreprise
Des outils individuels comme SonarQube et Clippy fonctionnent dans un seul langage. Dans les environnements d'entreprise où des programmes COBOL écrivent dans des ensembles de données lus par des services Java, où des flux de travaux JCL invoquent des programmes dans plusieurs langages et où la même logique métier a été dupliquée indépendamment dans trois systèmes différents écrits à trois décennies d'intervalle, les outils monolangages ne peuvent pas offrir une vision d'ensemble.
SMART TS XL's analyse de code statique Détecte simultanément les anomalies de code dans tous les langages de l'environnement : logique dupliquée entre un copybook COBOL et une classe utilitaire Java, code mort dans les programmes RPG qu'aucune tâche JCL n'invoque, modèles de classe divine dans les programmes COBOL où un seul paragraphe fait le travail de cinquante, et modèles de gestion des erreurs incohérents à la frontière entre les langages.
Le cartographie des dépendances des applications Cette fonctionnalité permet d'identifier les anomalies architecturales que les outils individuels au niveau des fichiers ne peuvent pas voir : quels composants présentent le couplage afférent le plus élevé (les plus dépendants, le risque le plus élevé de dysfonctionnements lors de modifications), où existent des dépendances circulaires entre des modules qui devraient être indépendants, et où une logique métier dupliquée a été maintenue indépendamment dans différents systèmes sans qu'aucune des copies ne soit au courant de l'autre.
Le analyse d’impact Cette approche permet de traiter les anomalies de code : avant de refactoriser un composant fortement couplé, l’analyse d’impact recense tous les composants dépendants qui doivent être testés, validés ou mis à jour. Ainsi, la « paralysie de la refactorisation » que subissent les équipes face à des bases de code volumineuses et problématiques se transforme en un programme de remédiation structuré et circonscrit, où chaque modification a une portée définie plutôt qu’un risque inconnu.
Pour les équipes menant modernisation de l'héritage Dans les programmes, l'analyse des anomalies de code est la base du plan de modernisation : le code mort est éliminé avant le début de la migration (réduisant ainsi la portée), la logique dupliquée est consolidée dans des implémentations canoniques, les composants les plus fortement couplés sont modernisés en dernier (après que tout ce qui en dépend a été traité), et les classes divines sont décomposées avant d'être converties dans un nouveau langage, car convertir une classe divine en Java produit une classe divine en Java.
Remédier aux anomalies de code : un cadre de priorisation
Tous les défauts de conception du code ne justifient pas une refactorisation immédiate. La bonne approche consiste à prioriser en fonction des risques :
Priorité 1, Odeurs présentes dans les composants à taux de changement élevé. Le code qui évolue fréquemment et qui présente une complexité ou un couplage élevés est celui qui génère le plus de défauts. Ces composants sont les plus coûteux à modifier et sont à l'origine du plus grand nombre d'incidents de production. Corrigez-les en priorité.
Priorité 2, Odeurs aux limites architecturales. Les classes omnipotentes et les composants fortement interdépendants sont les plus risqués à modifier, mais aussi les plus coûteux à ignorer. Leur refactorisation exige une analyse d'impact extrêmement rigoureuse.
Priorité 3, Code dupliqué au-delà des limites du système. Lorsque la même logique métier est présente dans plusieurs systèmes, les modifications doivent être coordonnées simultanément sur toutes les copies. La consolidation de ces doublons réduit les coûts de coordination et évite les divergences.
Priorité 4, Suppression du code mort. Le code mort est la catégorie la plus sûre à traiter : le supprimer ne peut pas perturber le fonctionnement, mais seulement révéler des dépendances auparavant masquées. Il convient de le supprimer avant toute migration ou conversion afin d’éviter de gaspiller des efforts en convertissant du code qui ne sera jamais utilisé.
Priorité 5, Odeurs de style et de structure dans les zones à faible risque. Les longues méthodes et listes de paramètres dans un code stable et peu évolutif peuvent être traitées de manière opportuniste ; lorsque le code voisin doit être modifié pour d'autres raisons, il est possible de remanier simultanément les éléments environnants problématiques.
La discipline consistant à détecter, mesurer et traiter les anomalies de code de manière systématique, plutôt que de manière réactive lorsqu'une anomalie a déjà provoqué une défaillance en production, est ce qui distingue les équipes de développement qui maintiennent leur rythme de livraison au fil du temps de celles qui ralentissent progressivement à mesure que leurs systèmes se développent.