Migration d'interfaces de programmation
La migration d'interfaces de programmation (en anglais : Application Programming Interface - API) est une technique consistant à transformer le code d'une interface de programmation en une autre. Les raisons à cela peuvent être multiples : obsolescence d'interface de programmation, volonté de convertir le code d'un langage vers un autre, etc.
Jusqu'à récemment, les développeurs étaient obligés d'effectuer cette tâche à la main. Néanmoins, des outils sont créés depuis peu pour effectuer cela de manière automatique.
Extraction de correspondance entre interfaces de programmation[1]
Principe général
L'extraction de correspondance entre interfaces de programmation (en anglais : Mining API Mapping - MAM) est une technique pour trouver et effectuer automatiquement la correspondance entre interfaces de programmation de deux langages différents.
Cette technique se base sur l'usage de le l'interface de programmation plutôt que sur son implémentation, et cela pour 3 raisons :
- Les interfaces de programmation sont souvent livrées sans leur code source.
- Les relations trouvées basées sur l'implémentation sont souvent moins précises que les relations basées sur l'usage des interfaces de programmation.
- La correspondance des relations des interfaces de programmation est souvent complexe et ne peut pas être construite à partir des informations disponibles sur l'implémentation des interface de programmation.
Ainsi, cette technique construit les relations entre deux interfaces de programmation de différents langages. Ces relations prennent la forme d'un graphe de transformation d'interface d'API (en anglais : API Transformation Graph - ATG) qui permet de faire la transformation voulue.
Cette technique fonctionne d'une interface de programmation en langage Java vers une interface de programmation en langage C#.
Création des paires
La première étape pour effectuer la transformation est de trouver des classes similaires par leur nom. L'extraction de correspondance entre interfaces de programmation se sert également des noms de paquets pour être plus précis dans la recherche.
Pour chaque classe des deux interfaces de programmation, cette technique essaie de créer les couples selon la formule suivante:
∀ c1 et c2 représentant des classes, ∃ un couple {c1, c2} si similarité{c1.nom, c2.nom} > SIM_THRESHOLD
SIM_THRESHOLD représente un seuil déterminé de manière astucieuse par l'algorithme.
Par la suite, pour chaque couple trouvé, l'algorithme essaie de trouver des attributs et des fonctions formant des couples selon les formules suivantes :
∀ a1 et a2 représentant des attributs, ∃ un couple {a1, a2} si similarité{a1.nom, a2.nom} > SIM_THRESHOLD
∀ f1 et f2 représentant des fonctions, ∃ un couple {f1, f2} si similarité{f1.nom, f2.nom} > SIM_THRESHOLD
Enfin, pour chaque fonction trouvée, l'algorithme essaie de trouver des variables locales et des paramètres formant des couples selon les formules suivantes :
∀ v1 et v2 représentant des variables locales, ∃ un couple {v1, v2} si similarité{v1.nom, v2.nom} > SIM_THRESHOLD
∀ p1 et p2 représentant des paramètres, ∃ un couple {p1, p2} si similarité{p1.nom, p2.nom} > SIM_THRESHOLD
Par exemple, cette technique forme un couple entre IndexFiles de Java et IndexFiles de C# puisque leur nom sont très similaires.
Graphe de transformation d'interface de programmation
Par la suite, l'algorithme construit un graphe de transformation d'interfaces de programmation (en Anglais : API Transformation Graph - ATG) pour chaque méthode. Ce type de graphe décrit les entrées, sorties, noms des méthodes et dépendances entre les données des interfaces de programmation et aide à comparer les méthodes de plusieurs manières.
Ainsi, un graphe de transformation d'interface de programmation pour une méthode m est un graphe orienté G{Ndata, Nm, E} :
- Ndata est un ensemble des champs de la classe dans laquelle se trouve m, des variables et des paramètres de m.
- Nm est l'ensemble de toutes les méthodes appelé par m.
- E est un ensemble d'arcs qui représente les dépendances entre données, et entre appels de méthodes et valeurs de retours.
La construction de ces graphe et leur amélioration via les résultats des autres graphes précédemment construits permet de trouver la correspondance à appliquer entre les deux interfaces de programmation.
Résultats et performance
L'implémentation d'un outil, nommé Java2CSharp, a permis de conduire des premières évaluations sur cette nouvelle technique.
Le tableau ci-dessous présente les résultats d'évaluation. Il montre le nombre de classes et méthodes correctement transformées et la proportion correspondante.
Projet | Classe | Méthode | ||
---|---|---|---|---|
Nombre | Précision | Nombre | Précision | |
db4o | 3155 | 83,3 % | 10787 | 90,0 % |
lucene | 718 | 90,0 % | 2725 | 90,0 % |
logging | 305 | 73,3 % | 56 | 90,0 % |
Ces résultats montrent une très grande précision de transformation grâce à cet outil.
Jumelage[2]
Principe général
Le jumelage (en anglais : twinning) est une méthode de migration d'interfaces de programmation qui oblige tout d'abord à décrire manuellement les correspondances entre deux interfaces de programmation A et B.
[ Enumeration (Vector v) { return v.elements(); } Iterator (ArrayList a) { return a.iterator(); } ]
Cette technique permet par la suite de faire 2 transformations :
- La première transformation est appelée l'adaptation superficielle (en anglais : shallow adaptation). Elle prend en entrée un programme P utilisant une interface de programmation A et une correspondance entre les interfaces de programmation A et B. Elle génère ainsi une variante du programme passé en entrée nommée P' qui utilise désormais l'interface de programmation B.
- La seconde transformation se nomme l'adaptation profonde (en anglais : deep adaptation). Elle prend également en entrée un programme P et une correspondance entre les deux interfaces de programmation A et B. Elle génère une interface I et un nouveau programme P' qui permet d'utiliser via l'interface I soit l'ancienne interface de programmation A, soit la nouvelle B.
Adaptation superficielle
L'adaptation superficielle génère un programme P' à partir d'un programme P et d'une correspondance entre les deux interfaces de programmation.
Cette transformation parcours l'arbre de syntaxe abstraite du programme et applique les changements spécifiés dans la correspondance entre les interfaces de programmation. Par exemple, avec la correspondances précédemment présentée, le code Enumeration elements = objects.elements();
devient Iterator elements = objects.iterator();
Cette technique fonctionne de manière séquentielle, c'est-à -dire qu'elle effectue les transformations par bloc. Les blocs peuvent représenter une ligne ou une suite de lignes délimitée par de accolades
Adaptation profonde
L'adaptation profonde génère, à partir d'un programme P et d'une correspondance M d'une interface de programmation A vers une interface de programmation B les éléments suivants :
- T : une interface de programmation contenant un ensemble de classes abstraites et d'interfaces dont le but est d'abstraire les différences contenu dans la correspondance M.
- P' : une variante de P qui appelle l'interface de programmation T à tous les endroits où P contient du code du domaine de M.
- I1, I2 : implémentations de l'interface de programmation T. I1 représente l'interface de programmation A utilisée dans le code de P et I2 représente la nouvelle interface B voulue grâce à la correspondance M.
Cela a pour avantage de pouvoir utiliser n'importe laquelle des implémentations dans P', puisque l'implémentation utilisée peut être changée très facilement.
Génération de l'API
L'algorithme calcule tout d'abord l'ensemble des appels aux constructeurs et méthodes devant être remplacés. Ce nouvel ensemble ainsi calculé représente les constructeurs et méthodes devant figurer dans la nouvelle interface de programmation T.
Génération des implémentations
L'algorithme de génération des implémentations EmitImpl(n, M)
se sert de l'ensemble précédemment calculé. Il utilise un nombre n faisant office de sélecteur. Il permet par la suite de faire des traitements comme sel1(T, T') = T
et sel2(T, T') = T'
. Ainsi EmitImpl(1, M)
génère l'implémentation de T en utilisant A et EmitImpl(2, M)
génère l'ensemble d'implémentation en utilisant B.
Génération du nouveau programme
La tâche suivante est de générer M' qui spécifie les correspondances entre l'interface de programmation initialement utilisée A et l'interface de programmation T permettant d'utiliser l'une des deux implémentations.
Enfin, il suffit de générer P' en laçant l'algorithme d'adaptation superficielle avec P et M'.
Expérimentations et résultats
La création d'un prototype a permis de tester jumelage. Le prototype a été écrit en environ 2 500 lignes de code et se présente sous la forme d'une extension du framework de translation de code Cornell Polygot. Il a permis de mener 2 expériences :
- Adapter un ensemble de petits programmes utilisant l'analyseur XML Crimson pour leur faire utiliser l'interface de programmation Dom4J Ã la place.
- Transformer un code client Twitter pour utiliser l'interface de programmation Facebook à la place.
Le prototype permettait une approche interactive pour définir les correspondances :
- Identification d'un bloc de code devant être transformé.
- Ecriture des correspondances associées.
- Application des correspondances aux programmes.
- Tentative de compilation du programme obtenu.
- Récupération des erreurs de type pour identifier le prochain bloc de code à transformer.
Approche statistique orientée données[3]
Principe général
L'approche statistique orientée données (en anglais : data-driven statistic approach), qui apprend statistiquement la correspondance entre les utilisations d'interfaces de programmation. Cela est fait depuis le corps des méthodes dans le code client des interfaces de programmation.
Pour chacune des méthodes de l'interface de programmation, l'approche statistique construit un graphe qui permet d'extraire les dépendances entre données et structures de contrôle.
L'analyse de ce graphe permet d'aligner les méthodes et ainsi de trouver les correspondances entre l'interface de programmation.
Représentation sous forme de graphe
Pour chacune des méthodes de l'interface de programmation devant être remplacée par une autre, l'algorithme construit une représentation sous forme de graphe de l'utilisation de l'interface de programmation.
Les nœuds représentent les appels de fonctions, accès aux attributs et les structures de contrôle. Les arcs représentent les dépendances de données et de contrôle entre les nœuds.
Symbole d'usage et phrase
L'approche statistique orientée données extrait depuis le graphe précédant le sous graphe représentant les interactions de l'interface de programmation avec un ou plusieurs objets. Pour chacun de ces sous-graphes, l'algorithme extrait les noms des nœuds pour construire une séquence de symbole. Chacun de ces symboles est appelé un symbole d'usage.
Ensuite, les séquences de symbole de tous les sous groupes précédemment formés sont rassemblées pour former une séquence pour l'ensemble de la méthode. Une telle séquence est appelée une phrase.
Alignement des symboles
Chacune des paires de phrases des méthodes correspondances en Java et C# sont alignées selon leurs symboles grâce à l'algorithme de maximisation d'attente[4]. Les séquences alignées avec un score plus grand qu'un seuil donné représentent les correspondances entre interfaces de programmation.
Résultats et avantage
Le développement d'un outil, nommé StaMiner, basé sur l'algorithme d'approche statistique orienté données a permis de mener des expériences et de comparer cet outil à d'autres. L'ensemble des librairies testées est similaires à celles testées dans la première partie (Extraction de correspondance entre interfaces de programmation).
L'évaluation ainsi menée a permis de montrer que StaMiner a correctement effectuer les transformations dans 87,1 % des cas. Comparé à Java2CSharp, StaMiner a transformé le code avec 4,2 % d'erreurs de compilation en moins et 6 % de code défectueux en moins.
Le principal avantage de cet algorithme par rapport à l'extraction de correspondances entre interfaces de programmation repose sur l'analyse du contenu des méthodes client. En effet, l'extraction de correspondances permet uniquement de détecter une similitude entre noms. Au contraire, l'approche statistique orienté données permet de détecter l'enchainement des dépendances entre données, appels de méthodes. Cela résulte en une plus grande compréhension du code client, et ainsi en une meilleure transformation.
Transformations d'eau[5]
La transformation d'eau est une technique permettant la migration de cadre logiciel (en anglais : framework) web.Les cadres logiciels web ont pour particularité d’entremêler plusieurs langages, notamment le HTML et le code interprété par le serveur. Cette technique n'est pas utile pour la migration des technologies n'utilisant que du code serveur (comme migrer une servlet JavaEE) qui peuvent être migrées via des techniques non spécifiques aux langages web.
Principe général
Cette technique de migration répond à deux contraintes :
- conserver l'emplacement exact du code HTML afin de ne pas dégrader l'interface utilisateur ;
- conserver les commentaires au même emplacement pour s'assurer que le code produit est maintenable.
Cette technique est une extension des grammaires d'îles qui consistent à extraire le code défini comme étant intéressant - les îles - du reste du code - l'eau.
La migration d'un framework web avec la transformation d'eau peut être décomposée en quatre étapes principales :
- suppression des balises HTML et les commentaires ;
- traduction du langage source vers le langage cible ;
- remplacement des interfaces de programmation du langage source vers les interfaces correspondantes dans le langage cible ;
- réinsertion du code HTML et des commentaires supprimés lors de la première étape.
Les étapes deux et trois sont effectuées via l'une des nombreuses méthodes existantes dans la littérature scientifique.
Les grammaires d'îles
Une grammaire d'île est une grammaire qui contient deux types de production :
- Des productions qui décrivent des constructions d’intérêt : les îles.
- Des productions ouvertes qui peuvent contenir toutes les sections inintéressantes restantes.
Formellement , on peut les définir comme suit :
Soit un langage non contextuel , une grammaire non contextuelle tel que et , où :
- et sont des ensembles finis avec ; est un ensemble de variables ou de symboles non terminaux et l'alphabet de symboles terminaux,
- le symbole de départ,
- est un ensemble fini de productions ou de règle de grammaire de la forme où et .
Nous pouvons définir une grammaire d'île pour un ensemble de structures d’intérêt tel que .
Cette définition assure que la grammaire d'île est plus souple que la grammaire d'origine i.e. . Donc peut analyser correctement un texte qui n'est pas valide selon .
Les transformations d'eau
La migration d'un cadre logiciel web par transformation d'eau contient 4 étapes.
- Définition du langage d'île comme étant le langage interprété par le serveur
- Application des transformations d'eau qui transforme l'eau en îles, c'est-à -dire rendre valide le code n'étant pas du code serveur dans la syntaxe du code serveur. Le résultat est un fichier valide écrit dans le langage serveur.
- Transformation du code vers le langage cible via n'importe quel algorithme de migration d'interface de programmation.
- Remise à l'eau des îles, c'est-à -dire l'opération inverse de la transformation d'eau.
Formellement , on peut définir une transformation d'eau comme suit :
Soit la transformation qui cartographie chaque ensemble de jetons consécutifs du fichier d'entrée à le langage d'île , où :
- et , où est le nombre de flux indépendants consécutifs de jetons du fichier d'entrée.
Plus simplement, chaque élément qui n'est pas valide dans le langage d'îles est transformé à , qui est valide dans le langage d'île.
Notes et références
- Hao Zhong, Suresh Thummalapenta, Tao Xie et Lu Zhang, « Mining API Mapping for Language Migration », Proceedings of the 32Nd ACM/IEEE International Conference on Software Engineering - Volume 1, ACM, iCSE '10,‎ , p. 195–204 (ISBN 9781605587196, DOI 10.1145/1806799.1806831, lire en ligne, consulté le )
- Marius Nita et David Notkin, « Using Twinning to Adapt Programs to Alternative APIs », Proceedings of the 32Nd ACM/IEEE International Conference on Software Engineering - Volume 1, ACM, iCSE '10,‎ , p. 205–214 (ISBN 9781605587196, DOI 10.1145/1806799.1806832, lire en ligne, consulté le )
- Anh Tuan Nguyen, Hoan Anh Nguyen, Tung Thanh Nguyen et Tien N. Nguyen, « Statistical Learning Approach for Mining API Usage Mappings for Code Migration », Proceedings of the 29th ACM/IEEE International Conference on Automated Software Engineering, ACM, aSE '14,‎ , p. 457–468 (ISBN 9781450330138, DOI 10.1145/2642937.2643010, lire en ligne, consulté le )
- (en) P.Koehn, « Statistical Machine Translation », The Cambridge Press,‎
- Ahmed E. Hassan et Richard C. Holt, « A Lightweight Approach for Migrating Web Frameworks », Inf. Softw. Technol., vol. 47, no 8,‎ , p. 521–532 (ISSN 0950-5849, DOI 10.1016/j.infsof.2004.10.002, lire en ligne, consulté le )