URL: https://linuxfr.org/news/modernisez-votre-code-java-en-un-clic-avec-autorefactor-v1-0-0 Title: Modernisez votre code Java en un clic avec AutoRefactor v1.0.0 ! Authors: djano palm123, BAud, Snark, Nÿco et Benoît Sibaud Date: 2015年03月23日T00:25:05+01:00 License: CC By-SA Tags: plugin, refactoring, bonnes_pratiques, java et eclipse Score: 40 Le 22 mars 2015 est sortie la version 1.0.0 du projet AutoRefactor. Ce logiciel est publié sous Eclipse Public License v1.0 et GNU General Public License v3+. AutoRefactor restructure automatiquement le code source d'un programme Java afin de : * Faciliter la maintenance en améliorant la lisibilité * exemple : lorsque toutes les branches d'un `if` ont les mêmes instructions à la fin des blocs, celles-ci sont déplacées après le `if`, ... * Moderniser le code * exemple : passer des APIs de java.util.Vector vers celle de java.util.List, ... * Appliquer les bonnes pratiques * exemple : utilisation d’accolades après un `if`, javadoc bien formés, bonne utilisation des APIs de BigDecimal, TestNG, ... * Réduire la taille du code * exemple : un `if` imbriqué dans un autre `if` devient un `if` unique, suppression des variables locales déclarées juste avant un `return`, ... * Améliorer les performances * exemple : utiliser Integer.valueOf(int) au lieu de new Integer(int), ... ---- [Site officiel](http://autorefactor.org/) [AutoRefactor sur Github](https://github.com/JnRouvignac/AutoRefactor) [Soirée Autorefactor à l'AlpesJUG 24/02/2015 – Le support](http://www.alpesjug.fr/?p=2723) [Exemples de restructuration de code effectuées par AutoRefactor (tirés de la suite de tests)](http://autoRefactor.org/html/samples.html) ---- # Historique # J'ai commencé ce projet car j'en avais assez de faire les mêmes changements manuellement (ou semi-manuellement avec les _refactorings_ proposés par Eclipse) dans tous les fichiers sur lesquels je travaille. J'ai commencé par utiliser des expressions rationnelles pour retravailler toute la base de code, mais les faux positifs étaient bien trop nombreux, et puisque les expressions rationnelles ne travaillent que sur du texte, toute analyse s'appuyant sur la sémantique du langage (types, variables, etc.) est impossible. J'ai donc fait diverses expérimentations pour pouvoir faire des analyses « comprenant » le langage. Hélas toutes ces expérimentations ont raté. Toutes, jusqu’à ce que je crée un greffon Eclipse s'appuyant sur l'API des _Java Development Tools_ (Eclipse JDT). Cette dernière fournit un _parser_, mais surtout une API pour faire des _refactorings_. En fait, c'est l'API que l'IDE Eclipse utilise pour ses propres _refactorings_. # Utilisation # Depuis l'interface d'Eclipse, le développeur sélectionne les fichiers, _packages_ ou projets Java à restructurer, et lance la réorganisation automatique du code. Pour cela, il choisit l’entrée `AutoRefactor` dans le menu contextuel accessible par un clic doit, ou bien il tape le raccourci clavier `Alt+Shift+Y`. Le greffon AutoRefactor restructure alors le code en appliquant automatiquement les _refactorings_ choisis par le développeur. # Principe de fonctionnement # Les restructurations de code sont implémentées par des _refactorings_ successifs, c'est-à-dire des petits changements incrémentaux qui modifient le code sans changer son comportement à l'exécution. Ceci est vrai pour la plupart des _refactorings_, ceci dit, certains peuvent avoir des effets de bord (voir ci-après), mais un programme sensible à ces changements s'expose à des comportements indéfinis lors de l’exécution. ## Système de passes successives ## Le greffon travaille avec un système de passes successives. Lorsque le développeur a choisi les règles de _refactoring_ à appliquer, le greffon prend la liste des _refactorings_ et (algorithme simplifié) : 1. le fichier Java à analyser est parsé et produit un [[arbre syntaxique abstrait]] \(_AST_ en anglais) 1. pour chaque _refactoring_ : 1. cherche des opportunités de _refactoring_ en [visitant](https://fr.wikipedia.org/wiki/Visiteur_(patron_de_conception)) l'arbre syntaxique abstrait 1. génère les réécritures de code lorsqu'une opportunité de _refactoring_ a été identifiée 1. lorsque tout l'arbre syntaxique abstrait a été visité, si des réécritures de code ont été générées : 1. alors, toutes les réécritures de code générées sont appliquées sur le fichier 1. le fichier est sauvegardé 1. boucle vers 1. 1. sinon, fin : il n'y a plus de _refactorings_ possibles sur ce fichier Java Actuellement, tous les _refactorings_ implémentés font du [[filtrage par motif]] \(_pattern matching_) et travaillent fichiers par fichiers. Du coup, il est extrêmement facile de paralléliser le code : le greffon lance autant de fils d’exécution en arrière-plan qu'il y a de processeurs disponibles et utilise une simple liste de tâches pour leur fournir du travail. Chaque tâche consiste à analyser et restructurer un fichier Java. Bien sûr, le greffon dispose d'une suite de tests composée de paires de fichiers exemples Java : un fichier avant restructuration, un autre fichier contenant le résultat que l'on souhaite obtenir après restructuration. La suite de tests unitaires exécute chaque règle de _refactoring_ indépendamment sur sa paire de fichiers et compare la sortie attendue avec la sortie obtenue. Les deux doivent correspondre. La compilation et les tests unitaires sont exécutés sur un serveur d’intégration continue. Je vous encourage très vivement à **regarder les [exemples tirés de la suite de tests](http://autorefactor.org/html/samples.html)**. Par ailleurs, je teste plus avant les _refactorings_ sur la base de code mature d'[OpenDJ](https://linuxfr.org/news/opendj-2-6-pour-faire-swinguer-vos-annuaires-ldap). Cela me permet de trouver pas mal de bugs avant qu'ils n'atteignent une version stable et donc de rajouter des cas de tests. Le code mature est bien plus sournois que les cas de tests auxquels on peut penser ! :) ### Attention ! Il faut toujours relire le code modifié automatiquement avant de committer les changements ! ### En effet, voici un exemple de code pour lequel les changements ont modifié le comportement : ```java Integer i1 = 0; Integer i2 = new Integer(0); if (i1 == i2) { // compare des références d'objets ! System.out.println("OK"); } ``` Ici, "`OK`" n'est pas affiché sur le terminal car `i1` et `i2` sont deux objets différents en mémoire. Maintenant si AutoRefactor modifie le code comme ceci : ```java Integer i1 = 0; Integer i2 = Integer.valueOf(0); if (i1 == i2) { // compare des références d'objets ! System.out.println("OK"); } ``` "`OK`" sera affiché sur le terminal car `i1` et `i2` référencent le même objet en mémoire. Comment est-ce possible ? Et bien la ligne `Integer i1 = 0;` est en fait générée comme ceci par le compilateur java : `Integer i1 = Integer.valueOf(0);`. De plus il existe un cache pour les objets `Integer` entre -128 et 127 (inclus), ce qui fait que `i1` et `i2` référencent le même objet en mémoire. Ce code aurait probablement du être écrit comme ceci : ```java int i1 = 0; int i2 = 0; if (i1 == i2) { // compare la valeur des entiers ! System.out.println("OK"); } ``` Et le code aurait correctement affiché "`OK`" sur le terminal. AutoRefactor ne sait pas encore le faire, mais j’espère y arriver un jour. Cet exemple démontre que certains _refactorings_ ne préservent pas totalement le comportement lorsque le code s'appuie sur des comportements « limites » de Java. Cependant il est possible que ce soit bien le comportement désiré par le programmeur. C'est pourquoi il est recommandé de toujours relire les _refactorings_ automatiques avant des les committer dans le gestionnaire de sources. Vous utilisez bien un gestionnaire de source, n'est-ce pas ? ###De la difficulté d’écrire des outils automatiques dans un monde subjectif### Après avoir mis ce gros bémol, je tiens à signaler que j'essaie d’avoir un résultat final propre et logique. Je suis pragmatique : soit j'essaie absolument de préserver le comportement du programme, et dans ce cas, je ne fais presque rien ; soit je prends des risques « mesurés » en ignorant les cas « bêtes » comme celui-ci dessus, et alors j'autorise beaucoup plus de possibilités de _refactorings_. C'est un équilibre dur à trouver dans certains cas tel qu'illustré par la méthode [`isHardToRead()`](https://github.com/JnRouvignac/AutoRefactor/blob/master/plugin/src/main/java/org/autorefactor/refactoring/rules/SimplifyExpressionRefactoring.java#L163). Bien sûr, c'est éminemment subjectif, mais puisque c'est moi qui code, j'impose mes idées. :) Plus sérieusement, si vous pensez que j'ai tort, je suis ouvert à discussion. J'avais d'ailleurs pensé rajouter une préférence pour forcer à préserver le comportement, mais j'ai renoncé lorsque je me suis rendu compte du nombre de _refactorings_ que cela interdirait. Par exemple, un code aussi simple que celui-ci : ```java System.out.println("this is " + obj.toString()); ``` ne pourrait plus être réécrit comme ceci : ```java System.out.println("this is " + obj); ``` En effet, on perdrait la vérification sur référence nulle. Si `obj` est `null`, la première écriture lève une `NullPointerException`, alors que la deuxième écriture imprime "`this is null`". Bien sur, il serait peut-être possible de détecter qu'`obj` n'est pas `null` en faisant une analyse du flot de contrôle (j'ai bien dit peut-être), mais cela nécessite des outils dont je ne dispose pas encore. Par pragmatisme, j'ai simplement considéré que s’appuyer sur un appel à `toString()` pour détecter une référence nulle était une mauvaise pratique dans ce cas, et j'ai quand même écrit la règle qui fait cette réécriture :) ##Futur## Même si tous les _refactorings_ actuellement implémentés font du filtrage par motif, j’espère bien parvenir un jour à faire de l'analyse de flot de contrôle en construisant un [[graphe de flot de contrôle]] \(_CFG_ an anglais). Ceci permettrait d’écrire des _refactorings_ comprenant les chemins d’exécution du code, comme le ferait un développeur qui lirait du code. En particulier, il deviendrait possible de réduire la portée des variables, comprendre quels chemins d’exécution du code sont morts (impossibles à atteindre) J'aimerai aussi développer une autre fonctionnalité : l'extraction (semi-?)automatique de méthodes. Voici un exemple de code que je vois trop souvent : ```java // ... quelques dizaines / centaines de lignes de code boolean found = false; for (String s : strings) { if (toFind.equalsIgnoreCase(s2)) { found = true; break; } } if (found) { // faire quelque chose } // ... quelques dizaines / centaines de lignes de code ``` Comment simplifier cette longue méthode ? En extrayant une nouvelle méthode, comme ceci : ```java // ... quelques dizaines / centaines de lignes de code if (containsIgnoreCase(strings, toFind)) { // faire quelque chose } // ... quelques dizaines / centaines de lignes de code } private boolean containsIgnoreCase(Collection strings, String toFind) { for (String s : strings) { if (toFind.equalsIgnoreCase(s2)) { return true; } } return false; } ``` La plus grosse difficulté, c'est de savoir comment nommer la nouvelle méthode, et un humain doit intervenir, d’où le coté semi-automatique. Le support de Java 7 arrive doucement avec le support de _refactorings_ générant l'opérateur diamant '``' (pas de besoin de répéter les types génériques), et le _multi-catch_ (pas besoin de copier-coller le même bout de code dans deux blocs _catch_ différents). Par exemple, le code suivant : ```java List strings = new ArrayList(); try { // plus de code } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } ``` Peut être réécrit comme ceci en Java 7 : ```java List strings = new ArrayList(); try { // plus de code } catch (IllegalArgumentException | NullPointerException e) { e.printStackTrace(); } ``` La version 1.1.0 à venir a beaucoup de bonnes et nouvelles choses disponibles. Je vous conseille de l'installer depuis les _nightlies_. #Appel à contributions# J’espère que la présentation de ce greffon vous aura intéressé. Si vous êtes un développeur Java, je vous encourage à l'installer : - Depuis l'_update site_ http://autorefactor.org/releases/ - Depuis l'Eclipse marketplace: - **Pour les aventureux, installez une nightly** depuis l'_update site_ http://autorefactor.org/nightly/ Si vous êtes vraiment enthousiasmés et que vous voudriez l’améliorer ou l'adapter à vos besoins, n’hésitez surtout pas ! Envoyez des rapports de bugs, des _pull-requests_, interagissez sur gitter, etc. Si vous voulez aider, mais que vous êtes à court d’idées, voici une petite liste : * Testez le logiciels sur de grosses bases de code anciennes. C'est là où se cachent les surprises. :) * Je suis aussi intéressé par des retours d’expériences, blog posts ou autres... Faites connaître ce greffon auprès de vos collègues ! * [J'ai plein d’idées dans le _bug tracker_](https://github.com/JnRouvignac/AutoRefactor/issues), mais pas assez de temps pour les implémenter. Si jamais le cœur vous en dit... * [Le site web est moche, et a besoin d'amour](http://autorefactor.org) * [Le greffon n'a pas encore de logo malgré mes misérables tentatives](https://github.com/JnRouvignac/AutoRefactor-Logo). Je m'amuse bien à réécrire le code automatiquement. Franchement, ce n'est pas trop difficile ou trop long d'ajouter de nouvelles règles, alors n’hésitez pas à vous lancer ! Je vous aiderai.

AltStyle によって変換されたページ (->オリジナル) /