Code smells :
ce que le code trahit.
Code smells:
what the code betrays.
Un code smell — une odeur de code — est un indice visible en surface qui suggère un problème plus profond de conception. Ce n'est pas un bug : le programme fonctionne. C'est un signal que le prochain changement va coûter plus cher qu'il ne devrait.
Définition et origine
Le terme vient de Kent Beck et a été popularisé par Martin Fowler dans Refactoring. L'analogie est volontaire : une odeur ne prouve pas qu'il y a un problème, mais elle justifie d'aller regarder.
La nuance est importante et souvent perdue. Une odeur est une heuristique, pas une règle. Une méthode de cent lignes est suspecte, pas condamnée. Un outil d'analyse statique qui refuse une fusion parce qu'une méthode dépasse trente lignes transforme une heuristique en dogme, et vous obtiendrez du code découpé artificiellement pour satisfaire le linter — plus difficile à lire que l'original.
Le catalogue, par famille
Les gonflements
- Méthode longue. Une fonction qui fait trop de choses. Le signe fiable n'est pas le nombre de lignes mais le nombre de niveaux d'abstraction mélangés : si une fonction ouvre une connexion SQL et calcule une TPS, elle vit à deux étages en même temps.
- Classe divine (god class). Une classe qui sait tout et fait tout. Elle devient un point de conflit permanent : toute l'équipe la modifie, chaque fusion se bat avec les autres.
- Liste de paramètres trop longue. Au-delà de trois ou quatre, les appels deviennent illisibles et les erreurs d'ordre passent la compilation.
- Obsession des primitifs. Un code d'entreprise représenté par une chaîne de caractères, une devise par un nombre à virgule flottante. Les règles associées se dispersent partout au lieu de vivre dans un type.
Les duplications
- Code dupliqué. L'odeur la plus universelle. Le vrai coût n'est pas l'espace occupé : c'est qu'une correction sur trois copies en laisse une derrière, et cette copie oubliée finira en incident.
- Classes alternatives aux interfaces différentes. Deux classes font la même chose avec des noms de méthodes différents. Personne ne sait laquelle utiliser, donc les deux vivent.
Les couplages
- Envie de fonctionnalité (feature envy). Une méthode qui utilise surtout les données d'une autre classe. Le comportement est du mauvais côté de la frontière.
- Chaîne de messages.
commande.client().adresse().ville().code(): chaque maillon est une dépendance sur une structure interne. Un seul changement en casse toute la chaîne. - Intimité inappropriée. Deux classes qui fouillent mutuellement dans leurs détails privés. Elles ne peuvent plus évoluer séparément.
- Modification divergente. Un même fichier change pour trois raisons sans lien. Il porte trois responsabilités qu'il faudrait séparer.
- Chirurgie au fusil de chasse. L'inverse : un petit changement métier oblige à toucher onze fichiers. Un concept est éparpillé au lieu d'être localisé.
Les jetables
- Code mort. Jamais appelé, jamais supprimé « au cas où ». Il est dans votre système de version : effacez-le.
- Généralité spéculative. Des abstractions bâties pour un besoin futur qui n'est jamais venu. La couche d'indirection reste, le besoin n'existe pas.
- Commentaires excessifs. Un commentaire qui explique comment le code fonctionne est souvent un déodorant sur du code obscur. Les bons commentaires expliquent pourquoi — un choix, une contrainte, un compromis. Ceux-là, gardez-les.
L'odeur la plus chère, et la moins citée
La logique métier dispersée. Le calcul d'une même remise existe dans le contrôleur web, dans le traitement par lots nocturne et dans l'export comptable — avec trois arrondis légèrement différents. Rien ne plante. Les chiffres ne concordent simplement pas, et on met six mois à comprendre pourquoi.
Aucun outil d'analyse statique ne la détecte. Elle se trouve en écoutant les gens du métier dire « oui mais l'écran affiche autre chose que le rapport ».
Quand ignorer une odeur
Toujours la même question : est-ce que ce code va changer ? Une odeur dans un module stable depuis quatre ans, que personne n'ouvre, ne coûte rien. La même odeur dans le code touché à chaque sprint coûte à chaque sprint.
Priorisez donc par fréquence de modification, pas par gravité théorique. L'historique de votre dépôt vous donne cette information gratuitement : les fichiers les plus modifiés, croisés avec les fichiers les plus complexes, forment votre vraie liste de travail.
Les outils, et leurs limites
Les analyseurs statiques — SonarQube, ESLint, RuboCop, PMD et les autres — détectent bien les odeurs mécaniques : duplication, complexité cyclomatique, longueur, code mort. Ils sont utiles et devraient tourner en intégration continue.
Ils ne détectent pas les odeurs de conception : mauvaise frontière de module, abstraction qui ment sur ce qu'elle fait, concept métier absent du code. Ces trois-là coûtent bien plus cher que tout ce qu'un linter signale, et se trouvent uniquement en refactorant et en relisant du code à plusieurs.
Conseil de terrain sur un code existant : n'activez pas cinq cents règles d'un coup. Vous obtiendrez douze mille avertissements que personne ne lira, et l'équipe apprendra à ignorer l'outil. Bloquez uniquement sur le code neuf, laissez l'ancien s'améliorer au passage.
Questions fréquentes
Qu'est-ce qu'un code smell ?
C'est un indice visible dans le code qui suggère un problème de conception plus profond. Ce n'est pas un bug : le programme fonctionne. C'est un signal que le prochain changement coûtera plus cher que nécessaire.
Quelle est la différence entre un code smell et un bug ?
Un bug produit un comportement incorrect maintenant. Un code smell ne produit rien d'incorrect : il rend le code plus difficile et plus risqué à modifier, ce qui finit par produire des bugs, mais plus tard.
Quels sont les code smells les plus fréquents ?
Le code dupliqué, les méthodes trop longues, les classes qui font tout, les listes de paramètres interminables, les chaînes de messages, et la logique métier dupliquée à plusieurs endroits avec de légères divergences.
Faut-il corriger tous les code smells ?
Non. Une odeur dans du code que personne ne modifie ne coûte rien. Priorisez selon la fréquence de modification : croisez les fichiers les plus souvent touchés avec les plus complexes, et vous obtenez la vraie liste de travail.
À lire ensuite
- Refactoring : changer sans casser
- Dette technique : la mesurer et la rembourser
- Definition of done et qualité
- Développement logiciel : le cycle complet
Parlons de votre projet
On reprend régulièrement des bases de code que d'autres ont laissées derrière eux. Si la vôtre vous ralentit, on peut la regarder et vous dire honnêtement ce qu'elle vaut.
A code smell is a surface-level hint that suggests a deeper design problem. It is not a bug — the program works. It is a signal that the next change will cost more than it should.
Definition and origin
The term comes from Kent Beck and was popularised by Martin Fowler in Refactoring. The analogy is deliberate: a smell does not prove there is a problem, but it justifies going to look.
That nuance matters and is often lost. A smell is a heuristic, not a rule. A hundred-line method is suspicious, not condemned. A static analyser that blocks a merge because a method exceeds thirty lines turns a heuristic into dogma, and you will get code chopped up artificially to satisfy the linter — harder to read than the original.
The catalogue, by family
Bloaters
- Long method. A function doing too much. The reliable sign is not line count but the number of abstraction levels mixed: a function that opens a SQL connection and computes a sales tax lives on two floors at once.
- God class. A class that knows everything and does everything. It becomes a permanent conflict point: the whole team edits it, and every merge fights the others.
- Long parameter list. Past three or four, calls become unreadable and ordering mistakes compile fine.
- Primitive obsession. A company code represented as a string, a currency as a float. The associated rules scatter everywhere instead of living in a type.
Duplication
- Duplicated code. The most universal smell. The real cost is not space: it is that a fix applied to three copies leaves one behind, and the forgotten copy becomes an incident.
- Alternative classes with different interfaces. Two classes doing the same thing with different method names. Nobody knows which to use, so both survive.
Couplers
- Feature envy. A method that mostly uses another class's data. The behaviour sits on the wrong side of the boundary.
- Message chains.
order.customer().address().city().code(): every link is a dependency on internal structure. One change breaks the whole chain. - Inappropriate intimacy. Two classes digging into each other's private details. They can no longer evolve separately.
- Divergent change. One file changes for three unrelated reasons. It carries three responsibilities that should be separated.
- Shotgun surgery. The inverse: a small business change forces edits to eleven files. A concept is scattered instead of localised.
Dispensables
- Dead code. Never called, never deleted "just in case". It is in version control: delete it.
- Speculative generality. Abstractions built for a future need that never arrived. The indirection layer stays; the need does not exist.
- Excessive comments. A comment explaining how code works is often deodorant on obscure code. Good comments explain why — a choice, a constraint, a trade-off. Keep those.
The most expensive smell, and the least cited
Scattered business logic. The same discount calculation exists in the web controller, in the nightly batch job and in the accounting export — with three subtly different roundings. Nothing crashes. The numbers simply do not reconcile, and it takes six months to work out why.
No static analyser detects it. You find it by listening to business people say "the screen shows something different from the report".
When to ignore a smell
Always the same question: is this code going to change? A smell in a module stable for four years, that nobody opens, costs nothing. The same smell in code touched every sprint costs every sprint.
So prioritise by change frequency, not theoretical severity. Your repository history gives you that for free: the most-changed files, crossed with the most complex ones, form your real worklist.
Tools, and their limits
Static analysers — SonarQube, ESLint, RuboCop, PMD and others — are good at mechanical smells: duplication, cyclomatic complexity, length, dead code. They are useful and should run in continuous integration.
They do not detect design smells: a bad module boundary, an abstraction that lies about what it does, a business concept missing from the code. Those three cost far more than anything a linter flags, and are found only by refactoring and reading code together.
Field advice on an existing codebase: do not switch on five hundred rules at once. You will get twelve thousand warnings nobody reads, and the team will learn to ignore the tool. Block only on new code, and let the old improve as you pass through.
Frequently asked questions
What is a code smell?
A visible hint in the code suggesting a deeper design problem. It is not a bug — the program works. It is a signal that the next change will cost more than it needs to.
What is the difference between a code smell and a bug?
A bug produces incorrect behaviour now. A code smell produces nothing incorrect: it makes the code harder and riskier to change, which eventually produces bugs, but later.
What are the most common code smells?
Duplicated code, overly long methods, classes that do everything, endless parameter lists, message chains, and business logic duplicated in several places with slight divergences.
Should every code smell be fixed?
No. A smell in code nobody edits costs nothing. Prioritise by change frequency: cross the most frequently touched files with the most complex ones and you get the real worklist.
Read next
- Refactoring: change without breaking
- Technical debt: measure it, pay it down
- Definition of done and quality
- Software development: the full lifecycle
Let's talk about your project
We regularly take over codebases other people left behind. If yours is slowing you down, we can look at it and tell you honestly what it is worth.