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[Quand ?], les développeurs étaient obligés d'effectuer cette tâche à la main. Néanmoins, des outils sont créés depuis peu[Depuis quand ?] pour effectuer cela de manière automatique.

Extraction de correspondance entre interfaces de programmation[1] modifier

Principe général modifier

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#.

Détails technique modifier

Création des paires modifier

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 modifier

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 modifier

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.

Résultats de la transformation d'API
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] modifier

Principe général modifier

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 modifier

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 modifier

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 modifier

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 modifier

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 modifier

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 modifier

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] modifier

Principe général modifier

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.

Détails techniques modifier

Représentation sous forme de graphe modifier

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 modifier

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 modifier

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 modifier

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] modifier

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 modifier

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.


Détails techniques modifier

Les grammaires d'îles modifier

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    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 modifier

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.

Bibliographie modifier

Notes et références modifier

  1. 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 )
  2. 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 )
  3. 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 )
  4. (en) P.Koehn, « Statistical Machine Translation », The Cambridge Press,‎
  5. 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 )