Origine et évaluation des clones
Taxonomie des clones
Dans cette section, nous classons les clones selon deux critères principaux. Le premier critère porte sur l’origine du clone : s’agit-il d’un clone effectif lié à une opération de copie intra-projet, inter-projets et avec quelle motivation ? Dans un second temps, nous étudions les niveaux des clones selon leur étendue sur le code source. Nous ne nous intéressons pas ici au degré d’approximation des clones qui sera analysé dans le chapitre suivant consacré aux opérations d’édition et d’obfuscation sur les clones.
Clones phénoménologiques
Quels sont les processus à l’origine de l’apparition de clones intra- et inter-projets ? Une première possibilité est liée à un procédé de copie de code par le développeur dont les motivations peuvent être diverses. Au sein d’un même projet, cette opération permet de réutiliser rapidement du code dans un nouveau contexte. Lorsque la copie est réalisée d’un projet extérieur, celle-ci peut être légitime ou non. Dans ce deuxième cas, il s’agit d’une opération de plagiat généralement suivie de procédés d’obfuscation afin de rendre la détection du clone moins aisée. Enfin d’autres clones de niveau structurel (architecture de paquetages ou unités de compilation) peuvent témoigner de l’utilisation de patrons de conception classiques. Clones intra-projet La copie de morceaux de code dans l’optique d’une réutilisation dans de nouveaux contextes est un processus (malheureusement) courant chez la plupart des programmeurs. Il apparaît ainsi selon plusieurs études [103, 102, 104] sur certains projets Open Source que leur volume pourrait être réduit jusqu’à 15% par la factorisation de morceaux de code copiés. Un moyen simple de détecter à leur source ces clones phénoménologiques pourrait être de surveiller les opérations de copie-collage de code au sein de l’environnement de développement utilisé [68]. Il serait ainsi possible d’inciter le programmeur à factoriser son code pour éviter toute redondance. Cependant, si dans certains cas la factorisation est possible, dans d’autres contextes celle-ci demeure impossible à cause des limitations du langage utilisé. Ainsi par exemple, un langage tel que Java n’offrant pas de mécanisme de généricité de type primitif nécessite la copie de code avec adaptation de types pour utiliser du code avec des types primitifs différents1 . Dans certains cas, en programmation objet, l’intégration de plusieurs fonctionnalités déjà implantées peut se heurter à l’absence d’héritage multiple. Une autre situation problématique est rencontrée lorsque le code correspondant à une fonctionnalité est éparpillé au sein d’un projet (aspect). Une solution serait d’étendre le langage pour surmonter ces limitations (introduction du support de composition de caractéristiques [88] ainsi que des aspects) ou 1On pourra se reporter à de nombreux exemples du JDK notamment avec les méthodes de la classe Arrays nécessitant la réimplantation de méthodes de tri pour tous les types primitifs. 33 2. Origine et évaluation des clones alors d’utiliser des outils de génération automatique de code (pratique couramment utilisée pour le développement de Software Product Lines). La réduction du volume de code d’un projet par la factorisation de clones permet principalement une diminution de l’effort de maintenance généralement justifiée par un souci temporel et financier. La fiabilité et la sécurité du code produit est également un critère important car un bug causé par un exemplaire de code cloné a une faible probabilité d’être corrigé spontanément sur les autres exemplaires. D’autre part, une réécriture optimisée d’un exemplaire de clone peut ne pas être reportée. Dès lors le choix délibéré d’introduire une duplication de code ne peut être rationnellement motivé que par un gain de productivité à court terme qui surpasserait le gain à long terme d’un code factorisé. Ce choix est notamment réalisé lorsque la production d’un code abstrait nécessite un effort de développement important et/ou lorsque sa compréhension serait difficile. Introduire des clones peut également être utile dans des cas d’optimisation du code à l’exécution ou pour l’adaptation de composantes sur des architectures différentes (tel que par exemple le module multi-thread du serveur web Apache [124]). Pour notre travail, nous nous intéressons principalement à la recherche de zones de code dupliqué sur un référentiel de code à un instant donné. L’étude de l’évolution de zones dupliquées au cours de la vie d’un projet [98] présente néanmoins un réel intérêt afin de juger du rôle fonctionnel des clones et de la nécessité de refactorisation. Un groupe de clones apparaissant entre deux versions peut être caractérisé, pour les versions suivantes du projet, par divers destins évolutionnistes. Des nouveaux exemplaires de code dupliqué peuvent se greffer au groupe (nouvelles opérations de copie-collage) ou au contraire être supprimés par factorisation. Des exemplaires peuvent faire l’objet de modifications homogènes ou au contraire évoluer indépendamment entre deux versions. Une évolution indépendante peut être la manifestation de corrections de problèmes non propagés par oubli à d’autres exemplaires ou alors plus légitimement le reflet d’une profonde adaptation contextuelle ; la copie initiale fournit alors un code squelette destiné à être remodelé. Une évolution indépendante n’exclut cependant pas une reconvergence ultérieure du code. L’opération de clonage de code est alors un choix de développement qui constitue une solution transitoire choisie dans l’incertitude des fonctionnalités et de l’architecture globale du projet. On notera qu’un des principaux écueils du suivi de groupes de clones entre versions reste de tracer temporellement les morceaux de code similaires. Clones inter-projets légitimes L’emprunt de code d’un projet vers un autre permet généralement un gain de temps de développement même si quelquefois certaines adaptations plus ou moins importantes doivent être menées. Cette pratique, pouvant consister à la réutilisation de portions plus [101] ou moins importantes de code (méthodes, classes et unité de compilation) peut être légitime si la licence du projet source du clone l’autorise. C’est le cas notamment des licences Open Source2 autorisant les œuvres dérivées et donc l’emprunt de code. Cependant une licence Open Source est libre de fixer ses propres conditions quant à la licence de l’œuvre dérivée : ainsi si les licences de type BSD n’imposent aucune condition quant au projet destination du clone (si ce n’est de conserver les informations concernant l’auteur du code original), les licences de type GPL conditionnent la redistribution d’œuvres dérivées sous les mêmes termes. Il convient donc de choisir judicieusement la licence du projet destination
Niveaux des clones
Clones locaux Un clone local concerne une portion de code similaire de taille modeste à l’échelle d’une fonction. La spécificité d’un clone local réside dans le fait que ses sous-éléments (typiquement des instructions) ne sont en règle générale pas commutatifs. Il peut néanmoins être possible de déterminer des dépendances entre instructions afin de segmenter le corps d’une fonction en différents groupements d’instructions indépendants. Nous présentons en figure 2.2 un exemple de deux exemplaires de clones concernant l’échange de valeurs de deux variables avec types différents et renommage d’identificateurs de variable. Ce clone pourrait être refactorisé par l’écriture d’une macro ou d’une fonction prenant pour paramètres les adresses des variables et leur taille mémoire. Les outils de détection de clones étudiés s’intéressent dans leur majorité à la détection de clones locaux avec une tolérance plus ou moins importante quant aux opérations d’édition entre clones. Clones structurels Un clone structurel est un clone de taille plus étendue qu’un clone local : un tel clone peut concerner à différentes échelles plusieurs groupes d’instructions indépendantes d’une fonction, un ensemble de fonctions, une classe, une unité de compilation, un paquetage voire un projet entier. Ces clones étant de taille importante, ils comportent généralement de nombreuses opérations d’édition. Les sous-éléments d’un groupe de clones structurels peuvent être dans un ordre différent selon les exemplaires. En effet, si l’ordre d’instructions présente une importance à l’échelle d’une fonction, les membres d’une classe ou d’une unité de compilation (variables membres, méthodes…) sont généralement commutatifs. Nous proposons au chapitre 11 une méthode permettant l’agrégation de clones locaux proches afin de créer des clones structurels approximatifs plus étendus : un exemple d’un tel clone (au niveau fonctionnel) révélé par cette méthode est spécifié en figure 2.3. Clones creux Contrairement aux clones structurels qui peuvent être décomposés comme une juxtaposition de clones locaux de localisations plus ou moins proches, les clones creux ne présentent comme similarité que la macro-structure du code source. Ainsi, par exemple, deux classes présentant des méthodes de signature équivalente (mêmes types de paramètres) sans que le corps de ces méthodes soit similaire peuvent être considérées comme des clones structurels creux. De même, deux portions de code présentant la même hiérarchie de structures de contrôle mais avec des instructions différentes peuvent être reportées comme des clones locaux creux. Localiser les clones creux peut être utile dans une optique de réingénierie afin de réorganiser la hiérarchie des classes par la création de nouveaux ancêtres communs lorsque ceci est nécessaire. Ces clones structurels permettent de mettre en lumière des schémas de conception spécifiques et peuvent également permettre de classifier des unités de source de domaines fonctionnels proches mais d’implantations différentes. La représentation la plus adaptée à l’étude des clones creux est l’arbre de syntaxe du code : on pourra alors les mettre en évidence par la recherche de sous-arbres homomorphes jusqu’à une certaine profondeur ou avec abstraction de certains nœuds.
Évaluation des clones détectés
Un clone détecté est un clone dont l’existence est découverte par l’utilisation d’un système de détection de clone, qu’il soit automatique ou humain. On remarque que les méthodes algorithmiques nécessitent l’utilisation de paramètres afin d’ajuster le report de clones conduisant à l’obtention de groupes de clones potentiellement différents. Quant à un juge humain, selon certains critères définis (degré de similarité, utilité de la factorisation, …), il peut rechercher des clones présents au sein de projets avec une part inévitable de subjectivité. Nous proposons maintenant quelques définitions pouvant servir de base à une approche quantitative et qualitative de mesure d’efficience d’un outil de recherche de clones. Il s’agit d’introduire les deux dimensions que sont l’extensibilité et la peuplabilité d’un groupe de clones et de les relier à la notion de pertinence.