Ce retour d'expérience personnel est issu de plusieurs points de vue : de mon œil neuf que j'avais lorsque j'ai débuté et que j'étais "utilisateur du générateur", puis d'un œil plus expérimenté en tant que "concepteur de générateur" et en condensant les retours d'expériences des développeurs au sein de différents projets utilisant l'approche MDA.
Une idée du contexte : les DSL ne sont pas forcément Turing-complet mais, dans tous les cas, le générateur mixe code manuel et code généré (approche par balises comme défini dans l'article précédent). Les DSL sont dans tous les cas des modèles (je n'ai pas de retour d'expérience sur des DSL texuels). Ils sont indépendants ou basés sur une extension d'UML avec des profils.
Les cas limites du MDA
L'inexpérience du développeur
Le développeur débutant ne connait pas les enjeux liés à l'approche MDA.
Il développe en dehors des balises prévues à cet effet et remet en cause l'outillage MDA de lui avoir fait perdre tout son travail.
Il considère que le code généré est trop compliqué à lire et à modifier et préfère considérer sa propre approche.
La phase de conception peut également être problématique pour les développeurs qui veulent tout de suite "coder du technique". Le cas le plus typique étant les développeurs avec une vision bottom-up et qui souhaite modéliser la base de données sans vision fonctionnelle ( "Comment ça je ne peux pas modéliser une colonne de table du type CHAR(3) pour Oracle ?" ).
Finalement, il laisse tomber l'approche MDA.
Le temps (ou la paresse du développeur...)
Le développeur connait les intérêts de l'approche MDA, mais considère le round-trip trop consommateur en temps et préfère modifier le code sans passer par le modèle. En fin de projet, les délais sont serrés et le modèle devient moins prioritaire : seule la livraison compte !
La raison est simple : ajouter du code depuis l'IDE lui prend très peu de temps comparé au temps de round-trip : modélisation, export XMI, validation du modèle, génération, vérification de la validité du code. Sans compter le temps d'ouverture des outils, le temps de chargement (du modeleur et du générateur) qui s’avèrent non négligeable. La fréquence de round-trip s'avère donc incompatible avec des délais courts.
De plus, la plupart du temps, le modèle ne fait pas partie de la livraison. Refaire une étape de modélisation n'est donc pas jugée prioritaire.
En solution, le développeur ajoute un maximum de code manuel alors que ce code pourrait être intégré au modèle. Finalement, il se rend compte que le modèle n'est plus vraiment à jour, et que la resynchronisation modèle / code sera coûteux et ne sera donc fait qu'"après la livraison, quand on aura plus de temps". Traduisez par "Jamais".
La perte de code
Le développeur effectue des roundtrips réguliers, mais il s'aperçoit que certaines portions de code ne sont pas correctement regénérées et lui font perdre son code manuel.
Ce cas peut arriver pour plusieurs raisons :
- Les balises de code manuel peuvent être supprimées lors d'une réorganisation automatique du code par l'IDE.
Par exemple, les balises autour des zones d'import de fichier Java sont souvent effacées lors d'une "réorganisation des imports" sous Eclipse. Sous FlashBuilder 4, les zones d'imports des AS3 sont systématiquement supprimées. - Les balises peuvent être modifiées lors
d'un reformatage.
Par exemple, Eclipse formate les commentaires : les balises (qui sont des commentaires) sont modifiées et ne sont donc plus valides. - Une maladresse du développeur, une balise est modifiée et le code manuel n'est pas ré-intégré.
- Un refactoring qui n'est pas pris en compte par l'outillage : Comment gérer les refactoring de méthodes, de classes ?
Par exemple, les balises de code manuel pour les méthodes sont constituées de la signature de la méthode. En cas de changement de celle-ci, la balise est changée et le code perdu.
Autre exemple, pour les renommages de classes : Un classe Toto devient un fichier Toto.java. Lors d'un renommage de Toto en Titi, le code de la classe existante Toto.java n'est pas ré-intégré dans Titi.java. Titi.java est vierge de tout code manuel. Toto.java est toujours présent mais orphelin. - Certains fichiers entièrement générés ont besoin d'être customisés par le développeur. Le développeur modifie donc le code généré consciemment. Lors d'une regénération, le code sera perdu, mais le développeur récupérera les modifications dans le SCM et les ré-intégrera manuellement.
A ajouter également, le non déterminisme du générateur : Lors de génération successive, pour un même modèle, le code généré n'est pas dans le même ordre. Ce principe s'explique car la spécification du méta-modèle (UML en l’occurrence) n'ordonne pas certaines relations entre objets.
Là encore, il pourra remettre en cause l'approche MDA (notamment sur la fin des projets) comme étant problématique.
La conformité du code
Le développeur perd du temps à corriger des erreurs dans le code généré : erreur de compilation, erreur à l’exécution ou encore pertes de performances.
Suite à un audit de code, ou une validation de qualité, il est démontré que le code généré est la cause de perte de qualité du projet. Le code généré entraine donc une dette technique qu'il faudra prendre en compte.
Plusieurs cas peuvent être remontés :
- Le code généré ne compile pas ou contient des portions de code au comportement douteux provoquant des bugs;
- Le code généré fait baisser la note qualitative du code source de l'application. Il n'est pas formaté correctement, n'est pas commenté, il est difficilement lisible et donc difficilement maintenable.
Ces points à eux seuls ne sont en général pas des causes d'abandon du MDA, mais peuvent faire baisser l'image de cette approche et peuvent avoir un effet cumulatifs avec d'autres points remontés précédemment.
Comment résoudre ces cas ?
Suite à ces différents cas d'abandon, j'ai défini 9 bonnes pratiques à suivre pour résoudre ces cas.
- Former les développeurs et les chefs de projets pour leur faire comprendre les intérêts qu'ils vont trouver à l'approche MDA : démarrage du projet plus rapide, suivi des préconisations d'architecture logicielle concernant la stack logicielle de l'entreprise, qualité de code, fiabilité du code technique qui a été éprouvée par les architectes logiciels et dont le développeur n'a plus à se préoccuper.
- Limiter le modeleur au DSL. Dans le cas où le DSL est une extension d'un langage existant (UML par exemple), il est nécessaire de customiser le modeleur pour limiter les possibilités de modélisation aux cas restreint par le DSL. Dans la mesure où on peut "ajouter" des concepts à UML, mais pas en "enlever", le modeleur ne pourra pas limiter toutes les possibilités offertes par le langage existant, ce qui nous amène au point suivant...
- Le modèle doit être validé, au moins en partie, pendant la phase de modélisation. Au même titre qu'un code qui ne compile pas, apparait en erreur dans l'IDE, il faut qu'un modèle qui ne soit pas valide apparaisse en erreur dans le modeleur. Ceci implique que la validation de la cohérence d'un modèle doit être faite au plus tôt dans la chaine de génération.
- La qualité du modèle doit être remontée au plus tôt. Au même titre que le code peut avoir une note de qualité (par exemple dans Sonar), il doit être envisager d'avoir une note de qualité du modèle. L'approche MDA permet une meilleure qualité de code, mais n'empêche pas de modéliser n'importe quoi (tout en respectant les règles du DSL déjà validées par le point 4). Noter la qualité du modèle permet donc d'assurer une qualité de code accrue. Ce point nécessite également de mettre en place un document de convention de modélisation.
- La chaîne de génération doit être transparente : la plus rapide et la moins intrusive possible pour le développeur. Idéalement le développeur devrait modéliser et développer de manière fluide en passant de son modeleur à son IDE de manière instantanée : L'étape de génération devrait lui être cachée !
- Le code généré doit être correct (il ne comporte pas d'erreur de syntaxe), efficace (le code généré n'implique pas de pertes de performances et n'ajoute pas de bug supplémentaires à l'application), fonctionnel (Il a été validé par des architectes logiciels par un POC) et maintenable (il répond aux standards de qualité définis dans les conventions de codage).
- Le générateur doit être déterministe : il doit toujours générer le même code (et surtout dans le même ordre) pour un même modèle en entrée.
- Le générateur, par son action (lors d'une regénération) ou son inaction (modification du code par l'IDE ou erreur du développeur), ne doit pas faire perdre de code manuel.
- Lors de la création du générateur, une étude importante doit être faite pour bien définir les zones de codes manuelles et les zones de code entièrement générées. Il faut pouvoir laisser des portes de sorties au développeur pour customiser le code et qu'il ne se sente pas restreint et considère l'approche MDA comme étant limitative au développement.
Ces bonnes pratiques sont bien évidemment spécifiques à ma propre expérience d'un point de vue développeur sur le cas concrets des générateurs mixtes généré/manuel. Dans la suite des articles, j'expliquerai comment on peut faire en sorte de respecter ces bonnes pratiques.
Le point de vue concepteur de générateur apporte également tout un lot de bonnes pratiques à mettre ne œuvre pour que l'approche MDA soit un succès.
L'utilisation d'autres types de générateurs ou de DSL (DSL textuels par exemple) apportent également leurs lot de bonnes pratiques.