refractoring java generics by inferring wildcards

18
Refractoring Java Generics by Inferring Wildcards, In Practic Michel Mathieu, Nadarajah Mag-Stellon 2014/2015

Upload: mag-stellon-nadarajah

Post on 16-Jul-2015

121 views

Category:

Software


4 download

TRANSCRIPT

Refractoring Java Generics by Inferring Wildcards, InPractic

Michel Mathieu, Nadarajah Mag-Stellon

2014/2015

Table des matieres

1 Introduction 2

2 Definition de la variance 32.1 Premisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32.2 Variance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2.2.1 La covariance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42.2.2 La contravariance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

3 Cas concret d’utilisation 6

4 Le fonctionnement de l’outil 84.1 La syntaxe de la variance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84.2 L’analyse de l’influence des types . . . . . . . . . . . . . . . . . . . . . . . 9

4.2.1 Le principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94.2.2 L’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

5 Les Applications de l’outil 14

6 Conclusion 17

Chapitre 1

Introduction

Ce rapport est un resume de l’article intitule ”Refactoring Java Generics by InferringWildcards, In Practice” ecrit par John Altidor et Yannis Smaragdakis. Cet article traited’un outil, un algorithme, permettant de renommer et inferer des types plus generiquesd’instances de generiques Java en utilisant des Wildcards. Des statistiques ont montre quesur les six principales librairies Java utilisant les generiques, 34% des declarations valablesde signatures de type variant peuvent etre generalisees, c’est a dire avec des types wildcardplus generaux. Or, pour une generalisation, il faut en moyenne mettre a jour 146 autresdeclarations. Cela montre qu’il est tres fastidieux de le faire a la main .C’est dans cette perspective que nous allons etudier les principes de cet outil et voirquelques exemples ou celui ci pourrait ameliorer notre code. Nous etudierons dans unsecond temps sa semantique, ainsi que les principes de generalisations avec ses problemeset les solutions apportees. Nous analyserons ensuite son algorithme pour enfin terminerpar ses applications.

Chapitre 2

Definition de la variance

2.1 Premisse

La maintenance, la securite et la fiabilite des programmes Java augmentent quand leslibrairies sont renommees pour definir des classes generiques. En effet, les generiques per-mettent a l’utilisateur d’indiquer au compilateur le type des elements dans une collectionet donc d’augmenter la securite en eliminant les cast douteux. Seulement, les generiquesrestreignent le sous typage comme nous le verrons dans un exemple ci-dessous.Le mecanisme de variance dans les langages de programmation modernes essaie de resoudrele probleme en autorisant deux instanciations d’un generique qui est sous type d’un autre.Le systeme 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 specifier si elle refere a une ”covariante””contravariante” ou ”invariante” version de la classe.

2.2 Variance

Afin de mieux comprendre les principes de l’outil, il est necessaire de comprendre lanotion de variance. Prenons un exemple, en regardant la difference entre les tableaux etles listes Java. Commencons par les tableaux :

Number [ ] nombre = new Number [ 3 ] ;nombre [ 0 ] = new In t eg e r (10) ;nombre [ 1 ] = new Double ( 3 . 1 4 ) ;nombre [ 2 ] = new Byte (0 ) ;

On peut voir ici qu’un tableau peut contenir des elements de type T et de n’importequel sous type de celui-ci. Java fait egalement etat qu’un tableau S[] est un sous type deT[] si S est sous type de T. On peut alors ecrire :

I n t eg e r [ ] e n t i e r s = {1 , 2 , 3 , 4} ;Number [ ] nombre = en t i e r s ; // c o r r e c t car In t eg e r e s t sous type de Number

En revanche, avec les listes, cela souleve des problemes :

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 ) ;L i s t<Number> nombres = en t i e r s ; // e r r eu r de compi la t ion

Il y a un probleme avec les types generiques. Le compilateur nous interdit formellementde faire ca. Tout ceci affecte le pouvoir du polymorphisme en java. La solution est d’utiliserles outils des generiques java : la covariance et la contravariance.

2.2.1 La covariance

Ici, au lieu d’utiliser un type T comme argument de notre type generique, on utiliseune ”wildcard” <?extendsT > :

List<? extends Number> mal i s t e = new ArrayList<Integer >() ;L i s t<? extends Number> mal i s t e = new ArrayList<Float >() ;L i s t<? extends Number> mal i s t e = new ArrayList<Double>() ;

Avec la covariance, nous pouvons lire des elements d’une structure, mais rien n’ecrirededans. Ainsi, ”Number n = maliste.get(0) ;” est autorise.En revanche ”maliste.add(3.14) ;” est refuse. En effet, le compilateur ne peut determiner letype exact de l’objet dans la structure generique. Cela peut etre n’importe quoi qui etendNumber, mais le compilateur ne peut en etre certain. Ainsi, toute tentative de retrouverune valeur generique est consideree comme une operation non sure et est donc rejeteeimmediatement par le compilateur.

2.2.2 La contravariance

Ici, on utilise une wildcard differente : <?superT >. La contravariance nous permet defaire l’operation opposee. Nous pouvons lire dans une structure mais pas ecrire.

List<Object> myObjs = new List<Object ( ) ;myObjs . add ( ”He l lo ” ) ;myObjs . add ( ”World” ) ;L i s t<? super Number> nombres = myObjs ;nombres . add (10) ;

Dans ce cas, nous pouvons bien ajouter un Number dans la liste nombres car Numbera pour ancetre Object. En revanche, ”Number n = nombres.get(0) ;” produit une erreur decompilation. En effet nous ne sommes pas surs a 100% d’avoir un Number. Si le compilateurlaissait passer cela, nous pourrions avoir a l’execution une ClassCastException. En somme,nous utilisons la covariance quand nous voulons seulement lire une valeur generique dansune structure et la contravariance quand on veut ecrire dedans. Un dernier exemple pourillustrer :

2.2 Variance 5

pub l i c s t a t i c void copy ( Lis t<? extends Number> source ,L i s t<? super Number> des t iny ) {

f o r (Number number : source ) {des t iny . add (number ) ;

}}

List<Integer> myInts = a sL i s t ( 1 , 2 , 3 , 4 ) ;L i s t<Object> myObjs = new ArrayList<Object>() ;copy (myInts , myObjs ) ;

Tous les types generiques sont etiquetes comme inherents a la covariance, contrava-riance, bivariant ou invariant au type de leurs parametres. Cette inherence peut donc etreemployee a tous les types generiques.Par exemple, nous pouvons changer de maniere raisonnable toutes les occurences de Iterator <T > en Iterator <?extendsT > ou bien Comparator < T > en Comparator <?superT >.Des chercheurs ont alors developpe un outil afin d’ameliorer cette generalisation. Il possededifferentes fonctionnalites :

1. Pour aider le programmeur a utiliser la variance en Java, il permet de reecrireautomatiquement le code en un code avec des wildcards plus generales.

2. Cependant, tous les types ne peuvent pas etre reecris, (ex : s’ils sont declares dansune tierce autre librairie ou le code source n’est pas disponible). Ainsi l’utilisateurpeut choisir de ne pas reecrire le code, si garder un type specifique est preferablepour une future mise a jour.

3. L’outil respecte la semantique Java et preserve le comportement du programme.

Chapitre 3

Cas concret d’utilisation

Apres avoir les principes de la covariance et la contravariance, nous allons voir main-tenant un exemple d’utilisation de l’outil pour renommer des entites d’un programme.Prenons le programme suivant :

c l a s s WList<E> {pr i va t e Lis t<E> elems = new LinkedList<E>() ;

void add (E elem ) {addAll ( Co l l e c t i o n s . s i n g l e t o nL i s t ( elem ) ) ;

}

void addAll ( L i s t<E> source ) {addAndLog( source . i t e r a t o r ( ) , t h i s . e lems ) ;

}

s t a t i c <T> void addAndLog( I t e r a t o r<T> i t r , L i s t<T> dest ) {whi le ( i t r . hasNext ( ) ) {T elem = i t r . next ( ) ;l og ( elem ) ;des t . add ( elem ) ;}

}

De maniere generale, l’interface List est invariante. c’est a dire qu’elle autorise la lectureet l’ecriture d’un element. Or dans la methode addAndLog, pour la liste dest rien n’estlu. On ne fait qu’ajouter un element avec add. On peut alors se limiter a une versioncontravariante de List en faisant un List <?superT > dest. Pour source, la seule methodeinvoquee est iterator() qui retourne un Iterator < E >. Or Iterator est covariant commenous l’avons vu precedemment. On peut alors de maniere sure inferer le type de source enList <?extendsT >. Cependant, si on ne changeait que le type de source, le programmene compilerait pas. En effet, la methode addLog attend un Iterator < T > mais on luifournit maintenant un Iterator <?extendsT > avec source. Une analyse du programmeest alors necessaire pour savoir si la generalisation d’un type entraıne le changement detype d’autres declarations. Ce flot d’analyse doit prendre en compte les dependances entre

7

chaque. Ici, nous pouvons alors changer le type de itr en Iterator <?extendsT >. Nousobtenons alors :

c l a s s WList<E> {pr i va t e Lis t<E> elems = new LinkedList<E>() ;

void add (E elem ) {addAll ( Co l l e c t i o n s . s i n g l e t o nL i s t ( elem ) ) ;

}

void addAll ( L i s t<? entends E> source ) {addAndLog( source . i t e r a t o r ( ) , t h i s . e lems ) ;

}

s t a t i c <T> void addAndLog( I t e r a t o r <? extends T> i t r , L i s t<? super T> dest ){

whi le ( i t r . hasNext ( ) ) {T elem = i t r . next ( ) ;l og ( elem ) ;des t . add ( elem ) ;}

}

Apres avoir vu un exemple de son utilisation, nous allons maintenant etudier son fonc-tionnement.

Chapitre 4

Le fonctionnement de l’outil

4.1 La syntaxe de la variance

L’outil de refractoring permet d’inferer la variance des types rencontres dans un pro-gramme.La variance dans le langage Java est implicite. En effet, il n’y pas de syntaxe pour que leprogrammeur puisse definir la variance d’un type.Contrairement, au langage Scala ou il est possible de definir la covariance et la contrava-riance par, respectivement, les annotations + et -. En Scala, par defaut, un element estinvariant.

L’outil de refractoring va donc se baser sur un ensemble de regles et s’inspirer de la syntaxede Scala pour la definition de la variance.

Intuitivement, la variance d’un element est contraint par l’utilisation qu’on en fait. Parexemple, le type des parametres d’une methode est generalement contravariant et le typede retour d’une methode est generalement covariant.

Ainsi, notons vx la variance de la variable X. Et, essayons de determiner la variance dequelques elements sur un exemple plutot simple :

i n t e r f a c e RList<X> { X get ( i n t i ) ; }i n t e r f a c e WList<Y> { void s e t ( i n t i , Y y ) ; }i n t e r f a c e IL i s t<Z> { Z setAndGet ( i n t i , Z z ) ; }

Dans l’interface RList, on remarque que la variable X est covariante car elle est le retourde la methode get. On note cela vx = + et se lit X est covariant dans RList.

Dans l’interface WList vY = - (contravariant) car Y est un des parametres de la methodeset.Dans l’interface IList vz = o (invariant) car Z est a la fois covariant et contravariant.

4.2 L’analyse de l’influence des types 9

C’est de cette maniere que l’on peut determiner la variance d’un element a partir d’unedefinition generique.Apres avoir inferer une generalisation pour un type donne T, l’outil de refractoring permetde remplacer le type T par sa generalisation.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 ( i n t i ) ; }i n t e r f a c e WList<Y> { void s e t ( i n t i , <? super Y> y ) ; }i n t e r f a c e IL i s t<Z> { Z setAndGet ( i n t i , Z z ) ; }

4.2 L’analyse de l’influence des types

4.2.1 Le principe

Le fait de generaliser les types d’un programme implique de nombreux compromis. Eneffet, plusieurs problemes apparaissent lors de la generalisation d’un type :

1. Generaliser une List < String > par une List <? extends String > pose un certainprobleme. C’est un exemple qui montre qu’en Java, il est impossible de redefinirLa classe String. Il faut donc se poser la question des classes, des methodes ...immuables en Java.

2. La redefinition de methode est possible uniquement si les parametres de la methodedu fils sont identiques a celle du pere. Ainsi, lorsque l’on generalise les types des pa-rametres d’une methode fils, Java ne considere plus cette methode comme redefinitionde methode de la methode pere. Or, on voudrait que ca soit le cas.

3. La generalisation d’un type peut amener a generaliser d’autres elements qui sontplus ou moins dependant de ce type. L’outil de refractoring ne doit pas ajouterd’erreur de compilation et ne doit pas modifier la semantique du programme.

Ces problemes sont resolus en realisant un graphe oriente des influences sur les declarationsdans le programme.Ainsi, l’outil de refractoring construit un graphe d’influence ; Pour chaque dependance entreles entites (variable, signature de methode ...) A et B, il existe une arete qui lie les nœudscorrespondant aux entites A et B dans ce graphe.Par la suite, lorsque l’on generalise l’entite A, on parcourt le graphe d’influence en generalisantles noeuds connexe au noeud de l’entite A.

Pour pallier aux soucis 1, l’outil de refractoring decide quels elements ne peuvent pasetre generalises.

Concernant le probleme 2, l’outil va considerer que la signature des parametres de la

4.2 L’analyse de l’influence des types 10

methode m du fils est fortement dependante de la signature de la methode m du pere.Dans le graphe d’influence, on va donc ajouter une arete entre la signature de la methodepere et celle du fils.

Pour donner une exemple clair au probleme 3, si on generalise le type de retour d’unemethode M alors les variables qui sont affectees par la valeur du resultat de la methode Mdoivent etre generalises. Il y aura donc un chemin dans le graphe d’influence qui va lier lamethode M et les variables affectees par la valeur de M.Prenons, un autre exemple, si l’on generalise les parametres p1 et p2 d’une methode M . Etsi dans le corps de cette methode M , un objet O fait un appel a la methode O.m(p1, p2).Alors il faudra generaliser la signature de la methode O.m. Il existe donc un chemin dansle graphe d’influence entre l’objet O et les parametres p1 et p2.

4.2.2 L’algorithme

Les noeuds du graphe d’influence possede la syntaxe abstraite suivante :

Figure 4.1 – Syntaxe du graphe d’influence

FieldDeclaration est la representation des variables de classes ou d’objet.V ariableDeclaration correspond a la declaration de variables locales.ParameterDeclarations correspond aux parametres des methodes ou des constructeurs.MethodDecl correspond au type de retour d’une methode.

On va egalement definir un langage pour decrire les programmes Java. Ce langage nouspermettra de definir des fonctions qui nous seront utiles pour l’algorithme d’analyse de l’in-fluence des types et permet d’abstraire la complexite de la representation d’un programmeJava.

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, leC et T sont respectivement la denotation du mot cle extends de Java et d’un tableau deT1, T2, ...Tn

Definissons les regles des fonctions nodesAffectingType(e) et destinationNode(e) enutilisant ce langage :

Figure 4.3 – Fonctions utilises par l’algorithme

4.2 L’analyse de l’influence des types 12

Explicitons ces fonctions.nodesAffectingType(e) permet de retrouver l’ensemble des declarations accesibles dans equi peut modifier le type de e. Et, destinationNode(e) donne l’ensemble des declarationsdependant de e. Ces fonctions seront tres utilisees dans l’algorithme.

Pour chacune de ces fonctions, on a defini trois regles. Par exemple, la regle N -MonoMethoddeclare que si le retour de la methode m ne depend pas de e alors seule la declaration dem peut affecter le type < T > m < e >.

Parlons de l’algorithme de creation du graphe d’influence. L’algorithme effectue trois pas-sages sur le programme pour analyser :

1. Les appels de methode : pour chaque appel de methode < T > m < e > , on varetrouver les declarations de e dans le programme. On va egalement retrouver lesmethodes qui prennent exactement e en parametre. Et, on va rajouter une areteentre e et les parametres des methodes trouvees precedement.

2. Les expressions : pour chaque expression e , on cherche les declarations D qui sontaffectees par e. Et, pour chaque declaration N qui est accessible par e et qui peutaffecter e, on relie dans le graphe d’influence N et D.

3. Les declarations de methode : pour chaque declaration de methode M , on va trouverles methodes M ′ qui redefinissent ou sont redefinies par M . On va ajouter une aretedans le graphe entre les parametres des methodes M et M ′.

L’ensemble de ces regles permettent de construire le graphe d’influence d’un programmeJava Voici un pseudo code de l’algorithme :

4.2 L’analyse de l’influence des types 13

Figure 4.4 – Pseudo code de l’algorithme du graphe d’influence

Chapitre 5

Les Applications de l’outil

Apres avoir vu comment fonctionnait l’outil, nous pouvons voir que plus le nombre dedeclarations augmente dans le graphe, et plus le nombre de declarations immuables faitde meme. Ainsi, moins de declarations seront reecrites parce qu’il existera plus d’aretesou les reecritures sont interdites dans le graphe. Pour palier ce probleme, les chercheursont pense qu’il fallait que l’analyse du programme ignore les declarations qui ne peuventetre affectees par la generalisation, c’est a dire ne pas les mettre dans le graphe. Voyonscertains exemples de types que le flot d’analyse ignore :

1. Les types primitifs tels que les int, char, boolean ainsi que les types monomorphiquescomme String et Object. Ces types ne peuvent pas etre modifies avec des wildcards.

2. Les types parametriques qui sont specifies bivariant ( ¡ ?¿ ). Ces types ne peuventpas etre plus generalises qu’ils ne le sont deja.

L’outil permet donc de generaliser des classes en selectionnant quelles declarationsparmi les variables locales, les arguments ou retour de methodes lesquels sont a renommer.Des travaux precedents ont montre que 53% des interfaces et 37% des classes peuvent etregeneralisees. Ceci montre alors l’impact que pourrait avoir cet outil si toutes ces declarationsetaient reecrites. Il aiderait egalement les utilisateurs qui n’ont pas de grandes notions devariance a ameliorer les performances de leur code. Afin d’evaleur le potentiel de cet outils,des statistiques ont permis de calculer combien de declarations de types parametriquespouvaient etre reecrites. L’outil a ete teste sur six librairies java. Nous n’en montreronsque trois ici :

15

Figure 5.1 – Statistiques des reecritures de toutes les declarations pour les typesgeneriques

Prenons ici l’exemple des interfaces java, il existe 170 interfaces avec des types pa-rametres. Parmi celles la, 148 peuvent etre reecrites. Apres le passage de l’outil, 34 ont etereecrites, soit 20%. Sur l’ensemble des six librairies testees, nous obtenons un total 12% dereecritures en prenant compte les classes et les interfaces, sur un potentiel de 73%. Celarepresente tout de meme 2220 sur 18259 reecritures.

16

Figure 5.2 – Statistiques des reecritures de toutes les declarations 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 reecriture seule de JDK ne prend que deux minutes pour 198 milles lignes decode.

Chapitre 6

Conclusion

En conclusion, apres avoir vu quelques exemples d’utilisations ainsi que son fonctionne-ment, nous pouvons voir que cet outil complexe nous permet d’ameliorer les perfermancesde notre code en generalisant le plus possible les types parametriques a l’aide de wildcards.Cependant, en regardant les statistiques sur le pourcentage de renommage apres le pas-sage de l’outil sur les six principales librairies Java, 34% des generiques ont pu etre plusspecifiques avec des wildcards plus generalisees. Nous pouvons alors penser que cet outilpeut encore etre ameliore. Des travaux futurs ont ete evoque dans ce sens.