Skip to content

Latest commit

 

History

History
604 lines (456 loc) · 26.8 KB

chapitre 17.adoc

File metadata and controls

604 lines (456 loc) · 26.8 KB

Chapitre 17

Nos recommandations

L’activité de noubliepaslalistedescourses ne cessant de croître, nous augmentons régulièrement la taille des équipes chargées des développements, de la maintenance de notre application et de ses dérivés (voir le Chapitre 14). Avec une dizaine de nouvelles recrues lors du dernier trimestre, il devient nécessaire d’organiser et d’accélérer le passage de connaissances. C’est Nicolas qui prend désormais en charge l’accueil des nouveaux et organise régulièrement des formations pour que les équipes aient un niveau minimal homogène. La partie des formations réservée à Maven se veut minimaliste mais efficace. Le but n’est pas de faire de toute l’équipe des experts capables de développer des plugins, des nouveaux packagings ou encore de déboguer au besoin l’outil. Ce que nous souhaitons, c’est que tous soient à l’aise avec son fonctionnement et qu’ils suivent nos recommandations afin d’éviter les écueils que nous avons déjà pu rencontrer.

Les bonnes bases

MangaNicolas

Bienvenue pour cette session de formation Maven 101 – essentials. L’objectif de cette formation n’est pas de vous transformer en gurus de Maven, connaissant la moindre de ses ficelles. Honnêtement, ça ne vous apporterait pas grand-chose. Ce que nous voulons, c’est faire de vous des développeurs efficaces, à l’aise avec cet outil que vous allez utiliser au quotidien. Nous voulons surtout vous empêcher de partir sur de mauvaises pistes et de finir, dans quelque temps, par regretter d’avoir choisi Maven. Au contraire, nous allons vous donner les clés pour en faire un allié de poids dans vos développements et dans l’industrialisation de votre travail d’ingénierie.

D’après nous, le meilleur moyen pour vous éviter à l’avenir de mettre le pied sur une peau de banane, c’est de vous les montrer tout de suite. La plupart des formateurs montrent de beaux exemples bien ficelés qui collent parfaitement à la technologie dont ils vantent les mérites. Si c’est ce que vous cherchez, jetez un œil à notre application blanche, c’est un chef-d’œuvre du genre. Nous allons ici faire un tour des embûches que nous avons rencontrées en utilisant Maven sur de nombreux projets.

Voici donc les 10 commandements de l’utilisateur de Maven.

Commandement no 1 : Les conventions de Maven tu suivras.

Maven propose ses propres conventions mais ne les impose pas. Un projet que nous avons fait migrer sous Maven utilisait comme répertoire de sources le chemin src/java et pour les tests test/java. Les POM ont donc été adaptés pour coller à cette convention.

Nous avons perdu un temps précieux à reconfigurer de nombreux plugins, qui ont la mauvaise idée d’utiliser le chemin src/main/java en dur et pas la variable $\{build.sourceDirectory}. Notre POM ne gagne pas en lisibilité, et c’est cher payé pour un petit caprice esthétique.

De la même façon, ne considérez jamais les conventions comme acquises. Utiliser le chemin /target/classes pour indiquer le répertoire de compilation du projet, c’est potentiellement s’exposer à un dysfonctionnement. Nous en avons fait la mauvaise expérience en configurant notre application pour utiliser Sonar (voir le Chapitre 12). La convention pour ce chemin est portée par la variable $\{project.build.outputDirectory}. C’est un peu plus long à écrire, mais c’est une garantie d’homogénéité des métadonnées du projet.

Le respect des conventions permet :

·     de simplifier de l’utilisation de Maven ;

·     de simplifier l’intégration de nouveaux développeurs ;

·     d’éviter de tomber dans des problèmes ou des bogues qui font perdre un temps précieux.

Commandement no 2 : Simplicité tu choisiras.

Notre premier mauvais élève est un projet que nous avons voulu faire migrer d’Ant vers Maven. Les technologies en œuvre avaient toutes le plugin Maven adéquat, mais la structure initiale du projet était particulièrement hétéroclite. Notre erreur a été de vouloir la conserver telle quelle, ce qui imposait :

·     plusieurs étapes de compilation entre divers répertoires de sources interdépendants ;

·     plusieurs phases d’instrumentation du code ;

·     une exécution séparée pour le calcul du taux de couverture des tests ;

·     un assemblage de multiples sous-versions du même projet, incluant diverses options et le code optionnel associé.

Inutile de dire que la migration vers Maven a été laborieuse et pas du tout convaincante, tant que nous n’avons pas pris la décision de revoir fondamentalement la structure du projet : des modules simples, ciblés sur une technologie ou sur un domaine fonctionnel précis, et répondant à une logique de construction standard.

Il existe quelques projets qui refusent d’utiliser Maven, sous prétexte qu’ils nécessitent d’innombrables lignes XML pour obtenir le résultat attendu, lorsque c’est possible. Spring 2 en est un exemple, le framework étant toujours construit avec le bon vieux Ant. Ce n’est pourtant pas une fatalité, et cela a été démontré dans Better Builds With Maven (disponible librement en ligne), qui propose un POM permettant de construire Spring sans acrobaties particulières.

Les développeurs de Spring sont-ils tordus ? Non ! Par contre, ils ont fait des choix qui vont à contresens des préconisations de Maven. Par exemple, Spring est disponible à la fois comme un unique JAR et comme un ensemble de sous-bibliothèques spécialisées. Ensuite, le cœur de Spring 2 est compatible à la fois Java 1.3 et Java 5, ce qui nécessite une double compilation puis le regroupement du résultat dans une unique archive JAR.

Bien que Spring ait récolté un grand succès pour ses qualités techniques, les structures de son code source et de son script de compilation le rendent inutilement complexe. Après tout, si vous travaillez sur Java 5, vous pouvez très bien déclarer une dépendance vers spring:2.0.8:tiger[68] à la place de spring:2.0.8. Les dépendances transitives feront le reste.

La morale de cette histoire, c’est qu’il ne faut pas chercher à plier Maven à des besoins complexes mais plutôt essayer de comprendre comment traiter nos besoins selon la philosophie de Maven. Autant Ant permet de faire à peu près tout et n’importe quoi, autant Maven suppose qu’on adhère à sa logique pour en tirer tout le bénéfice.

Des projets comme Alfresco ou Liferay ne s’accommodent pas facilement de Maven. Il faut prendre le temps d’analyser les besoins et d’organiser au mieux le projet pour être efficace.

Commandement no 3 : Au fur et à mesure de tes besoins, les outils nécessaires tu mettras en place. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

La pléthore de plugins d’analyse de code disponibles nous encourage à mettre en place un suivi qualité de haut niveau. Il est si simple de lancer l’un de ces nombreux outils pour analyser le code et identifier des problèmes automatiquement – il est juste dommage qu’il ne les corrige pas aussi automatiquement !

Nous avons ainsi pris un projet récemment adapté à Maven et produit quelques mégaoctets de rapports variés. Mais que faire de ces pages et des indicateurs en tout genre que nous obtenons ? Comment traiter des règles parfois incompatibles ou, en tout cas, dont nous ne voyons pas forcément l’intérêt ?

Si nous utilisons les règles par défaut de Checkstyle, établies sur les recommandations de codage SUN – qui datent des débuts de Java –, celui-ci nous insultera pour chaque méthode qui ne dispose pas de commentaire Javadoc. Issue d’une époque révolue, cette règle part du principe que tout code public doit être accompagné d’un commentaire explicatif. Elle va nous amener à écrire ce genre de chose :

/**

 * affecte le prenom

 * @param prenom le nouveau prenom

 */

public void setPrenom( String prenom ) \{ …​

Voilà un magnifique commentaire dont l’utilité est sans appel ! Soit vous générez l’ensemble des commentaires pour satisfaire Checkstyle, soit vous définissez une règle qui préconise la forme plutôt que la pertinence du commentaire ou la clarté du nom de la méthode ! Sincèrement, tout développeur préférera, même sans commentaire Javadoc, une méthode nommée :

resilierContrat( long idContrat ) throws ImpayesEnCoursException 

Plutôt qu’une méthode équivalente utilisant un vocabulaire obscur, mais largement accompagnée de commentaires et qui satisfait nos outils d’analyse :

/**

 * Résiliation du contrat

 * @param l l’identifiant de contrat

 * …​

 */

resCtr( long l ) throws ResErrorException 

Choisir des règles de développement est une tâche qui nécessite une culture et un historique de mise en œuvre du langage. La tendance est aux noms de méthodes et de variables clairs, explicites, quitte à faire long – les écrans 16:9 ont probablement aidé à cette évolution. Fini le code sur 80 colonnes ou l’utilisation des tabulations !

Utiliser l’outillage que Maven met à notre disposition n’est pas une fin en soi. Ce doit être l’aboutissement d’une démarche qui définit les règles que nous voulons vérifier ou les outils que nous voulons mettre en œuvre. Introduits sur le projet un par un, ces outils seront bien perçus et leur plus-value reconnue. Imposés tout d’un coup sans explication, ils seront vécus comme une contrainte inutile et deviendront vite inutilisés, voire contre-productifs.

De la même façon, installer un serveur d’intégration continue n’a de sens que pour des équipes déjà familiarisées avec l’outillage de tests automatisés et la pratique du développement dirigé par les tests, et sensibilisées au surcoût d’un projet dont le code est instable.

Inutile donc de barder notre POM de plugins en tout genre et de déclarations sans fin, juste parce que le format du fichier le permet. Si personne ne les exploite ou ne sait comment en tirer parti, ce sera du temps perdu, un travail contre-productif. Maven n’est qu’un moyen qui nous aide à mettre en œuvre les bonnes pratiques actuelles du développement de logiciels. Il n’est en aucun cas une solution magique. Il permet de mettre en place différentes stratégies de tests (unitaires, d’intégration…) mais il ne le fera jamais à la place de nos équipes. Elles seules peuvent s’assurer que les bonnes pratiques sont suivies.

Commandement no 4 : De la sur-conception point tu ne feras.

L’application maxiMaousse[69], elle aussi basée sur Maven, comprend 58 modules ! Elle suit une décomposition en couches techniques ainsi qu’un redécoupage en modules fonctionnels. Une modification fonctionnelle touche ainsi rarement plus d’un ou deux modules, réduisant – théoriquement – le travail de non-régression.

En pratique, cette application est ingérable dans notre IDE en raison de l’avalanche de modules. Les évolutions touchent rarement un seul module, et les interdépendances sont nombreuses. La construction du projet sur un poste de développement incluant les tests unitaires et de qualité devient un vrai cauchemar, elle est surtout contre-productive et l’identification du code un vrai casse-tête. Certains modules ne comptent que quelques classes ! Ici, on a visiblement confondu la notion de package et de module Maven.

La gestion multimodule de Maven est puissante, ce n’est pas une raison pour l’appliquer juste parce qu’elle existe. Nous ne créons un nouveau module que lorsqu’un nouveau besoin apparaît. Cela arrive déjà suffisamment assez vite : par exemple, pour répondre à une contrainte technique ou pour différencier deux modules qui travaillent sur des technologies différentes et dont nous voulons clairement scinder la gestion. Si la décomposition en modules fins peut avoir du sens pour une bibliothèque utilitaire, elle apporte rarement de la simplicité sur une application. Au mieux, on pourra découper celle-ci en fonction de ses couches techniques afin de permettre à des équipes de compétences différentes d’intervenir de manière plus isolée. Cependant, les modules resteront fortement dépendants ; aussi, pourquoi ne pas simplement utiliser des packages dédiés dans le même projet ?

Le seul cas pratique où la décomposition en modules peut apporter une certaine plus-value concerne la génération de code. Lorsqu’un projet est basé sur de nombreux services web, l’analyse des WSDL et la génération de code prennent du temps, même pour constater que le code généré est à jour. Pour ne pas pénaliser les développeurs sur leur poste, isoler ce code dans un sous-module peut être une bonne idée. Après tout, on ne change pas de WSDL tous les matins !

Bref, les occasions de justifier le découpage d’un module en plusieurs modules sont nombreuses. Alors, n’en faites pas plus que nécessaire. Vous testerez le réacteur de Maven bien assez tôt.

Commandement no 5 : Tes outils et ton build à jour tu maintiendras.

Même si mettre en place l’outillage de développement n’est pas une fin en soi, c’est un mal nécessaire, au coût non négligeable, permettant d’offrir à l’équipe un environnement aussi agréable que possible et optimisé pour travailler. De très nombreux outils peuvent y figurer :

·     Maven, le programme en tant que tel mais aussi tous les plugins qu’il utilise ;

·     le gestionnaire de versions de sources (Subversion, Git…) ;

·     le gestionnaire de tâches et de bogues (Jira, Mantis…) ;

·     le repo (nexus, artifactory, archiva) ;

·     le serveur d’intégration continue (Hudson, Continuum, Bamboo…) ;

·     les outils de tests (Selenium, Fitnesse…) ;

·     l’environnement de développement intégré (Eclipse, NetBeans, Intellij…) ;

·     et bien d’autres encore.

Il est important que ces outils soient maîtrisés, mais il est encore plus important qu’ils soient mis à jour et que leur intégration soit poussée à son maximum. Celle-ci permet, par exemple, via le numéro du bogue placé dans le commentaire d’un commit sur le gestionnaire de sources, de faire le lien depuis le gestionnaire d’anomalies pour afficher les lignes modifiées par la correction. Ce genre d’intégration fait gagner beaucoup de temps pour la revue des corrections de bogues. Et les intégrations entre produits sont nombreuses. Celles unifiant tous les services au sein de l’IDE sont probablement celles qui amélioreront le plus la productivité.

Les mises à jour de chaque outil sont importantes. Prenez l’exemple de Maven. Sur un projet multimodule monstrueux provenant tout droit de l’ère jurassique (plus de 150 modules), Maven 2.0.10 mettait près de huit minutes rien que pour s’initialiser et générer l’ordre de construction des modules. Avec Maven 2.1.0 et supérieur, cela prend moins d’une minute. Même si ce cas est extrême (mais bien réel), il est représentatif des gains que l’on peut obtenir en faisant l’effort de maintenir à jour ses outils.

Les projets durent longtemps et les outils évoluent vite. Les maintenir à jour permet d’en obtenir le meilleur. Cela entraîne un coût récurrent mais qui est finalement vite rentabilisé par les gains de productivité de l’équipe.

Commandement no 6 : Dans un projet, la même version tous les modules auront. ^^^^^^^^^^^^^^^^^^^^^^^^^^

Sur un projet de messagerie, nous avons développé une couche complète d’abstraction autour de l’envoi/réception de messages, indépendante du fonctionnel. Nous avons voulu capitaliser dessus et la proposer à d’autres projets. Une fois ce code déplacé dans un module, il devenait exploitable sur une autre application qui profitait ainsi de nos longues heures de mise au point.

La réutilisation est un rêve de tout chef de projet. Seulement, lorsqu’une application nous a demandé d’utiliser ce module, s’est posé le problème de la gestion de sa version. Notre demandeur ne voulait pas utiliser un SNAPSHOT ; sa livraison étant prévue sous peu, il lui fallait un code stable. Notre code répondait à cette attente, mais étant lié à notre application, il partageait son numéro de version. Nous pouvions faire une livraison isolée de ce module, mais alors celui-ci référençait un parent encore en SNAPSHOT !

Nous avons donc dû figer notre POM parent dans une version 1, livrer le fameux module mutualisé en version 1.0.0 et continuer notre application en version 1.0.0-SNAPSHOT. À ce petit jeu, nous nous sommes rapidement retrouvés avec des versions dans tous les sens dans les POM des différents modules et l’impossibilité d’utiliser le processus de livraison détaillé au Chapitre 11.

La morale de cette histoire, c’est que les modules d’un projet devraient toujours partager la même version, sans quoi la gestion manuelle des numéros de version devient infernale. Le plus simple pour satisfaire ce besoin est de ne définir cette version que dans le POM parent du projet et dans les références <parent>. Toutes les autres références se font via la variable $\{project.version}. Ainsi, pas de risque de se tromper.

Pour déclarer du code comme bibliothèque commune, nous devons créer un nouveau projet Maven indépendant : POM dédié, gestion de version dédiée, gestionnaire de code dédié, etc. Une fois notre code utilitaire préparé pour être réutilisable ailleurs, sous forme d’un projet à part entière, il ne peut plus être considéré comme un élément de notre application, même si celle-ci devient un contributeur privilégié de ce composant commun.

Commandement no 7 : La gestion des versions tu centraliseras.

Dans un projet basé sur de nombreux modules, un travail vite pénible consiste à assurer la cohérence de versions des dépendances. Des plugins peuvent nous aider dans cette tâche, mais il existe une solution bien plus simple : le <dependencyManagement>. Cet élément, que nous allons ajouter dans le POM parent du projet, déclare pour chaque dépendance la version de référence à utiliser sur le projet. Dans les modules, nous déclarons alors les dépendances sans préciser de version, éliminant ainsi le problème.

Le <pluginManagement> permet de faire la même chose pour les plugins avec, en plus, la possibilité de définir une configuration centralisée, mais qui ne sera appliquée que sur les modules qui utilisent le plugin.

Info

Les plugins déclarés pour le reporting ne profitent cependant pas de cette gestion centralisée et doivent donc explicitement contenir un numéro de version. Il s’agit en quelque sorte d’un bogue de conception de Maven, mais le corriger supposerait de modifier le comportement général de Maven vis-à-vis de la gestion des versions. L’équipe de développement est très réticente à changer cette gestion qui peut avoir de lourds impacts sur les projets existants.

Commandement no 8 : Comme la peste les dépendances optionnelles tu éviteras. ^^^^^^^^^^^^^^^^^^^^^^^^^^

Nous avons créé une belle bibliothèque utilitaire commons-geegol dont l’objectif est d’apporter à tous nos projets des classes utilitaires, facilitant l’accès à de nombreux outils. Ce code dépend donc de très nombreuses dépendances, mais seule une sous-partie est utile pour un utilisateur, qui ne va exploiter que quelques classes de notre bibliothèque. Nous pouvons :

·     Déclarer toutes ces dépendances, auquel cas les utilisateurs vont nous huer, se plaindre que Maven télécharge des centaines de JAR inutiles et perdre des heures à configurer des <exclusions>.

·     Déclarer ces dépendances <optional>, ce qui les rend juste indicatives. Nos utilisateurs ne vont pas nous huer tout de suite, mais plus tard quand, lors de l’exécution, ils constateront qu’une dépendance manque.

La philosophie de Maven nous encourage à utiliser un module dédié pour chaque technologie ou outil que nous voulons supporter. Si cela veut dire avoir dix modules, ce n’est pas un problème. La gestion des dépendances et de la livraison étant automatisée, cela n’a aucun impact sur le temps passé par le développeur sur son travail, la seule chose qui compte au final. Par contre, nous gagnerons dans la finesse de nos métadonnées et dans la bonne décomposition de notre code.

Commandement no 9 : Les SNAPSHOT tu utiliseras.

Sur un projet comptant plusieurs (dizaines de) modules et de très nombreuses classes, il peut être pénalisant d’avoir tout le code accessible sous forme de projet dans l’IDE. Nous pouvons, par exemple, exploiter certains modules sous forme de SNAPSHOT, comme celui contenant le code généré de nos schémas XSD. Le conserver dans notre IDE n’apporte rien et pénalise l’intégration Maven qui va devoir sans cesse reconstruire ce code, ou du moins s’assurer qu’il est à jour – perte de temps que le développeur ressentira très nettement :

·     Attendre la compilation moins d’une seconde c’est fantastique, mais rarissime.

·     Attendre cinq à dix secondes, c’est le temps nécessaire pour voir qu’il se passe quelque chose et se reposer les poignets.

·     Attendre trente secondes que le projet compile, cela incite à affubler son IDE de noms d’oiseaux.

·     Attendre plus d’une minute à chaque construction, c’est s’exposer à une montée d’énervement, souvent accompagnée d’une augmentation alarmante de la consommation de café, qui ne suffit pourtant pas à expliquer le premier phénomène. Peut-on alors encore parler de productivité ?

En reposant sur les SNAPSHOT pour tous les modules dans lesquels nous ne faisons aucune modification et qui correspondent à du code évoluant très peu, nous allégeons d’autant le travail de l’IDE. Notre serveur d’intégration continue a le mérite de ne prendre ni pause ni café. Il peut construire pour nous les SNAPSHOT de nos modules au fur et à mesure qu’une construction réussie est atteinte.

Commandement no 10 : L’IDE toujours tu privilégieras.

Cela fait très expert de scruter la console et de taper à une vitesse folle des commandes incompréhensibles. Si vous envisagez un rôle dans une série américaine, pourquoi pas ? mais si vous voulez travailler confortablement et former rapidement vos équipes, cherchez plutôt de l’aide du côté de votre environnement de développement.

Trop souvent, nous perdons du temps sur les postes de développement suite à un comportement bizarre de Maven. Le cas typique se traduit par un appel à l’aide du type : "J’ai beau lancer des mvn clean install, project clean sous Eclipse, et build all, je n’arrive pas à démarrer mon serveur Tomcat à cause d’une NoClassDefFoundError."

Le but du développeur n’est pas de passer son temps devant la Matrice*[70*_], surtout en mode crypté (de toute façon, ça fatigue vite les yeux). Il faut toujours privilégier la productivité de l’équipe, sans quoi les belles méthodes et les outils préconisés seront vite oubliés dans le feu de l’action.

Nous avons vu au Chapitre 9 que cette intégration est déjà très correcte et progresse même rapidement sous Eclipse, qui est trop longtemps resté à la traîne. Apprenez à bien utiliser le support de Maven dans les IDE pour fournir à l’équipe un outil aussi transparent que possible. Les versions récentes de m2eclipse proposent, par exemple, la variable m2eclipse qui permet de différencier un build classique d’un build sous Eclipse. Un bon moyen de rendre l’IDE plus réactif est d’en profiter pour désactiver les étapes non indispensables de la construction du projet. Le Listing 16.1 montre l’activation d’un profil exclusivement en dehors de m2eclipse.

Listing 16.1 un profil pour éviter les plugins trop consommateurs sous m2eclipse

<profile>

  <id>not-m2e</id>

  <activation>

    <property>

      <name>!m2e.version</name>

    </property>

  </activation>

  <build>

  <!-- plugins trop consommateurs lors des builds m2Eclipse -→

  </build>

</profile>

Une autre option consiste à exploiter l’intégration avancée sous Eclipse que permet le mode incrémental de m2eclipse. Le Listing 16.2 montre une telle configuration pour associer le plugin adapté à la phase de recopie des fichiers de ressources. L’astuce consiste, lors d’un build m2eclipse, à utiliser la version SNAPSHOT du plugin de gestion des ressources (qui gère ce mode incrémental) et à activer le configurateur m2eclipse associé aux projets Java.

Listing 16.2 un profil pour activer le cycles de vie reconfigurable de m2eclise 0.9.9

<profile>

      <id>m2e</id>

      <activation>

        <property>

          <name>m2e.version</name>

        </property>

      </activation>

      <build>

        <plugins>

         <plugin>

           <groupId>org.maven.ide.eclipse</groupId>

           <artifactId>lifecycle-mapping</artifactId>

           <version>0.9.9</version>

           <configuration>

             <mappingId>customizable</mappingId>

             <configurators>

               <configurator id='org.maven.ide.eclipse.jdt.javaConfigurator'/>

             </configurators>

             <mojoExecutions>

               <mojoExecution>org.apache.maven.plugins:maven-resources-plugin::</mojoExecution>

             </mojoExecutions>

           </configuration>

         </plugin>

        </plugins>

        <pluginManagement>

          <plugins>

           <plugin>

             <groupId>org.apache.maven.plugins</groupId>

             <artifactId>maven-resources-plugin</artifactId>

             <version>2.4</version>

           </plugin>

          </plugins>

        </pluginManagement>

      </build>

    </profile>

Conclusion

En résumé, une règle simple : n’essayez pas d’aller contre Maven. Les conventions ont été choisies pour refléter les bonnes pratiques et des règles simples d’organisation. S’il suit une logique qui va contre vos objectifs, c’est que vous n’avez pas saisi son mode de fonctionnement. Soyez critiques sur l’organisation de votre projet et de votre code. Pourquoi Maven veut-il vous imposer tel mode de fonctionnement ? Vous arriverez sans doute à repenser votre structure pour quelque chose de plus simple, ordonné de manière homogène, et qui se plie mieux au mode de pensée de Maven. Au final, vos projets n’en seront que plus clairs et plus compréhensibles.