Un code smell est un indice de la qualité dé­fi­ciente d’un code. Des code smells se pré­sen­tent quand une im­pres­sion dé­sa­gréable se dégage à l’examen d’un code. Nous exposons les code smells connus et vous ex­pli­quons comment s’en dé­bar­ras­ser.

Qu’est-ce qu’un code smell ?

Un code smell (traduit lit­té­ra­le­ment par « mauvaise odeur ») est un aspect du code qui insuffle aux pro­gram­meurs ex­pé­ri­men­tés l’im­pres­sion immédiate que le code n’est « pas propre ». Avoir un bon « nez » pour détecter les code smells est com­pa­rable au talent d’un maître artisan. S’il observe des câbles en­che­vê­trés ou des conduites mal posées, il comprend im­mé­dia­te­ment que les choses ont été mal faites. À la dif­fé­rence près que ces motifs évo­ca­teurs se pré­sen­tent autrement dans un code source.

Le célèbre pro­gram­meur bri­tan­nique Martin Fowler impute la création du terme à son collègue Kent Beck. Sur son propre blog, Fowler fournit la dé­fi­ni­tion suivante :

Citation

« A Code Smell is a surface in­di­ca­tion that usually cor­res­ponds to a deeper problem in the system. » – Source : https://mar­tin­fow­ler.com/bliki/CodeSmell.html
Tra­duc­tion : « Un code smell est un indice su­per­fi­ciel, qui s’ac­com­pagne gé­né­ra­le­ment d’un problème plus profond dans le système. » (traduit par IONOS)

Un code smell signale l’existence d’un problème sys­té­ma­tique. Soit le code a été écrit par un pro­gram­meur pré­sen­tant un manque de com­pé­tence ou par une équipe de pro­gram­ma­tion à forte rotation. Dans ce dernier cas, le degré variable de com­pé­tence, de con­nais­sance du codebase et de con­nais­sance des di­rec­tives et des normes débouche sur la qualité dé­fi­ciente du code. Le code commence à sentir mauvais.

Un code smell ne doit pas être confondu avec des déficits ponctuels parsemant iné­vi­ta­ble­ment chaque codebase. À première vue, un code smell n’est rien d’autre qu’un simple indice. Quand un pro­gram­meur perçoit un code smell, il est important de vérifier si un problème sys­té­ma­tique existe réel­le­ment.

Si le code semble ainsi déficient, il existe dif­fé­rentes façons de « nettoyer » le code. On parle souvent à ce stade d’approches de re­fac­to­ring ou de « réusinage » du code en français, qui servent à améliorer la structure du code tout en con­ser­vant sa fonc­tion­na­lité. Selon la portée et la com­plexité du code, il peut toutefois s’avérer im­pos­sible de supprimer les code smells. Seule la « réé­cri­ture » du code, c’est-à-dire tout reprendre de zéro, peut résoudre ce problème.

Quels sont les types de code smells ?

Le code est un support qui se déploie sur dif­fé­rents niveaux d’abs­trac­tion. Prenons l’exemple d’une ap­pli­ca­tion. Elle se compose d’un certain nombre d’éléments, qui sont tous définis dans le code : il s’agit des variables, fonctions, classes, modules. Pour examiner les dif­fé­rents code smells, nous dis­tin­guons les niveaux d’abs­trac­tion :

  1. code smells généraux
  2. code smells au niveau de la fonction
  3. code smells au niveau de la classe
  4. code smells au niveau de l’ap­pli­ca­tion

Code smells généraux

Les code smells généraux se re­trou­vent à tous les niveaux d’une ap­pli­ca­tion. Ils ne cons­ti­tuent pas d’erreurs à part entière la plupart du temps, mais ce sont des schémas qui exercent une influence négative. Une règle de base dans la pro­gram­ma­tion stipule que le code devrait être écrit prin­ci­pa­le­ment pour des ob­ser­va­teurs humains. Même les pro­gram­meurs inex­pé­ri­men­tés écrivent souvent du code qui est à même de fournir le résultat désiré, mais qui n’en demeure pas moins in­com­pré­hen­sible.

Dé­no­mi­na­tion in­cor­recte des struc­tures de code

Nommer les struc­tures de code in­di­vi­duelles, telles que les variables et les fonctions, est un grand art. Mal­heu­reu­se­ment, le code est souvent rempli de noms dénués de sens, absurdes, voire con­tra­dic­toires. Un parfait exemple en sont les noms de variable cons­ti­tués d’une seule lettre : x, i, n, etc. Comme ces noms ne précisent aucun contexte, leur but et leur sig­ni­fi­ca­tion restent un mystère. Il est pré­fé­rable d’utiliser des noms ex­pres­sifs. Ceci permet de lire le code comme un langage naturel et il est plus facile de suivre le flux du programme et d’iden­ti­fier les in­co­hé­rences logiques. Voici le même code avec un nom bien choisi :

Pas de style de codage uniforme

Un code cor­rec­te­ment construit se lit d’une seule traite : on remarque im­mé­dia­te­ment que le code a été créé en res­pec­tant certaines règles. Si une telle rigueur fait défaut, nous sommes en présence d’un code smell. Les di­ver­gences observées dans la dé­no­mi­na­tion indique que plusieurs personnes ont écrit des parties du code chacune de leur côté. Quand il s’agit d’une équipe, ceci peut être dû à un manque de spé­ci­fi­ca­tion uniforme.

Conseil

Avons-nous éveillé votre curiosité ? Consultez également notre article « Le Clean Code, c’est quoi ? ».

Variables non définies

Certains langages, tels que C++, Java et Ja­vaS­cript, vous per­met­tent de déclarer une variable sans spécifier de valeur initiale. La variable existe à partir de la dé­cla­ra­tion, mais n’est pas définie. Cela peut entraîner des bogues subtils. Ce code smell a des im­pli­ca­tions par­ti­cu­liè­re­ment fatales dans des langages au typage flou. Car en l’absence de dé­fi­ni­tion initiale, il est im­pos­sible d’iden­ti­fier le type attendu pour la variable.

Valeurs codées en dur

Les pro­gram­meurs inex­pé­ri­men­tés com­met­tent souvent cette erreur : pour comparer une variable avec une valeur spé­ci­fique, ils entrent la valeur di­rec­te­ment. On parle ici de « valeurs codées en dur ». Les valeurs codées en dur sont pro­blé­ma­tiques dès lors qu’elles ap­pa­rais­sent à plusieurs endroits du programme. Les copies in­di­vi­duelles ont tendance à muter in­dé­pen­dam­ment les unes des autres au fil du temps. Il est pré­fé­rable de définir une constante pour la valeur. On établit ainsi une Single Source of Truth (SSOT, « source unique de vérité ») : tout code né­ces­si­tant la valeur accède à la seule et même constante définie à un seul endroit.

Nombres magiques dans le code

Un nombre magique ou magic number est un cas spécial de valeurs codées en dur. Imaginons que notre code utilise une valeur limitée à 24 bits. 24 bits cor­res­pon­dent à 16 777 216 valeurs possibles ou aux nombres de 0 à 16 777 215.

En l’absence de com­men­taire, il sera mal­heu­reu­se­ment difficile de cerner la pro­ve­nance de cette valeur à une date ul­té­rieure : un nombre magique est né. Le manque de con­nais­sances sur l’origine de la valeur augmente le risque de mo­di­fi­ca­tion ac­ci­den­telle.

On peut éviter ces sources d’erreur en incluant la dé­fi­ni­tion de la valeur comme une ex­pres­sion dans l’af­fec­ta­tion de la constante.

Ins­truc­tions de contrôle pro­fon­dé­ment im­bri­quées

Dans la plupart des langages, aucune limite n’est imposée dans la pro­fon­deur des struc­tures de code im­plan­tées. Mal­heu­reu­se­ment, cela augmente également la com­plexité, ce qui complique la lecture et la com­pré­hen­sion du code. Un code smell très courant est l’im­bri­ca­tion profonde des ins­truc­tions de contrôle dans des boucles et des ra­mi­fi­ca­tions. Plusieurs approches existent pour aplanir l’im­bri­ca­tion, par exemple recourir à un opérateur booléen AND afin de tenir compte des deux con­di­tions dans une ins­truc­tion IF. Une autre approche qui fonc­tionne avec autant de con­di­tions que souhaité fait appel à des clauses de garde.

Note

Évaluer un code comme « pro­fon­dé­ment imbriqué » tient lieu du jugement subjectif. Ce critère s’applique d’une manière générale : les ins­truc­tions de contrôle devraient être im­bri­quées sur un maximum de trois niveaux, ou quatre à titre ex­cep­tion­nel. Il n’est pra­ti­que­ment jamais re­com­mandé ni né­ces­saire d’imbriquer le code à des niveaux plus profonds. Si la tentation de réaliser des im­bri­ca­tions plus profondes se présente, il est temps de re­fac­to­ri­ser le code.

Code smells au niveau de la fonction

Dans la plupart des langages, les fonctions sont l’unité d'exé­cu­tion de base du code. Il existe des sections de code plus petits, comme les variables et les ex­pres­sions, mais ceux-ci sont autonomes. Écrire pro­pre­ment des fonctions est l’apanage d’une ex­pé­rience affirmée en matière de pro­gram­ma­tion. Nous exposons quelques code smells parmi les plus courants ayant trait à la fonction.

Lacunes dans le trai­te­ment des ex­cep­tions

Les fonctions con­tien­nent des valeurs d’entrée sous la forme d’arguments. Dans de nombreux cas de figure, seules certaines valeurs ou plages de valeurs sont valides. Il incombe au res­pon­sable de la pro­gram­ma­tion de vérifier la validité des valeurs d’entrée et de gérer les ex­cep­tions en con­sé­quence.

Accès à la variable à partir d’un domaine de validité parent dans la fonction

Les domaines de validité sont une ca­rac­té­ris­tique fon­da­men­tale de la plupart des langages de pro­gram­ma­tion. Elles spé­ci­fient les noms définis à des endroits précis du code. Ce faisant, les domaines de validité enfant héritent des domaines parent. Quand on accède à une variable définie en externe au sein d’une fonction, nous sommes en présence d’un code smell. Car la valeur peut avoir changé entre la dé­fi­ni­tion de la variable et l’appel de la fonction.

L’idéal serait d’accéder à des valeurs qui sont passées en tant qu’arguments au sein des fonctions. Pour éviter de trans­mettre la même valeur à plusieurs reprises, il suffit de définir des pa­ra­mètres par défaut.

Note

Accéder à une variable située dans le domaine de validité d’une fonction en­ve­lop­pante génère une closure ou « fermeture » en français. Ceci ne cor­res­pond pas à un code smell. Les closures sont un concept important dans la pro­gram­ma­tion Ja­vaS­cript.

Code smells au niveau de la classe

La pro­gram­ma­tion orientée objet (OOP) peut concourir à augmenter la réu­ti­li­sa­tion du code et à réduire la com­plexité. Mal­heu­reu­se­ment, la con­cep­tion de classe est sujette à de multiples erreurs, qui prendront ul­té­rieu­re­ment la forme d’un code smell.

L’un des codes smells les plus courants au niveau de la classe est le God object ou la « classe dieu » cor­res­pon­dante. Un God object combine toutes sortes de fonc­tion­na­li­tés éparses et viole ainsi le principe de la « sé­pa­ra­tion des préoc­cu­pa­tions » ou Se­pa­ra­tion of Concerns en anglais.

Les code smells sont ré­cur­rents dans le contexte de la hié­rar­chie d’héritage. Parfois, le code est inu­ti­le­ment distribué sur plusieurs niveaux d’héritage. L’erreur de recourir aux relations d’héritage au lieu de la com­po­si­tion comme approche prin­ci­pale pour assembler des objets est souvent commise.

Dans la même veine, l’uti­li­sa­tion par inad­ver­tance de « Data Classes » est également con­si­dé­rée comme un code smell. Il s’agit ici de classes qui n’im­plé­men­tent pas leur propre com­por­te­ment. Si aucune méthode autre que les getters et setters gé­né­riques n’est définie, envisagez d’utiliser une structure de données plus simple, telle qu’un dict ou une struct.

Code smells au niveau de l’ap­pli­ca­tion

C’est le cauchemar de toute personne amenée à pro­gram­mer : on est chargé(e) de tra­vail­ler sur une ap­pli­ca­tion existante, et le premier examen du code montre que l’ap­pli­ca­tion entière réside dans un énorme fichier unique. La manière dont les éléments cons­ti­tu­tifs du code in­te­ra­gis­sent est confuse. Comme sur une assiette de nouilles, tout est sens dessus dessous dans un « code spaghetti ».

Toute forme d’abs­trac­tion fait défaut, il n’existe aucune ré­par­ti­tion en classes, et tout au mieux peu de fonctions. Il existe toutes sortes de codes re­don­dants, ce qui entraîne des bogues subtils. On observe de même une pro­li­fé­ra­tion de variables globales.

L’uti­li­sa­tion de variables globales est un code smell par­ti­cu­liè­re­ment répandu. Comme les variables globales peuvent être po­ten­tiel­le­ment modifiées n’importe où dans le code, la porte est grande ouverte pour dis­sé­mi­ner des erreurs dif­fi­ciles à corriger. Lors du débogage, on se voit obligé de parcourir tout le code pour dénicher les bogues.

Un autre code smell spé­ci­fique à l’ap­pli­ca­tion est le manque de sé­pa­ra­tion des préoc­cu­pa­tions. Il est aisé de mélanger le balisage, la fonc­tion­na­lité et le rendu dans un seul fichier, par­ti­cu­liè­re­ment dans le cadre des projets PHP. Cela rend difficile la réu­ti­li­sa­tion et le re­fac­to­ring du code. Il est pré­fé­rable de viser une nette sé­pa­ra­tion des préoc­cu­pa­tions, par exemple en s’appuyant sur le modèle MVC (Model View Con­trol­ler).

Comment supprimer un code smell ?

En soi, il est pré­fé­rable de veiller à la propreté du code de manière per­ma­nente. La meilleure façon de procéder est d’adopter le schéma suivant :

  1. Prototype : créer un brouillon
  2. Test : tester la fonc­tion­na­lité
  3. Re­fac­to­ri­ser : nettoyer le code
  4. Ship : livrer le code à l’en­vi­ron­ne­ment de pro­duc­tion

Mal­heu­reu­se­ment, le troisième point est souvent ignoré, surtout lorsque la décision revient à un ou une res­pon­sable sans ex­pé­rience du codage. Le rai­son­ne­ment est le suivant : le code fonc­tionne, alors pourquoi y investir encore des efforts ? Au fil du temps, le code commence à « puer » ; des dettes tech­niques s’ac­cu­mu­lent.

Si un codebase existant contient des code smells, il n'est pas possible de tout re­com­men­cer à zéro. Il faut alors réusiner le code. Il est pré­fé­rable d’adopter une approche in­cré­men­tielle et de délimiter les zones à l’intérieur du codebase qui sont re­la­ti­ve­ment faciles à nettoyer. Vous séparez les com­po­santes opé­ra­tion­nelles ou pouvant être amé­lio­rées des « coins sombres », qu’il est pré­fé­rable de laisser en paix.

Délimiter les zones de code les moins affectées des zones les plus affectées par les code smells réduit la com­plexité. Cela facilite le test du code et l’exécution des étapes de débogage. Des outils de revue de code au­to­ma­ti­sée qui traquent les code smells et proposent des sug­ges­tions et/ou des aides au nettoyage peuvent faciliter ce processus.

Les approches de re­fac­to­ring sont efficaces pour éliminer les code smells. Il s’agit d’en­cap­su­ler ou d’éclater la fonc­tion­na­lité dans des fonctions. La sup­pres­sion des sections de code inu­ti­li­sées et l’amé­lio­ra­tion du nommage éclair­cis­sent le codebase. On peut s’appuyer ici sur les principes de base « Don’t Repeat Yourself » (DRY) ou « ne pas se répéter » et « You ain’t gonna need it » (YAGNI) ou « Tu n’en auras pas besoin ».

Aller au menu principal