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écution 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