…ou la descente aux enfers d’une application JSF, RICHFACES, SPRING

Voici un retour d’expérience sur un événement projet comme on est appelé à en rencontrer beaucoup sur le cycle de vie d’une application, notamment lors de la maintenance, les enseignements à en tirer sont nombreux.

Il s’agissait de migrer de version une application développée en JSF1.2/RichFaces de navigateur Internet Explorer 8 (IE8) vers IE9.

Pour avoir passé il y a peu la redoutable montée de version de IE6 vers IE8, l’équipe en charge du développement, parfaitement rompue à ce contexte technique, avançait avec confiance sur ce sujet. Tout au plus devait-il s’agir d’ajustements cosmétiques isolés…. Mais ce petit battement d’aile de papillon a provoqué une belle tornade sur toute l’application, dont voici les détails.

Cadre technique: l’application en question est conçue selon une architecture multicouche avec les librairies & versions suivantes, qui ont leur importance pour la suite des événements :

dépendances JSF, Spring, RichFaces, JUnit

Après un premier test rapide, les comportements Ajax qui sont massivement utilisés par RichFaces ne fonctionnent pas sous IE9. C’est étonnant à double titre :

  • Les appels ajax sont motorisés par jQuery dont la portabilité est un gros point fort,
  • Les développements jQuery faits par ailleurs ne présentent aucune régression

RichFaces fonctionnant en ‘boîte noire’, il a été décidé de migrer en version 4.0, réputée compatible avec IE9.

Dépendances JSF, RichFaces, Spring, Junit 2

Des changements en cascade

Comme représenté ci-dessus, RichFaces 4 ne se branche que sur du JSF 2.0 : ce sera donc notre première étape de montée de version collatérale.

Puis s’enchainent les étapes suivantes :

  • JSF2.0 impose Spring Web Flow 2.3
  • Spring Web Flow nécessite Spring (Core & Security)>=3.0 et Spring Faces >=2.3.0
  • Spring 3.0 nécessite de passer JUnit en 4.9 ; en effet, le chargement du fichier de contexte applicatif bloquait lors des lancements de test.

En arrivant à la situation où les briques au cœur de notre application ont été remplacées… ainsi que celles qui sont plus ‘périphériques’ :

Dépendances JSF, RichFaces, Spring, JUnit 3

Il faut surtout se figurer que chacune de ces montées de version a provoqué de difficultés :

  • Bugs notoires ( a4j :commandLink non opérationnels dans Spring faces 2.3.0) : application d’un patch ou bien attente de la version corrective ? c’est selon vos priorités.
  • Changement de configuration xml (faces-config.xml, web.xml, tous ces fichiers au cœur de l’application sont impactés),
  • Incompatibilité de versions de servlet avec Tomcat,
  • Modification de certaines balises JSF : impact sur toutes les JSPs,
  • Perte des espaces de nom sous Eclipse (entrant en confilt au niveau des xsd préalablement utilisés : Une montée de version du plugin spring d’eclipse peut corriger ces problèmes
  • …et encore bien d’autres plus ou moins intrusives

De plus, ces incohérences étaient parfois à l’origine de problèmes rencontrés seulement à l’exécution du serveur au travers d’exceptions NoSuchMethodException et java.lang.UnsupportedOperationException pour lesquelles la difficulté était de trouver la librairie en cause. Elles survenaient au chargement de l’application ou bien au détour d’une page, ce qui exigeait de faire des tests exhaustifs.

Voici quelques conclusions que nous pouvons en tirer :

1) Basique : IE9 et RichFaces 3.3.3 ne font pas bon ménage !

2) Architecture : les applications composites sont une approche rationnelle pour tirer profit des innombrables librairies adressant chacune au mieux le rôle pour lequel elles ont été créées. Si elles mettent effectivement à disposition une grande richesse de fonctionnalités, elles peuvent se révéler instables quand on doit en modifier un élément technique.

3) JSF (et autres frameworks Web à forte cohésion avec le serveur): la structuration d’applications JSF est intrinsèquement orientée serveur (règles de navigation, de contrôle, cycle de vie des Beans…) et de ce fait entraîne des changements profonds dans les cas problématiques comme celui-ci. Les pistes désormais explorées vers une séparation plus nette entre client Web et un serveur orienté REST permettra de clarifier certaines pratiques et alléger la responsabilité du serveur dans la génération de l’interface utilisateur.

4) Gestion des dépendances : engagés dans une application composite, il faudra couvrir les versions parfois conflictuelles de librairies, d’implémentations. Même outillée avec Maven, cela reste difficile quand c’est à l’exécution, avec des erreurs absconses, que cela se caractérise.

5) Tests : sur une application riche fonctionnellement, qui va traverser malgré elle quelques générations techniques, un portefeuille de tests automatisés sera le garant d’une bonne maîtrise de la non régression (Unitaires, Fonctionnels) ; Dans ce cas présent, l’application dispose d’un riche panel de tests JUnit à connotation ‘Intégration’, faute de scripts Selenium exhaustifs (seuls quelques uns on été créés).

6) Projet : De la difficulté récurrente de connaître l’impact d’une évolution, même sur un environnement technique et fonctionnel bien maîtrisé.

7) Open source : à la fois riche et ouvert, il peut se rendre problématique pour des demandes de support. Nous sommes dans le cas de communautés très importantes, très réactives, ce qui facilite la prise en compte des anomalies / évolutions ; mais cela sera dommageable sur des librairies de niches, peu documentée ou suivies…. La pérennité est à prendre en compte.

« Jeter un regard objectif sur la qualité du code de l’application, pour en évaluer le niveau de maintenabilité »

Le contexte du projet

Une application connait plusieurs vies : la conception, la définition des principes d’architecture, l’implémentation, la mise en production du projet, puis la maintenance.
On définit classiquement en début de projet les éléments d’architectures logicielles, le rôle des différents composants, packages ou sous-projets et les bonnes pratiques à appliquer.

De manière très schématique, le projet aboutit à une version stabilisée lors de la première mise en production, avec une équipe ayant une connaissance métier et technique importante pour avoir initié et réalisé cette première phase. Mais quand vient la période des premières maintenances correctives et évolutives, l’équipe en place a été réduite ou remplacée, et les nouvelles réalisations doivent le plus souvent être réalisées par une équipe qui ne possède pas ou incomplètement la « mémoire » du projet.

A cela s’ajoute un phénomène classique lorsque le projet a connu une évolution conséquente de son périmètre fonctionnel lors de la réalisation :

  • Contraint par les délais, la date de livraison n’est pas modifiée,
  • L’équipe est sous pression pour livrer l’application à temps en jonglant entre : la couverture fonctionnelle, la maîtrise technique et la documentation,
  • Les phases de recette et les dispositifs consacrés à la qualité (lecture croisée du code, exécution et analyse de la qualimétrie, documentation) sont sacrifiés au profit du respect du jalon.

Dérive Coût Qualité Délai

Les audits de codes servent la viabilité du projet et peuvent être menés :

  1. Périodiquement pendant les développements pour surveiller la stabilité du niveau de qualité,
  2. A des moments clés : transmission de responsabilité du projet à un nouveau fournisseurou à une nouvelle équipe, passage du mode projet en mode maintenance, opération de benchmark.

La finalité est d’éviter ce que l’on rencontre le plus souvent sur des projets nés dans la difficulté (et ils sont nombreux…) et dont la maintenance a été chaotique : un mille-feuille de code instable, où l’on détecte les interventions de multiples intervenants qui impriment leur approche personnelle du codage, des normes, et leur interprétation des règles métier.

Les enjeux de l’audit de code

Lorsque nous initions un audit de code complet, nous nous attachons à le mener selon :

  • 1. Un axe ‘Architecture technique globale’ où nous étudions le découpage en sous-projets, en couches techniques, l’industrialisation et le respect dans le temps des normes et méthodes en vigueur ;
  • 2. Un axe métier, en sélectionnant un échantillon de fonctionnalités critiques qui sont représentatives de l’application :
    • De par la complexité et le nombre des règles de gestion impliquées,
    • De par la fréquence d’utilisation de la fonctionnalité lors de l’activité normale des utilisateurs,
    • De par la spécificité liée au métier : forte transversalité (comme la gestion d’un workflow), forte réutilisation attendue ;
  • 3. Un axe ‘outillage’ reposant sur des mesures automatisées avec un usage pondéré et personnalisé des outils :

Quelques généralités :
L’audit de code ne doit pas être dogmatique et il est nécessaire de savoir faire la part des choses exigibles selon la typologie du projet : complexité, criticité pour les métiers, ou en fonction des jalons dans la vie du projet (début, maintenance, fin).

Audit par échantillonnage fonctionnel

A partir des fonctionnalités identifiées, on vérifie que le couple « documentation + code » nous fournit les points d’entrée et la lisibilité nécessaires pour une activité de maintenance :

  • Nécessité d’identifier où se trouvent les fonctionnalités, comment elles sont organisées ;
  • Nécessité d’avancer en confiance dans le code en sachant comment les traitements de validation, contrôle, métier, ou persistance sont implémentés. A ce titre, il est impératif de vérifier que les règles de nommage sont homogènes et pertinentes et que la Javadoc est correctement renseignée.

Diagnostic audit d'échantillon fonctionnel

L’équipe d’audit détecte ainsi les forces et faiblesses du code de l’application sur les pans les plus sensibles et qui seront les plus concernés par la maintenance évolutive, laquelle pourrait apporter une certaine instabilité en cas de non maîtrise des fondamentaux techniques et fonctionnels.

La prochaine évolution sera-t-elle la carte qui provoquera la chute de ce qui tenait en équilibre jusqu’à présent ?

Audit par outillage

Cette approche permet de couvrir l’exhaustivité de l’application( avec certes moins de discernement que par échantillonnage), et de relever toutes les non conformités existantes avec différents niveaux de criticité : informatif, mineur et majeur.

En Java, les outils FindBugs et Checkstyle sont, par exemple, une première étape nécessaire pour révéler l’état de santé globale de l’application au regard des bonnes pratiques de codage.
On peut juger de manière globale chacun des domaines suivants :

  • Complétude de la JavaDoc,
  • Bonnes pratiques de codage (robustesse: default dans un switch, gestions des cas aux limites, des valeurs nulles),
  • Complexité du code : respect des tailles (nb de lignes par classe, par méthode, …), imbrications de boucles, lisibilité,
  • Duplication de code,
  • Règles de nommage
    • Pour plus de pertinence, l’équipe procède à une exécution par passes successives pour:

      • sélectionner les critères et calibrer les seuils,
      • avoir une vue plus précise en écartant temporairement les éventuels métriques tombés en alerte.

      Résultats synthétiques de l'audit avec CheckStyle

      En croisant les informations avec l’audit par échantillonnage, on dispose d’un maillage dense entre :

      • Le type de non conformités rencontrées,
      • L’homogénéité (ou non) de la répartition de ces manquements aux bonnes pratiques.

      Pour aller plus loin dans le détail et pouvoir juger dans la durée de l’évolution de la qualité du projet, nous utilisons InfoScope Source. Ce produit nous permet de donner une profondeur à plusieurs dimensions à notre audit technique :

      • 1. Une dimension technique
        InfoScope Source offre la possibilité d’ajouter / enlever des métriques, de définir des seuils de gravité.
      • 2. Une dimension de composants auscultés
        On observe au travers de l’outil les notes depuis une vue globale, par Projet, puis en profondeur :
        Projet > Package > Classe > Méthode

        A chaque niveau son tableau de bord récapitulatif.

      • 3. Une dimension temporelle
        InfoScope Source offre un suivi des tendances de la qualimétrie d’un projet dans le temps pour observer les progrès ou dérives.

      Stabilité parfaite de la qualité entre deux mesures

      Le résultat est une restitution navigable dans l’outil qui donne à chaque niveau le degré de maintenabilité du domaine observé ainsi que le détail des non-conformités entrant en jeu.


      Exemple de restitution exploitant Infoscope Source :

      Les problèmes se concentrent essentiellement dans les packages ‘services’ du projet. Cependant, ces classes sont notées très négativement et déprécient de ce fait la note globale. De plus, elles regroupent l’essentiel des lignes de code du projet et des règles métier, ce qui leur donne encore plus de poids dans la note finale.
      Les non-conformités les plus rencontrées sont :
      i. Non respect de la syntaxe des boucles ‘if’.
      ii. Complexité cyclomatique (imbrication de règles fonctionnelles, de boucles de traitement, de conditions) importante, voir extrêmement élevée.
      iii. Nombre de méthodes et du nombre de lignes par méthode non maîtrisé
      iv. Taux de commentaire insuffisant (notamment sur les classes les plus volumineuses et les plus importantes fonctionnellement)

      Les pré-requis pour mener un audit

      Les membres de l’équipe doivent disposer d’un solide bagage technique, c’est évident. Non seulement il faut avoir connaissance des bonnes pratiques générales pour pouvoir juger du code, mais il faut encore avoir travaillé sur suffisamment de projets et d’environnements pour comprendre les spécificités techniques et les choix d’architecture pour prendre le recul nécessaire.

      Il faut également connaître la façon dont fonctionne une équipe de développement, en mode projet ou en maintenance, pour identifier :

      • 1. Les raisons des dérives qui seraient observées,
      • 2. Pour caractériser la gravité de certaines non-conformités en termes d’impact sur l’organisation,
      • 3. Planifier de manière argumentée et priorisée les actions correctives.

      En sortie d’audit…

      L’équipe consolide les informations au travers de préconisations organisées selon leurs priorités : gravité de la non-conformité, ou impact potentiel sur l’application.
      On peut citer à titre d’exemple :

      • Renforcer la couverture de tests unitaires (en ciblant préférentiellement les classes au cœur de l’application),
      • Harmoniser et compléter la documentation de façon pertinente,
      • Respecter absolument l’inclusion des traitements cohérents dans une seule et même transaction (ACID),
      • Réduire les points et anomalies relevés par FindBugs et CheckStyle.

      Les équipes de maintenance disposent alors des éléments nécessaires pour faire vivre et évoluer l’application dans les meilleures conditions de sécurité, de coûts et de productivité.

      Enfin, Il est préconisé de mener ces audits de façon régulière, avant que la dérive n’exige un travail de mise à niveau trop conséquent et finalement décourageant.

Real Object-Oriented

ROO, c’est un générateur d’application JEE en mode ligne de commande, qui veut offrir une approche inspirée des meilleures pratiques retenues par Spring et offrir une grande productivité. Son positionnement en fait un candidat sérieux aux outils de mise en place technique d’un projet.
(Remarque : La version utilisée dans cet article est la 1.1.2.)

Console Roo sous Eclipse

La console ROO sous Eclipse (STS)

Principes généraux

L’architecture est structurée selon les principes suivants :

  • Concision du code, par l’usage intensif d’aspects (avec AspectJ) ;
  • Affranchissement de la couche DAO, les instructions de persistance étant rattachées aux entités
    • on appelle par exemple Utilisateur.findUtilisateur(id)
    • ou bien utilisateur.persist();
  • Découplage du code généré et du code modifié, pour continuer à utiliser les lignes de commande après implémentations des spécificités du projet ;
    • Le code généré est maintenu dans les classes d’Aspect,
    • Le code personnalisé est intégré aux .java à la main du développeur.
  • Ouverture technologique, grâce à un portefeuille de plugins richement doté, et appelé à être complété au vu des développements en cours (JSF 2.0, JAXB,Vaadin,Wicket…),
  • Approche Top-Down du modèle : la génération de la base de données se fait à partir de la configuration JPA des classes métier.

Un portefeuille d’extensions riche

En termes de librairies et d’outillage, c’est du classique quand on est dans l’écosystème Spring :
Maven, JPA, Spring MVC+ Security ,…
Les bases de données couvertes sont: DB2, ORACLE, MYSQL, H2, POSTGRES…, et dernièrement, Google App Engine !

Certains choix techniques sont en cours de changement, Dojo est appelé à être remplacé par jQuery dans la version 1.1.3.

Pour ce qui est de la persistance, le choix est donné pour l’implémentation de JPA : Hibernate, EclipseLink, DataNucleus…

De même, pour la présentation, on peut choisir entre Spring MVC associés à des JSPs ou bien GWT.

Prise en main & Utilisation

L’intégration dans STS (déclinaison d’Eclipse à la sauce Spring) est tout à fait opérationnelle, et même sympathique, grâce à une complétion conviviale et un accès à l’historique des commandes.
Le cloisonnement entre le code écrit et celui généré en lignes de commande est particulièrement efficace, sur les parties Modèle et Contrôleur.

Enrichissement de la classe modele

Enrichissement de la classe modele : JSON et JPA

Ce cloisonnement est plus sensible pour le code des tests en selenium ; on gardera à l’esprit qu’il vaut mieux demander à Spring ROO de générer les scenarios de tests après avoir stabilisé le développement du modèle.

Comportements relevés au fil de l’utilisation de ROO

Concernant GWT, le plugin nous a apporté quelques désagréments…. ayant conduit à l’abandon de cette approche, pour l’instant tout du moins; du code GAE est généré systématiquement, à tort (correction attendue en 1.1.3).

De plus, Il faut noter que l’approche par AspectJ limite fortement la pertinence des tests de couverture, nous n’avons pas trouvé à ce jour le moyen de faire passer Cobertura dans les aspects… et ils sont nombreux avec Spring ROO.

Certaines extensions sont seulement intégrées pour amorcer les développements, notamment l’utilisation de Spring Web Flow, qui est un simple outillage du projet pour amorcer des développements à la main.

En revanche, l’approche REST associée à la génération simplifiée de code JSON (basée sur flexjson), plaide beaucoup en faveur de ROO quand il s’agit de mettre rapidement en place un backend de données, pour vos développements Mobile par exemple.

Utilisation de Selenium

L’outillage du projet avec selenium est intéressant. On pourra toujours gloser sur l’intérêt de tester ‘fonctionnellement’ des écrans qui sont surtout d’ordre ‘gestion de tables de référence’, mais on dispose en l’état d’une première brique opérationnelle pour notre campagne de tests :

Commande Roo Selenium

Ajout de scripts Selenium à un contrôleur Spring MVC

Que l’on lance dans la fenêtre de commande avec :
mvn selenium :selenese
A noter que si on ajoute un champ dans l’entité Appartement, le script selenium n’est pas impacté et l’exécution suivante tombe en erreur…. Il vaut mieux construire ses tests en fin de cycle, autant que possible.

De plus, les données en base ne sont pas nettoyées à chaque cycle. il faudra introduire un mécanisme de nettoyage ou de remise à niveau de notre jeu de test. On a l’assurance de pouvoir intégrer sans efforts les scripts générés grâce au plugin Selenium de FireFox dans le portefeuille de tests du projet ; mais dans ce cas, nous avons commencé à basculer vraiment vers le cœur de notre projet.

Quelques avertissements et précautions

ROO souffre encore de certains défauts de jeunesse qui, sans remettre en cause son utilisation, doivent conduire à prendre quelques précautions :

  • Les tests unitaires plantent à l’exécution (ceux de type ‘tests mock’) ;
  • Les relations ManyToMany ne sont pas correctement intégrées dans les JSPs ;
  • L’annotation @NotNull a provoqué des plantages d’écrans spectaculaires ;
  • Et quelques autres…

Mais la communauté est active et les versions se succèdent à un rythme soutenu.
On bénéficie en tout état de cause d’un référentiel de bonnes pratiques.