En génie logiciel, les code smells ou mauvaises odeurs peuvent être de mauvaises pratiques de conception logicielle qui conduisent à l’apparition de défauts[1],[2]. Ces défauts sont souvent issus de mauvais choix d’implantation ou de conception et conduisent à une complexification du code source et de la maintenance et évolutivité de celui‐ci. À la différence d'un AntiPattern, les code smells ne sont pas forcément des erreurs, c'est-à-dire qu'ils peuvent persister sans perspective d'évolution dans un logiciel. Afin de corriger un code smell, il est nécessaire de procéder à un réusinage du code source, c’est‐à‐dire modifier le code sans en altérer son comportement.
Des facteurs tels que la compréhensibilité du code, la facilité avec laquelle il peut être modifié, la facilité avec laquelle il peut être amélioré pour prendre en charge les changements fonctionnels, la capacité des codes à être réutilisés dans des contextes différents, le degré de testabilité du code et la fiabilité du code sont des facteurs. qui peut être utilisé pour identifier les code smells[3].
Exemples de code smells
À la manière des patrons de conceptions, de nombreux exemples de code smells ont été répertoriés et décrits dans la littérature scientifique.
L’anti‐patron Duplicated Code est un exemple de code smell classique. Il s’agit de trouver la même portion de code à plusieurs endroits d’une application.
La duplication de code peut se situer dans une même classe, au sein de méthodes différentes, ou dans des classes différentes.
L’anti‐patron Feature Envy décrit une méthode qui fait de nombreux appels à des méthodes d’autres classes. Le plus souvent ces appels sont faits à des getters (ou accesseurs) par besoin de données.
C’est le signe que la méthode se trouve probablement dans la mauvaise classe.
Aussi connu sous le nom de God Class ou Winnebago, l’anti‐patron Blob est une classe ayant trop de responsabilités au sein du logiciel.
Cela se manifeste par un trop grand nombre d’attributs et de dépendances aux classes data, ainsi qu’un nombre d’appels de ces méthodes par les classes extérieures très important.
L’anti‐patron Long Parameter List est une erreur héritée des débuts de la programmation, avant l’arrivée de l’orienté objet. Chaque fonction nécessitait toute une série de paramètres, qu’il était préférable d’utiliser à la place des variables globales.
Mais aujourd’hui et depuis l’arrivée des langages orientés objets, la situation a évolué et le surnombre de paramètres ne fait que nuire à la lisibilité et la compréhension. Cela complique également l’utilisation et la refactorisation du code.
L’implantation de longues méthodes est un problème récurrent chez les développeurs débutants. Il s’agit de rendre les méthodes inutilement longues et complexes quand il est possible de les décomposer.
Cette décomposition en plus d’améliorer la lisibilité et la compréhension d’un code, permet de simplifier la refactorisation par la suite. On la caractérise souvent par l’utilisation d’un trop grand nombre de paramètres internes, de boucles ou conditions ainsi que de valeurs de retour.
L’anti‐patron Large Class décrit simplement le cas d’une classe possédant beaucoup de variables. Ce genre de situation induit parfois une utilisation séparée des variables et conduit le plus souvent vers de la duplication de code.
Détection et correction de code smells
Moyens de détection
Pour chaque code smell, il existe des moyens de détection plus ou moins mathématiques ainsi que des pratiques permettant de corriger ou même éviter l’erreur. Pour chacun des six code smells précédents, voici la méthodologie de correction ainsi que le moyen de détection à adopter.
Duplicated Code
Pour détecter le smell Duplicated Code (code dupliqué littéralement), il faut mesurer le pourcentage de lignes dupliquées dans le logiciel. Cela peut paraître assez simple d’un premier abord, mais un morceau de code peut être dupliqué de différentes manières. La détection du duplicata exact est triviale, mais dans certains cas, il faut aussi être capable de gérer le renommage ou l’alias.
Si tel est le cas, il faut donc que l’algorithme de détection teste toutes les possibilités de renommage pour être bien certain de ne pas manquer un doublon de code. De plus, il est tout à fait possible que la duplication de code ait été modifiée légèrement. Pour reprendre notre exemple précédent, il faudrait aussi prendre en compte les cas suivants :
Integervar= 5;// Du code ne modifiant pas var...Stringstr=String.valueOf(var);
Dans d’autres cas, il se peut que deux algorithmes différents fassent le même travail. Le cas échéant, l’algorithme le plus optimisé est sélectionné et utilisé pour les deux appels. Cette erreur est souvent commise par les débutants à la suite d'abus de copier/coller. En plus d’augmenter sensiblement la taille du code source, elle engendre également des problèmes lors de la maintenance. Pour corriger ce problème, il faut extraire le code dupliqué et le déplacer dans une nouvelle méthode et remplacer les anciens codes par des appels à cette nouvelle méthode. Enfin, il est possible de l’éviter en s’imposant les bonnes pratiques de développement suivantes :
Factoriser son code le plus possible (et dès que possible).
Le smell Feature Envy (littéralement la « jalousie de fonctionnalités ») survient dans une classe quand une méthode est plus intéressée par les membres (attributs et méthodes) d’une autre classe que les siens. Pour corriger ce genre de problème au sein d’une classe, plusieurs solutions :
Déplacer une méthode d’une classe dans une autre si elle utilise plus les données de celle‐ci que les siennes.
Par exemple, dans la classe Rectangle, la méthode perimetre() peut être totalement déplacée dans la classe AutreClasse.
Déplacer une partie d’une méthode dans une autre si elle accède aux données d’une autre classe plus que les siennes. Même principe avec une méthode accédant aux données de plusieurs classes.
Par exemple, dans la classe bateau, la méthode preparer_bateau() utilise plus d’appels de méthodes de la classe SPI que ses propres méthodes.
La correction adaptée serait donc de créer une méthode dans SPI regroupant les trois appels et d’appeler cette nouvelle méthode dans preparer_bateau().
Pour détecter le code smell Feature Envy, il faut être capable de localiser les méthodes utilisant trop les méthodes d’une classe extérieure. Pour cela, il faut mesurer la force de couplage qu’ont les méthodes d’une classe avec les autres méthodes ou données de classes extérieures. Lanza et Marinescu proposent une formule conditionnelle de détection basée sur des métriques[9] :
Avec :
5, une valeur fixée et expliquée dans leur ouvrage.
LAA (Locality of Attribute Accesses) qui représente la proportion de méthodes de la classe utilisant accédant à plus d’attributs de classes extérieures qu’à ses propres attributs.
FDP (Foreign Data Providers) qui représente la proportion d’utilisation d’attributs « extérieurs » et utilisés dans très peu d’autres classes.
ATFD (Access to Foreign Data) qui représente la proportion de méthodes de la classe qui accèdent aux attributs directement ou via des accesseurs d’autres classes extérieures.
Cette méthode de détection est utilisée dans les outils iPlasma et inFusion. D’autres outils comme JDeodorant, essayent de déplacer les méthodes dans des autres classes. Ainsi pour chaque méthode du programme, un algorithme va former une liste de classes candidates où il est possible de déplacer la méthode. Ensuite, pour chacun de ces candidats, un calcul de distance sera effectué ainsi qu’un calcul sur le nombre d’appels d’accesseurs. À la fin, la classe sélectionnée est celle dont la distance calculée est minimale et le nombre d’appels d’accesseurs maximal[10],[11]. Autre approche avec un outil nommé HIST basé sur l’analyse de commits, une méthode affectée par le Smell Feature Envy a tendance à changer plus fréquemment avec les méthodes des autres classes qu’avec celles de sa propre classe[12]. Enfin, il existe également un plugin Eclipse permettant de détecter cette erreur. Ce greffon nommé Eclipse Metrics[13] calcule un indice de jalousie maximal et dès que cet indice est dépassé, l’utilisateur est informé via un message d’avertissement.
BLOB
Cette erreur est l’impact direct de l’incapacité à concevoir correctement des architectures logicielles complexes dans un langage de programmation procédurale. En plus d’être totalement inélégant, le programme perd en vitesse d’exécution et la classe est difficilement maintenable. Il est possible de l’éviter en s’imposant les bonnes pratiques de développement suivantes :
Il n’y a pas de correction miracle pour un tel cas, il faut repenser son code afin de le factoriser. La taille de la classe n’est pas liée à cette erreur de conception. D’une manière générale, voici les corrections typiques
Retirer les méthodes n’ayant aucun impact sur la classe en question et les replacer dans la classe affectée par l’appel de la méthode.
Ainsi, dans la classe ci‐dessous, la méthode afficher_pilote() devra être déplacée dans la classe Pilote…
Pour la détection, Lanza et Marinescu proposent la formule conditionnelle suivante reposant sur trois métriques[14] :
Avec :
47 et 5, des valeurs fixées et expliquées dans leur ouvrage.
WMC (Weighted Method Count) qui représente la somme de la complexité statistique de toutes les méthodes dans une classe.
TCC (Tight Class Cohesion) qui représente le nombre relatif de méthodes accédant aux attributs.
ATFD (Access to Foreign Data) qui représente le nombre de classes externes qui accèdent aux attributs de la classe directement ou via des accesseurs.
Cette méthode de détection est utilisée dans les outils iPlasma et inFusion.
DECOR utilise des fiches de règles nommées « Rules Cards »[15]. D’après les caractéristiques de ces « Rules Cards », un BLOB est une classe ayant un LCOM5 (un manque de cohésion de méthodes, Lack of Cohesion Of Methods en anglais) supérieur à 20 et un nombre de méthodes et d’attributs supérieurs à 20. De plus, les classes sont identifiables par des noms suspects comme Process, Control, Manage, System etc[16]...
Pour connaître le LCOM5 d’une classe, on utilise la formule ci‐dessous[17] :
Avec :
l, le nombre d’attributs
k, le nombre de méthodes
a, la somme du nombre d’attributs distincts accédés par chaque méthode dans une classe
D’autres outils comme JDeodorant essayent de décomposer la classe en plusieurs afin de déterminer si la classe est un BLOB[18]. Cette décomposition s’effectue par clustering, s’il est possible de partitionner de manière optimale les méthodes dans différentes classes alors c’est un BLOB. Pour former les différents clusters, on utilise alors entre les membres (attributs, méthodes, etc.) de la classe, la distance de Jaccard définie ainsi[19],[11] :
Enfin, avec HIST, outil basé sur l’analyse de commits, on peut détecter un BLOB car c’est une classe qui change régulièrement dans le temps
[20].
Long Parameter List
Le code smell Long Parameter List (longue liste de paramètres littéralement) survient dans les classes contenant une méthode ayant trop de paramètres. Cette erreur est typiquement commise lors de la fusion de plusieurs algorithmes en un seul. Autre possibilité, lorsqu’un développeur essaye de rendre une classe trop paramétrable. Généralement, il est fort difficile de comprendre ces listes de paramètres tant qu’elles deviennent assez vite contradictoires et difficiles à utiliser concrètement.
Voici des exemples de corrections possibles :
Retirer les paramètres résultant d’appels de méthodes sur un autre objet, l’objet en question peut être placé dans le domaine de sa classe ou utilisé comme paramètre.
Par exemple, si les derniers paramètres de la fonction calcul() sont issus d’un objet Mesures :
publicvoidcalcul(...,intx,inty,intz){...}
Il est alors directement possible de passer l’objet Mesure en paramètre.
publicvoidcalcul(...,Mesuresmesures){...}
Regrouper les paramètres pouvant l’être et passer l’objet ainsi créé dans la méthode.
Pour reprendre l’exemple précédent, même si les paramètres x, y et z sont totalement indépendants, il est possible de créer un objet pour réduire le nombre de paramètres.
publicvoidcalcul(...,intx,inty,intz){...}
Encore une fois, il est envisageable de passer ce nouvel objet créé en paramètre.
Pour ce qui est de la détection, il suffit de compter le nombre de paramètres d’une méthode. Chaque outil de détection a son nombre maximum autorisé. Par exemple, PMD a pour valeur maximale 10 et Checkstyle 7[21].
Long Method
Le code smell Long Method (longue méthode littéralement) survient dans les classes contenant une méthode ayant trop d’instructions. Cette erreur est généralement commise par les développeurs ne décomposant pas suffisamment leurs codes. Pour éviter de telles erreurs, un développeur peut adopter les mêmes principes de développement cités dans la partie sur le Duplicated Code : Travailler en TDD ou factoriser son code en partitionnant le code en petites méthodes. Pour détecter ce code smell, il ne suffit pas de compter le nombre de lignes de code des méthodes. En effet, dans certains cas, il est réellement nécessaire d’avoir autant de codes quand il n’est pas fractionnable. On ne peut donc pas créer des moyens de détection comme pour le Long Parameter Method.
Cependant, certains outils ne détectent ce smell qu’en comptant le nombre de lignes de codes NLOC. C’est le cas de Checkstyle, qui considère qu’une méthode est longue à partir de 150 lignes et PDM à partir de 100 lignes[21]. JDeodorant utilise des techniques de tranchage pour déterminer si une méthode peut être scindée en plusieurs autres.
Large Class
Le smell Large Class (classe large littéralement) est une classe contenant trop de responsabilités. Cette erreur est souvent commise par le développeur débutant négligeant les principes de l’orienté objet. La NASA Software Assurance Technology Center a publié les valeurs seuils recommandées pour éviter de créer une Large Class.
Selon elle, une classe a trop de fonctionnalités quand l’une des conditions suivantes n’est pas valide[23] :
Le nombre de méthodes dans la classe est inférieur à 20
Le poids des méthodes par classe est inférieur à 100
Le nombre de méthodes et le nombre de méthodes invoquées par chacune de ces méthodes est inférieur à 100
Le nombre de classes référencées dans la classe est inférieur à 5
On peut aussi compter le nombre de lignes de la classe (souvent appelé NLOC pour number of lines of code) et le comparer à un maximum que l’on s’impose. Par exemple, PMD considère qu’une classe est large si elle contient plus de 1000 lignes de codes et Checkstyle 2000 lignes[21]. Cependant, il est possible de faire des exceptions pour les classes contenant des interfaces graphiques pour l’utilisateur (GUI). Pour corriger ce problème, il faut réduire les responsabilités de la classe en question en restructurant le code en différentes classes de faibles responsabilités. Outre le gain de clarté après la restructuration, la maintenance du programme sera plus aisée.
Outils existants
Il existe de plus en plus d’outils de détection automatique de ces mauvaises pratiques de conception. Voici une liste non exhaustive des principaux outils existants.
DECOR
Proposé par Naouel Moha, DECOR[24] pour Detection&Correction, est un outil qui consiste en la détection et la correction automatique des code smells. Le processus est décomposé en deux parties, DETEX et COREX, qui font la spécificité de cette méthode.
Tout d’abord, la détection est assurée par un ensemble de règles qu’il faut spécifier à l’aide d’un langage de haut niveau.
Ensuite, l’utilisateur suggère ses restructurations de codes souhaitées, de nouveau grâce à l’utilisation du même langage.
Enfin, l’outil se charge d’analyser et corriger automatiquement les défauts dans le code, en se basant sur les règles décrites au préalable.
Cette méthode englobant à la fois la détection et la correction présente l’avantage d’automatiser l’ensemble du processus sans faire intervenir manuellement le développeur pour chaque correction détectée.
DECOR est aujourd’hui capable de détecter 19 code smells différents parmi lesquels on trouve le Blob, Long Method ou Long Class par exemple[25].
inFusion
L’outil inFusion est une solution commerciale clé en main à destination des développeurs et basé sur l’outil iPlasma.
Il présente l’avantage d’être un outil complet avec sa propre interface pour être utilisé de façon indépendante.
Il est capable de détecter plus de 20 code smells parmi lesquels Duplicated Code, Blob, Long Method ou Long class.
inFusion gère également l’analyse de code Java, C ou C++.
JDeodorant
Présenté sous la forme d’un plugin pour l’environnement de développement Eclipse[26] développé par les équipes de l’Université Concordia au Canada et l’Université de Macédoine en Grèce.
L’outil qui s’utilise donc directement dans l’éditeur permet de détecter des code smells et de proposer la refactorisation appropriée.
JDeodorant[27] possède plusieurs avantages, notamment :
Pré‐évaluation des effets de chaque solution proposée.
Guidage de l’utilisateur dans la compréhension des erreurs de conception.
Simplicité de l’outil, « one-click approach ».
Stench Blossom
L’outil Stench Blossom[28] est un détecteur de code smells qui fournit un environnement interactif qui permet aux développeurs de visualiser rapidement les smells contenus dans leur code.
La rétroaction est synthétique et visuelle et offre des informations très détaillées sur l’origine de l’erreur sous la forme d’ensemble de pétales.
Plus l’erreur est importante et plus le pétale est grand. Le développeur peut alors voir directement quelle erreur est la plus importante en regardant l’ensemble créé et corriger son code en conséquence.
Stench Blossom est un plugin Eclipse qui détecte 8 mauvaises odeurs dans des codes Java.
HIST
L’outil HIST, pour Historical Information for Smell deTection[29], est un outil de détection de code smells apportant la particularité d’une approche basée sur l’historique d’un gestionnaire de versions, en plus de l’analyse statique de code habituelle.
La présence de problèmes de conception au sein d’un code source oblige les développeurs à adopter un comportement spécifique lors d’une modification du programme. Ces comportements sont ensuite détectables grâce à l’historique du gestionnaire de versions. HIST propose donc de repérer ces refactorisations particulières via la mise en place de patrons de détections spécifique à un code smells.
HIST prend en charge 5 code smells parmi les plus connus, Divergent Change, Shotgun Surgery, Parallel Inheritance, Blob et Feature Envy.
Par exemple, la détection du code smell Blob par l’utilisation d’un historique se base sur le comportement de refactorisation. En effet, quand une classe Blob est présente dans un programme, la probabilité que cette classe soit modifiée lors d’une refactorisation est élevée. Il suffit alors de regarder dans le projet une classe qui est régulièrement modifiée dans un commit impliquant une autre classe[30].
Comparativement à d’autres outils (notamment DECOR), Hist possède un taux de détection plus élevé[31], cependant il nécessite également plus d’informations que le seul code source. De plus, l’analyse de l’historique se présente comme une phase supplémentaire à l’analyse statique, la comparaison n’a donc d’intérêt tant les deux méthodes sont complémentaires.
Autres
Bien sûr, beaucoup d’autres outils existent, parmi lesquels Checkstyle ou pMD.
↑Geoffrey Hecht, « Détection et analyse de l’impact des défauts de code dans les applications mobiles », Université Lille-I (thèse), Université Lille 1 : Sciences et Technologies ; Université du Québec à Montréal, (lire en ligne, consulté le )
↑Girish Suryanarayana, Ganesh Samarthyam et Tushar Sharma, « Technical Debt », dans Refactoring for Software Design Smells, Elsevier, (lire en ligne), p. 1–7
(en) Martin Fowler, « Refactoring: Improving the Design of Existing Code », Addison-Wesley,
(en) Michele Lanza et Radu Marinescu, « Object-oriented metrics in practice: using software metrics to characterize, evaluate, and improve the design of object-oriented systems », Springer Science & Business Media,
(en) Fabio Palomba, Gabriele Bavota, Rocco Oliveto et Andrea De Luzia, « Anti-Pattern Detection: Methods, Challenges and Open Issues », Advances in Computers, Chapter 4, vol. 95,
(en) Naouel Moha, Yann-Gaël Guéhéneuc, Laurence Duchien et Anne-Françoise Le Meur, « DECOR: A Method for the Specification and Detection of Code and Design Smells », IEEE Transactions on Software Engineering, vol. 36, no 1, , p. 20-36 (ISSN0098-5589, DOI10.1109/TSE.2009.50)
(en) Marios Fokaefs, Nikolaos Tsantalis, Alexander Chatzigeorgiou et Jörg Sander, « Decomposing object-oriented class modules using an agglomerative clustering technique », IEEE International Conference on Software Maintenance, , p. 93-101 (ISSN1063-6773, DOI10.1109/ICSM.2009.5306332)
(en) Cristina Marinescu, Radu Marinescu, Petru Florin Mihancea, Daniel Ratiu et Richard Wettel, « iplasma: An integrated platform for quality assessment of object-oriented design », International Conference on Software Maintenance, , p. 77-80
(en) Nikolaos Tsantalis, Théodoros Chaikalis et Alexander Chatzigeorgiou, « JDeodorant: Identification and Removal of Type-Checking Bad Smells », IEEE European Conference on Software Maintenance and Reengineering, , p. 329-331 (ISSN1534-5351, DOI10.1109/CSMR.2008.4493342)
(en) Emerson Murphy-Hill et Andrew P. Black, « Decomposing object-oriented class modules using an agglomerative clustering technique », IEEE International Symposium on Software Visualization, , p. 5-14 (DOI10.1145/1879211.1879216)
(en) Fabio Palomba, Rocco Oliveto, Gabriele Bavota, Andrea De Lucia, Massimiliano Di Penta et Denys Poshyvanyk, « Detecting Bad Smells in Source Code using Change History Information », IEEE International Conference on Automated Software Engineering, , p. 268-278 (DOI10.1109/ASE.2013.6693086)
(en) Fabio Palomba, Rocco Oliveto, Gabriele Bavota, Andrea De Lucia, Massimiliano Di Penta et Denys Poshyvanyk, « Mining Version Histories for Detecting Code Smells », IEEE Transactions on Software Engineering, vol. 41, , p. 462-489 (DOI10.1109/TSE.2014.2372760)
(en) Ankita Mann, Sandeep Dalal et Dhreej Chhillar, « An Effort to Improve Cohesion Metrics Using Inheritance », International Journal of Engineering and Advanced Technology, vol. 2, (ISSN2250-3005)
(en) Francesca Arcelli Fontana, Pietro Braione et Marco Zanoni, « Automatic detection of bad smells in code: An experimental assessment », Journal of Object Technology, vol. 11,
(en) Bartosz Walter et Pawel Martenka, « Looking for Patterns in Code Bad Smells Relations », IEEE International Conference on Software Testing, Verification and Validation Workshops, , p. 465-466 (DOI10.1109/ICSTW.2011.89)
(en) Nikolaos Tsantalis et Alexander Chatzigeorgiou, « Identification of Move Method Refactoring Opportunities », IEEE Transactions on Software Engineering, vol. 35, , p. 347-367 (ISSN0098-5589, DOI10.1109/TSE.2009.1)
(en) M.V. Mäntylä, « Identification of Move Method Refactoring Opportunities », IEEE International Symposium on Empirical Software Engineering, (DOI10.1109/ISESE.2005.1541837)