Refractoring java generics by inferring wildcards

332 vues

Publié le

Refractoring java generics by inferring wildcards

Publié dans : Logiciels
0 commentaire
0 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Aucun téléchargement
Vues
Nombre de vues
332
Sur SlideShare
0
Issues des intégrations
0
Intégrations
28
Actions
Partages
0
Téléchargements
2
Commentaires
0
J’aime
0
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive

Refractoring java generics by inferring wildcards

  1. 1. Refractoring Java Generics by Inferring Wildcards, In Practic Michel Mathieu, Nadarajah Mag-Stellon 2014/2015
  2. 2. Table des mati`eres 1 Introduction 2 2 D´efinition de la variance 3 2.1 Pr´emisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.2 Variance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 2.2.1 La covariance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.2.2 La contravariance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 3 Cas concret d’utilisation 6 4 Le fonctionnement de l’outil 8 4.1 La syntaxe de la variance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 4.2 L’analyse de l’influence des types . . . . . . . . . . . . . . . . . . . . . . . 9 4.2.1 Le principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 4.2.2 L’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 5 Les Applications de l’outil 14 6 Conclusion 17
  3. 3. Chapitre 1 Introduction Ce rapport est un r´esum´e de l’article intitul´e ”Refactoring Java Generics by Inferring Wildcards, In Practice” ´ecrit par John Altidor et Yannis Smaragdakis. Cet article traite d’un outil, un algorithme, permettant de renommer et inf´erer des types plus g´en´eriques d’instances de g´en´eriques Java en utilisant des Wildcards. Des statistiques ont montr´e que sur les six principales librairies Java utilisant les g´en´eriques, 34% des d´eclarations valables de signatures de type variant peuvent ˆetre g´en´eralis´ees, c’est `a dire avec des types wildcard plus g´en´eraux. Or, pour une g´en´eralisation, il faut en moyenne mettre `a jour 146 autres d´eclarations. Cela montre qu’il est tr`es fastidieux de le faire `a la main . C’est dans cette perspective que nous allons ´etudier les principes de cet outil et voir quelques exemples o`u celui ci pourrait am´eliorer notre code. Nous ´etudierons dans un second temps sa s´emantique, ainsi que les principes de g´en´eralisations avec ses probl`emes et les solutions apport´ees. Nous analyserons ensuite son algorithme pour enfin terminer par ses applications.
  4. 4. Chapitre 2 D´efinition de la variance 2.1 Pr´emisse La maintenance, la s´ecurit´e et la fiabilit´e des programmes Java augmentent quand les librairies sont r´enomm´ees pour d´efinir des classes g´en´eriques. En effet, les g´en´eriques per- mettent `a l’utilisateur d’indiquer au compilateur le type des ´el´ements dans une collection et donc d’augmenter la s´ecurit´e en ´eliminant les cast douteux. Seulement, les g´en´eriques restreignent le sous typage comme nous le verrons dans un exemple ci-dessous. Le m´ecanisme de variance dans les langages de programmation modernes essaie de r´esoudre le probl`eme en autorisant deux instanciations d’un g´en´erique qui est sous type d’un autre. Le syst`eme de typage java utilise lui, le concept de ”wildcard”. C’est `a dire, lors de l’uti- lisation d’une classe, nous pouvons choisir de sp´ecifier si elle r´ef`ere `a une ”covariante” ”contravariante” ou ”invariante” version de la classe. 2.2 Variance Afin de mieux comprendre les principes de l’outil, il est n´ecessaire de comprendre la notion de variance. Prenons un exemple, en regardant la diff´erence entre les tableaux et les listes Java. Commen¸cons par les tableaux : Number [ ] nombre = new Number [ 3 ] ; nombre [ 0 ] = new Integer (10) ; nombre [ 1 ] = new Double (3.14) ; nombre [ 2 ] = new Byte (0) ; On peut voir ici qu’un tableau peut contenir des ´el´ements de type T et de n’importe quel sous type de celui-ci. Java fait ´egalement ´etat qu’un tableau S[] est un sous type de T[] si S est sous type de T. On peut alors ´ecrire : Integer [ ] e n t i e r s = {1 ,2 ,3 ,4}; Number [ ] nombre = e n t i e r s ; // correct car Integer est sous type de Number En revanche, avec les listes, cela soul`eve des probl`emes :
  5. 5. 2.2 Variance 4 List<Integer > e n t i e r s = new ArrayList<Integer >() ; e n t i e r s . add (1) ; e n t i e r s . add (2) ; List<Number> nombres = e n t i e r s ; // erreur de compilation Il y a un probl`eme avec les types g´en´eriques. Le compilateur nous interdit formellement de faire ¸ca. Tout ceci affecte le pouvoir du polymorphisme en java. La solution est d’utiliser les outils des g´en´eriques java : la covariance et la contravariance. 2.2.1 La covariance Ici, au lieu d’utiliser un type T comme argument de notre type g´en´erique, on utilise une ”wildcard” <?extendsT > : List <? extends Number> maliste = new ArrayList<Integer >() ; List <? extends Number> maliste = new ArrayList<Float >() ; List <? extends Number> maliste = new ArrayList<Double >() ; Avec la covariance, nous pouvons lire des ´el´ements d’une structure, mais rien n’´ecrire dedans. Ainsi, ”Number n = maliste.get(0) ;” est autoris´e. En revanche ”maliste.add(3.14) ;” est refus´e. En effet, le compilateur ne peut d´eterminer le type exact de l’objet dans la structure g´en´erique. Cela peut ˆetre n’importe quoi qui ´etend Number, mais le compilateur ne peut en ˆetre certain. Ainsi, toute tentative de retrouver une valeur g´en´erique est consid´er´ee comme une op´eration non sˆure et est donc rejet´ee imm´ediatement par le compilateur. 2.2.2 La contravariance Ici, on utilise une wildcard diff´erente : <?superT >. La contravariance nous permet de faire l’op´eration oppos´ee. Nous pouvons lire dans une structure mais pas ´ecrire. List<Object> myObjs = new List<Object () ; myObjs . add ( ” Hello ” ) ; myObjs . add ( ”World” ) ; List <? super Number> nombres = myObjs ; nombres . add (10) ; Dans ce cas, nous pouvons bien ajouter un Number dans la liste nombres car Number a pour ancˆetre Object. En revanche, ”Number n = nombres.get(0) ;” produit une erreur de compilation. En effet nous ne sommes pas sˆurs `a 100% d’avoir un Number. Si le compilateur laissait passer cela, nous pourrions avoir `a l’ex´ecution une ClassCastException. En somme, nous utilisons la covariance quand nous voulons seulement lire une valeur g´en´erique dans une structure et la contravariance quand on veut ´ecrire dedans. Un dernier exemple pour illustrer :
  6. 6. 2.2 Variance 5 public s t a t i c void copy ( List <? extends Number> source , List <? super Number> destiny ) { f or (Number number : source ) { destiny . add (number) ; } } List<Integer > myInts = asList (1 ,2 ,3 ,4) ; List<Object> myObjs = new ArrayList<Object >() ; copy ( myInts , myObjs) ; Tous les types g´en´eriques sont ´etiquet´es comme inh´erents `a la covariance, contrava- riance, bivariant ou invariant au type de leurs param`etres. Cette inh´erence peut donc ˆetre employ´ee `a tous les types g´en´eriques. Par exemple, nous pouvons changer de mani`ere raisonnable toutes les occurences de Iterator < T > en Iterator <?extendsT > ou bien Comparator < T > en Comparator <?superT >. Des chercheurs ont alors d´evelopp´e un outil afin d’am´eliorer cette g´en´eralisation. Il poss`ede diff´erentes fonctionnalit´es : 1. Pour aider le programmeur `a utiliser la variance en Java, il permet de r´e´ecrire automatiquement le code en un code avec des wildcards plus g´en´erales. 2. Cependant, tous les types ne peuvent pas ˆetre r´e´ecris, (ex : s’ils sont d´eclar´es dans une tierce autre librairie o`u le code source n’est pas disponible). Ainsi l’utilisateur peut choisir de ne pas r´e´ecrire le code, si garder un type sp´ecifique est pr´ef´erable pour une future mise `a jour. 3. L’outil respecte la s´emantique Java et pr´eserve le comportement du programme.
  7. 7. Chapitre 3 Cas concret d’utilisation Apr`es avoir les principes de la covariance et la contravariance, nous allons voir main- tenant un exemple d’utilisation de l’outil pour renommer des entit´es d’un programme. Prenons le programme suivant : c l a s s WList<E> { private List<E> elems = new LinkedList<E>() ; void add (E elem ) { addAll ( C o l l e c t i o n s . s i n g l e t o n L i s t ( elem ) ) ; } void addAll ( List<E> source ) { addAndLog( source . i t e r a t o r () , t h i s . elems ) ; } s t a t i c <T> void addAndLog( Iterator <T> itr , List<T> dest ) { while ( i t r . hasNext () ) { T elem = i t r . next () ; log ( elem ) ; dest . add ( elem ) ; } } De mani`ere g´en´erale, l’interface List est invariante. c’est `a dire qu’elle autorise la lecture et l’´ecriture d’un ´el´ement. Or dans la m´ethode addAndLog, pour la liste dest rien n’est lu. On ne fait qu’ajouter un ´el´ement avec add. On peut alors se limiter `a une version contravariante de List en faisant un List <?superT > dest. Pour source, la seule m´ethode invoqu´ee est iterator() qui retourne un Iterator < E >. Or Iterator est covariant comme nous l’avons vu pr´ec´edemment. On peut alors de mani`ere sˆure inf´erer le type de source en List <?extendsT >. Cependant, si on ne changeait que le type de source, le programme ne compilerait pas. En effet, la m´ethode addLog attend un Iterator < T > mais on lui fournit maintenant un Iterator <?extendsT > avec source. Une analyse du programme est alors n´ecessaire pour savoir si la g´en´eralisation d’un type entraˆıne le changement de type d’autres d´eclarations. Ce flot d’analyse doit prendre en compte les d´ependances entre
  8. 8. 7 chaque. Ici, nous pouvons alors changer le type de itr en Iterator <?extendsT >. Nous obtenons alors : c l a s s WList<E> { private List<E> elems = new LinkedList<E>() ; void add (E elem ) { addAll ( C o l l e c t i o n s . s i n g l e t o n L i s t ( elem ) ) ; } void addAll ( List <? entends E> source ) { addAndLog( source . i t e r a t o r () , t h i s . elems ) ; } s t a t i c <T> void addAndLog( Iterator <? extends T> itr , List <? super T> dest ) { while ( i t r . hasNext () ) { T elem = i t r . next () ; log ( elem ) ; dest . add ( elem ) ; } } Apr`es avoir vu un exemple de son utilisation, nous allons maintenant ´etudier son fonc- tionnement.
  9. 9. Chapitre 4 Le fonctionnement de l’outil 4.1 La syntaxe de la variance L’outil de refractoring permet d’inf´erer la variance des types rencontr´es dans un pro- gramme. La variance dans le langage Java est implicite. En effet, il n’y pas de syntaxe pour que le programmeur puisse d´efinir la variance d’un type. Contrairement, au langage Scala o`u il est possible de d´efinir la covariance et la contrava- riance par, respectivement, les annotations + et -. En Scala, par d´efaut, un ´el´ement est invariant. L’outil de refractoring va donc se baser sur un ensemble de r`egles et s’inspirer de la syntaxe de Scala pour la d´efinition de la variance. Intuitivement, la variance d’un ´el´ement est contraint par l’utilisation qu’on en fait. Par exemple, le type des param`etres d’une m´ethode est g´en´eralement contravariant et le type de retour d’une m´ethode est g´en´eralement covariant. Ainsi, notons vx la variance de la variable X. Et, essayons de d´eterminer la variance de quelques ´el´ements sur un exemple plutˆot simple : i n t e r f a c e RList<X> { X get ( int i ) ; } i n t e r f a c e WList<Y> { void set ( int i , Y y) ; } i n t e r f a c e IList <Z> { Z setAndGet ( int i , Z z ) ; } Dans l’interface RList, on remarque que la variable X est covariante car elle est le retour de la m´ethode get. On note cela vx = + et se lit X est covariant dans RList. Dans l’interface WList vY = - (contravariant) car Y est un des param`etres de la m´ethode set. Dans l’interface IList vz = o (invariant) car Z est `a la fois covariant et contravariant.
  10. 10. 4.2 L’analyse de l’influence des types 9 C’est de cette mani`ere que l’on peut d´eterminer la variance d’un ´el´ement `a partir d’une d´efinition g´en´erique. Apr`es avoir inf´erer une g´en´eralisation pour un type donn´e T, l’outil de refractoring permet de remplacer le type T par sa g´en´eralisation. En prenant l’exemple ci-dessous, l’outil de refractoring effectue les substitutions suivantes : i n t e r f a c e RList<X> { X get ( int i ) ; } i n t e r f a c e WList<Y> { void set ( int i , <? super Y> y) ; } i n t e r f a c e IList <Z> { Z setAndGet ( int i , Z z ) ; } 4.2 L’analyse de l’influence des types 4.2.1 Le principe Le fait de g´en´eraliser les types d’un programme implique de nombreux compromis. En effet, plusieurs probl`emes apparaissent lors de la g´en´eralisation d’un type : 1. G´en´eraliser une List < String > par une List <? extends String > pose un certain probl`eme. C’est un exemple qui montre qu’en Java, il est impossible de red´efinir La classe String. Il faut donc se poser la question des classes, des m´ethodes ... immuables en Java. 2. La red´efinition de m´ethode est possible uniquement si les param`etres de la m´ethode du fils sont identiques `a celle du p`ere. Ainsi, lorsque l’on g´en´eralise les types des pa- ram`etres d’une m´ethode fils, Java ne consid`ere plus cette m´ethode comme red´efinition de m´ethode de la m´ethode p`ere. Or, on voudrait que ¸ca soit le cas. 3. La g´en´eralisation d’un type peut amener `a g´en´eraliser d’autres ´el´ements qui sont plus ou moins d´ependant de ce type. L’outil de refractoring ne doit pas ajouter d’erreur de compilation et ne doit pas modifier la s´emantique du programme. Ces probl`emes sont r´esolus en r´ealisant un graphe orient´e des influences sur les d´eclarations dans le programme. Ainsi, l’outil de refractoring construit un graphe d’influence ; Pour chaque d´ependance entre les entit´es (variable, signature de m´ethode ...) A et B, il existe une arˆete qui lie les nœuds correspondant aux entit´es A et B dans ce graphe. Par la suite, lorsque l’on g´en´eralise l’entit´e A, on parcourt le graphe d’influence en g´en´eralisant les noeuds connexe au noeud de l’entit´e A. Pour pallier aux soucis 1, l’outil de refractoring d´ecide quels ´el´ements ne peuvent pas ˆetre g´en´eralis´es. Concernant le probl`eme 2, l’outil va consid´erer que la signature des param`etres de la
  11. 11. 4.2 L’analyse de l’influence des types 10 m´ethode m du fils est fortement d´ependante de la signature de la m´ethode m du p`ere. Dans le graphe d’influence, on va donc ajouter une arˆete entre la signature de la m´ethode p`ere et celle du fils. Pour donner une exemple clair au probl`eme 3, si on g´en´eralise le type de retour d’une m´ethode M alors les variables qui sont affect´ees par la valeur du r´esultat de la m´ethode M doivent ˆetre g´en´eralis´es. Il y aura donc un chemin dans le graphe d’influence qui va lier la m´ethode M et les variables affect´ees par la valeur de M. Prenons, un autre exemple, si l’on g´en´eralise les param`etres p1 et p2 d’une m´ethode M. Et si dans le corps de cette m´ethode M, un objet O fait un appel `a la m´ethode O.m(p1, p2). Alors il faudra g´en´eraliser la signature de la m´ethode O.m. Il existe donc un chemin dans le graphe d’influence entre l’objet O et les param`etres p1 et p2. 4.2.2 L’algorithme Les noeuds du graphe d’influence poss`ede la syntaxe abstraite suivante : Figure 4.1 – Syntaxe du graphe d’influence FieldDeclaration est la repr´esentation des variables de classes ou d’objet. V ariableDeclaration correspond `a la d´eclaration de variables locales. ParameterDeclarations correspond aux param`etres des m´ethodes ou des constructeurs. MethodDecl correspond au type de retour d’une m´ethode. On va ´egalement d´efinir un langage pour d´ecrire les programmes Java. Ce langage nous permettra de d´efinir des fonctions qui nous seront utiles pour l’algorithme d’analyse de l’in- fluence des types et permet d’abstraire la complexit´e de la repr´esentation d’un programme Java.
  12. 12. 4.2 L’analyse de l’influence des types 11 Figure 4.2 – Mini langage Avec cette syntaxe, on va pouvoir expliciter la variance des types par v ou w. Et, le et T sont respectivement la d´enotation du mot cl´e extends de Java et d’un tableau de T1, T2, ...Tn D´efinissons les r`egles des fonctions nodesAffectingType(e) et destinationNode(e) en utilisant ce langage : Figure 4.3 – Fonctions utilis´es par l’algorithme
  13. 13. 4.2 L’analyse de l’influence des types 12 Explicitons ces fonctions. nodesAffectingType(e) permet de retrouver l’ensemble des d´eclarations accesibles dans e qui peut modifier le type de e. Et, destinationNode(e) donne l’ensemble des d´eclarations d´ependant de e. Ces fonctions seront tr`es utilis´ees dans l’algorithme. Pour chacune de ces fonctions, on a d´efini trois r`egles. Par exemple, la r`egle N-MonoMethod d´eclare que si le retour de la m´ethode m ne d´epend pas de e alors seule la d´eclaration de m peut affecter le type < T > m < e >. Parlons de l’algorithme de cr´eation du graphe d’influence. L’algorithme effectue trois pas- sages sur le programme pour analyser : 1. Les appels de m´ethode : pour chaque appel de m´ethode < T > m < e > , on va retrouver les d´eclarations de e dans le programme. On va ´egalement retrouver les m´ethodes qui prennent exactement e en param`etre. Et, on va rajouter une arˆete entre e et les param`etres des m´ethodes trouv´ees pr´ec´edement. 2. Les expressions : pour chaque expression e , on cherche les d´eclarations D qui sont affect´ees par e. Et, pour chaque d´eclaration N qui est accessible par e et qui peut affecter e, on relie dans le graphe d’influence N et D. 3. Les d´eclarations de m´ethode : pour chaque d´eclaration de m´ethode M, on va trouver les m´ethodes M qui red´efinissent ou sont r´ed´efinies par M. On va ajouter une arˆete dans le graphe entre les param`etres des methodes M et M . L’ensemble de ces r`egles permettent de construire le graphe d’influence d’un programme Java Voici un pseudo code de l’algorithme :
  14. 14. 4.2 L’analyse de l’influence des types 13 Figure 4.4 – Pseudo code de l’algorithme du graphe d’influence
  15. 15. Chapitre 5 Les Applications de l’outil Apr`es avoir vu comment fonctionnait l’outil, nous pouvons voir que plus le nombre de d´eclarations augmente dans le graphe, et plus le nombre de d´eclarations immuables fait de mˆeme. Ainsi, moins de d´eclarations seront r´e´ecrites parce qu’il existera plus d’arˆetes o`u les r´e´ecritures sont interdites dans le graphe. Pour palier ce probl`eme, les chercheurs ont pens´e qu’il fallait que l’analyse du programme ignore les d´eclarations qui ne peuvent ˆetre aff´ect´ees par la g´en´eralisation, c’est `a dire ne pas les mettre dans le graphe. Voyons certains exemples de types que le flot d’analyse ignore : 1. Les types primitifs tels que les int, char, boolean ainsi que les types monomorphiques comme String et Object. Ces types ne peuvent pas ˆetre modifi´es avec des wildcards. 2. Les types param´etriques qui sont sp´ecifi´es bivariant ( ¡ ?¿ ). Ces types ne peuvent pas ˆetre plus g´en´eralis´es qu’ils ne le sont d´ej`a. L’outil permet donc de g´en´eraliser des classes en s´el´ectionnant quelles d´eclarations parmi les variables locales, les arguments ou retour de m´ethodes lesquels sont `a renommer. Des travaux pr´ec´edents ont montr´e que 53% des interfaces et 37% des classes peuvent ˆetre g´en´eralis´ees. Ceci montre alors l’impact que pourrait avoir cet outil si toutes ces d´eclarations ´etaient r´e´ecrites. Il aiderait ´egalement les utilisateurs qui n’ont pas de grandes notions de variance `a am´eliorer les performances de leur code. Afin d’´evaleur le potentiel de cet outils, des statistiques ont permis de calculer combien de d´eclarations de types param´etriques pouvaient ˆetre r´e´ecrites. L’outil `a ´et´e test´e sur six librairies java. Nous n’en montrerons que trois ici :
  16. 16. 15 Figure 5.1 – Statistiques des r´e´ecritures de toutes les d´eclarations pour les types g´en´eriques Prenons ici l’exemple des interfaces java, il existe 170 interfaces avec des types pa- ram´etr´es. Parmi celles l`a, 148 peuvent ˆetre r´e´ecrites. Apr`es le passage de l’outil, 34 ont ´et´e r´e´ecrites, soit 20%. Sur l’ensemble des six librairies test´ees, nous obtenons un total 12% de r´e´ecritures en prenant compte les classes et les interfaces, sur un potentiel de 73%. Cela repr´esente tout de mˆeme 2220 sur 18259 r´e´ecritures.
  17. 17. 16 Figure 5.2 – Statistiques des r´e´ecritures de toutes les d´eclarations pour les types variant Pour les types variant, nous voyons que nous obtenons de bien meilleures performances. Sur l’ensemble des six librairies, on obtient un total de 34% de renommage. De plus, la r´e´ecriture seule de JDK ne prend que deux minutes pour 198 milles lignes de code.
  18. 18. Chapitre 6 Conclusion En conclusion, apr`es avoir vu quelques exemples d’utilisations ainsi que son fonctionne- ment, nous pouvons voir que cet outil complexe nous permet d’am´eliorer les perfermances de notre code en g´en´eralisant le plus possible les types param´etriques `a l’aide de wildcards. Cependant, en regardant les statistiques sur le pourcentage de renommage apr`es le pas- sage de l’outil sur les six principales librairies Java, 34% des g´en´eriques ont pu ˆetre plus sp´ecifiques avec des wildcards plus g´en´eralis´ees. Nous pouvons alors penser que cet outil peut encore ˆetre am´elior´e. Des travaux futurs ont ´et´e evoqu´e dans ce sens.

×