Affichage des articles dont le libellé est generateur. Afficher tous les articles
Affichage des articles dont le libellé est generateur. Afficher tous les articles

jeudi 10 octobre 2013

Formater du code Java avec JDT

Je continue ma série d'article sur l'industrialisation des générateurs de code commencé précédemment.
Dans l'article précédent je présentais comment analyser la syntaxe d'un code source Java afin d'effectuer un premier niveau de validation.
Dans cet article, nous allons voir comment formater son code de manière programmatique (en standalone), conformément à un formateur Eclipse. On va voir comment utiliser Eclipse JDT pour nous aider.

Mise en place du projet

Pour l'utilisation du formateur d'Eclipse, je m'appuie sur les librairies disponibles dans Eclipse Kepler. J'utilise les librairies suivantes :
  • org.eclipse.jdt.core_3.9.1.v20130905-0837.jar
  • org.eclipse.jface_3.9.1.v20130725-1141.jar
  • org.eclipse.jface.text_3.8.101.v20130802-1147.jar
  • org.eclipse.core.runtime_3.9.0.v20130326-1255.jar
  • org.eclipse.equinox.common_3.6.200.v20130402-1505.jar
  • org.eclipse.text_3.5.300.v20130515-1451.jar
  • org.eclipse.osgi_3.9.1.v20130814-1242.jar
  • org.eclipse.core.resources_3.8.101.v20130717-0806.jar
  • org.eclipse.core.jobs_3.5.300.v20130429-1813.jar
  • org.eclipse.core.contenttype_3.4.200.v20130326-1255.jar
  • org.eclipse.equinox.preferences_3.5.100.v20130422-1538.jar

Il est possible de formater son code Java en utilisant l'API directement depuis la ligne de commande. C'est ce qu'explique Peter Friese dans son article Formatting your code using the Eclipse code formatter.
L'idée est d'appeler l'API depuis la ligne de commande :
<path-to-eclipse>\eclipse.exe -vm <path-to-vm>\java.exe -application **org.eclipse.jdt.core.JavaCodeFormatter** -verbose -config <path-to-config-file>\org.eclipse.jdt.core.prefs <path-to-your-source-files>\*.java
C'est une approche qui pourrait répondre à nos besoins, mais dans mon cas, je souhaitais pouvoir exécuter depuis un process Java existant.

Avant de pouvoir appeler le formateur JDT, il faut d'abord récupérer les options de formatage. On a deux possibilités : utiliser les préférences du workspace ou utiliser un formateur XML.

Récupération des options depuis les préférences de workspace

Le fichier org.eclipse.jdt.core.prefs contient les préférences de formatage d'un workspace Eclipse.
Il est disponible dans le répertoire .metadata\.plugins\org.eclipse.core.runtime\.settings de votre workspace.
Il propose des options de formatage sous la forme Clé=Valeur. Ce fichier Properties peut donc être lu facilement avec l'API Properties de Java. L'objet Properties implémente Map<Object, Object>, on peut donc récupérer nos propriétés sous forme de Map très facilement avec un simple cast. L'API de formattage de JDT utilisant un Map<String, String>, cette approche nous simplifie le travail.
(je n'indique ici que le code utile...j'ai intentionnellement supprimé les catch et fermeture de flux pour faciliter la lecture)
private static Map<String, String> readPreferences(String filename) {

    File configFile = new File(filename);
    BufferedInputStream  stream = new BufferedInputStream(new FileInputStream(configFile));
    final Properties formatterOptions = new Properties();
    formatterOptions.load(stream);
    return (Map)formatterOptions;
}

Récupération des options depuis un formateur Eclipse

Pour la récupération d'options depuis un fichier de formateur Eclipse, le travail est un peu plus (mais à peine) compliqué.
Le fichier XML n'est qu'une autre représentation du fichier Properties dans laquelle chaque noeud setting propose le couple [clé,valeur] avec les attributs [id, value].
Un parsing du fichier permet donc d'en sortir une Map<String, String> sans trop de difficultés.

private static Map<String, String> readFormatter(String filename) {
    Map<String, String> options = new TreeMap<String, String>();
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);

    DocumentBuilder builder = factory.newDocumentBuilder();
    org.w3c.dom.Document doc = builder.parse(filename);
    XPathFactory xFactory = XPathFactory.newInstance();
    XPath xpath = xFactory.newXPath();
    XPathExpression expr = xpath.compile("//setting");
    Object result = expr.evaluate(doc, XPathConstants.NODESET);

    NodeList nodes = (NodeList) result;
    for(int i = 0; i < nodes.getLength(); i++) {
        Node id = nodes.item(i).getAttributes().getNamedItem("id");
        Node value = nodes.item(i).getAttributes().getNamedItem("value");
        options.put(id.getNodeValue(), value.getNodeValue());
    }
    return options;
}

Formater le code à partir des options

Une fois les options récupérées sous la forme d'un Map<String, String>, il suffit de récupérer un CodeFormatter avec createCodeFormatter(), puis de formater le code à partir du code source existant. Dans notre cas, nous formatons l'intégralité d'un fichier source (K_COMPILATION_UNIT). Le source formatté est récupéré par une gymastique : le TextEdit est appliqué à un Document, le code source est ensuite récupéré depuis le document avec get() sous la forme d'une chaine de caractère.

CodeFormatter formatter = ToolFactory.createCodeFormatter(options);
TextEdit textEdit = formatter.format(CodeFormatter.K_COMPILATION_UNIT, source, 0, source.length(), 0, null);
IDocument document = new Document(source);
textEdit.apply(document);
String formattedSource = document.get();

Conclusion

L'intérêt de pouvoir formater son code de manière programmatique simplifie le travail lors de la création d'un générateur de code. En effet, il n'y a plus besoin de se soucier de prendre en compte le formatage lors de la conception du générateur de code :ce formatage peut peut être fait en post-génération.
L'intérêt de pouvoir paramétrer son formateur et l'externaliser du générateur est autrement plus intéressant si l'on souhaite proposer plusieurs "saveurs de formatage" (idéal notamment pour les Centre de Service en SSII par exemple), car il permet de générer des sources compatibles aux conventions de codage de chaque client de manière très simple et à moindre coût.

les sources complètes sont disponibles sur Github.


mardi 7 février 2012

Les limites du MDA en entreprise

Je souhaitais faire un retour d'expérience concret sur les cas "typiques" de limites de l'approche MDA en entreprise d'un point de vue "développeur". Ces limites peuvent pousser, dans certains cas, à abandonner l'approche MDA.
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.  
Le temps passé sur le SCM à réintégrer le code perdu devient donc problématique. D'autant plus, lorsque nous préconisons une approche incrémentale, le développeur fera nécessairement plusieurs round-trip au cours du développement de son logiciel. Chaque round-trip nécessitant de la part du développeur qu'il vérifie que du code n'as pas été perdu. La fréquence de round-trip s'avère donc incompatible avec des délais courts (point précédent).
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.
Le développeur perd du temps à remettre le code en conformité.

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.
  1. 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.
  2. 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...  
  3. 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.
  4. 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.
  5. 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 !
  6. 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).
  7. 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.
  8. 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.
  9. 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.
Aller plus loin
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.





dimanche 22 janvier 2012

Gestion de code mixte généré / manuel

Ce message fait suite à l'explication sur les principes de base des générateurs et s'attarde principalement sur le cas 2 qui était problématique : Comment gérer le code généré et le code manuel pour un même artefact?
J'utilise le terme « artefact » parce que le cas 2 expliqué dans le message précédent ne génère pas obligatoirement un seul fichier comme on va le voir plus bas.

Il existe plusieurs possibilités de gérer les artefacts qui doivent contenir du code généré et du code manuel. Je vais passer en revue les différentes possibilités avec leurs avantages et inconvénients.
Dans ce message (comme dans le précédent d'ailleurs), je représente en bleu le code généré et en jaune le code manuel.


1. L'approche « un fichier par artefact »
1.1. Approche par « balises »
Le code généré contient des balises qui permettent de repérer les zones de code généré et les zones de code manuel. Afin de ne pas affecter la syntaxe du code source, ces balises sont toujours placées en tant que commentaire.
Cette approche n'est donc pas à proprement parlé une approche « génération textuelle », car elle suppose, pour fonctionner, que le fichier généré suive une grammaire qui offre le concept de commentaire. En fait, la portée est très large car quasiment toutes les représentations basées sur du texte proposent des commentaires : Tous les langages (Java, .NET, assembleur, etc.), les langages dédiés (ceux basés sur SGML, SQL, JSON), fichiers de propriétés (properties), les scripts (bash, shell), les documents (RTF, LATeX, PDF). Bref, pas de quoi s'inquiéter sur cet aspect.
Cette solution reste la plus générique et la plus adaptable, quelque soit le type de fichier.

Les balises de code manuel permettent de délimiter les portions de code que le développeur peut modifier sans risque. La portion de code manuel ne sera pas écrasée lors d'une future génération.
Lors d'une regénération, le générateur fait un comparatif entre les balises du fichier qu'il vient de générer et ces mêmes balises dans le fichier existant. Il est ainsi capable de ré-intégrer les portions de code manuel.
Les balises sont des clés et il est important qu'elles restent identiques entre chaque génération. 
La subtilité consiste donc à créer des balises qui soient uniques pour chaque portion de code manuel. Lors de la création du générateur, il faut définir les différentes zones de code manuel et leur associer des balises. Bien sûr, les balises peuvent elles-mêmes être créées dynamiquement pour être constituées avec des informations du modèle.

Exemple
public float calculateTotalPrice(float price, float tax) {
// start of user code
    return price * tax ;
// end of user code 
}


On peut considérer 2 cas :
  • Soit on place des balises sur les portions de code généré, 
  • soit on place des balises sur les portions de code manuel. 
 Tout dépend de l'approche que l'on a. L'idée est évidemment de placer un minimum de balises et donc :
  • Si on génère une grande partie du fichier et que le code manuel est minoritaire => on privilégie une approche « priorité code généré »
  • Si on ne génère qu'une petite partie du fichier et que le code généré est minoritaire => on privilégie une approche « priorité code manuel »

1.1.1. Les balises « priorité au code généré »
Petit aparté : Dans certains outils de génération, on trouve la notion de « balise utilisateur », « user tag » ou « user code ». Je trouve que le terme n'est pas approprié car la notion d'utilisateur me fait penser à l'utilisateur final du logiciel. Or ce n'est pas le cas ici, le code indiqué entre balise est du code « développeur », c'est à dire du code que le développeur doit ajouter manuellement pour que le programme soit fonctionnel. Pour simplifier les choses, il est préférable de parler de « code manuel ».

Ici, le code généré est prioritaire, ce sont donc les portions de code manuel qui sont délimitées. Lors d'une regénération, si les balises sont manquantes, c'est le code manuel qui sera perdu.


Utilisé par : outils de génération MIA Generation ou Acceleo.

1.1.2. Les balises « priorité au code manuel »


Ici, le code manuel est prioritaire et ce sont donc les portions de code générées qui sont délimitées.
Lors d'une regénération, si les balises sont manquantes, le code manuel restera intact.
C'est le principe offert par les wizards et générateurs d'interface qui vont insérer du code généré dans un code manuel.
Utilisé par : NetBeans pour la génération des UI Java Swing.
Exemple de portion de code généré pour l'interface graphique sous NetBeans.


1.1.3. Les balises « depuis » et « jusqu'à ».
Il existe des alternatives proposées par certains outils qui permettent de placer le code généré en début fichier et le code manuel en fin de fichier (ou inversement).
Je les nomme balises « Jusqu'à » et « Depuis ».

Utilisé par : MIA Generation propose cette fonctionnalité au travers des balises code manuel :
  • Si la balise de début n'existe pas et que seul la balise de fin existe, alors tout le début du fichier jusqu'à la balise sera du code manuel. Le fin du fichier sera du code généré.
  • Si la balise de fin n'existe pas et que seul la balise de début existe, alors le code manuel est situé depuis la balise et jusqu'à la fin du fin. Le début du fichier sera du code généré.
Remarque : Malgré la possibilité offerte, je n'ai jamais rencontré le besoin de mettre en place cette approche. Je ne pense pas que cette approche apporte un réel intérêt.

1.1.4. Limitations
  • Cette approche pollue le code avec des balises qui n'ont finalement aucune valeur ajoutée pour le développeur. Heureusement, certains outils (NetBeans par exemple) sont capable de réduire les zones générées dans l'éditeur et donc de ne pas les faire apparaître au développeur.
  • Les risques d'écrasement sont réels. Si le développeur supprime ou modifie une balise utilisateur, le code peut se trouver écrasé par inadvertance. Il existe également des cas pour lesquels la regénération est problématique : le changement de signature de méthode (issu d'un renommage par exemple) modifie la clé de la balise et écrase donc le code manuel existant. Il est nécessaire de coupler le code source avec un SCM pour éviter toute perte de code.


1.2. Approche différentielle
Il s'agit d'une approche plus sophistiquée qui consiste à repérer les différences entre le code généré à vide et le code actuel afin d'en déduire le code manuel.

L'opération consiste ensuite à ré-intégrer ce code manuel dans le fichier nouvellement généré. Contrairement à l'approche par balise, cette approche à une portée plus large : elle ne cible plus spécifiquement les fichiers dont la grammaire propose le concept de commentaires. Du fait de son approche différentielle, elle cible tous les types de fichiers texte.

Utilisé par : Il n'existe pas, à ma connaissance, de générateur qui prennent en compte cette approche (même si John Vlissides y fait référence ici).

Limitations
  • Il s'agit d'une approche risquée car parfois le code manuel peut difficilement être réintégré.
  • Elle est évidemment complexe à mettre en œuvre.
1.3. Approches spécifiques aux langages
Certains langages disposent de métadonnées permettant d'annoter des portions de code comme étant générées.

1.3.1. Java 6 et l'annotation @Generated
A ma connaissance, seul Java 6 propose cette possibilité avec l'annotation @Generated. Elle peut être placé sur une classe, un attribut ou une méthode.

@Generated(value = "ClassNameThatGeneratedThisCode")

    public void toolGeneratedCode(){


    }

Limitations :
  • Le générateur ne doit plus simplement faire une analyse textuel du code source, mais également un parsing de l'AST pour repérer l'annotation @Generated dans le code.
  • Le gestion de la regénération devient donc plus couteuse à mettre en place.
  • De plus, cette approche n'offre pas de souplesse : on ne peut pas délimiter finement les portions de code manuelles.

2. Approche multi-fichiers par artefact

2.1. L'approche « abstraction »
Cette approche s'applique aux langages objets qui proposent les concepts d'héritage : C++, C#, Java, AS3, VB.NET et consorts. On écarte ici toutes les possibilités de générer des fichiers pur texte. Il est donc impossible de gérer autant de cas que dans l'approche par balises.

Cette approche consiste à abstraire l'implémentation concrète de la classe qui sera codée manuellement de son interface qui, elle, sera générée. Cette approche suit un des principe de base de la programmation orientée objet : le polymorphisme. Pour garder un code portable et réutilisable, il est préférable de faire référence à une classe par son interface, plutôt que par son implémentation, comme rappelé dans le livre Design Patterns :
  • Program to an interface, not an implementation.
  • Don't declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class.
 

Le code généré est donc celui de l'interface (ou classe abstraite). Le code manuel est laissé dans l'implémentation. Lors d'une regénération, l'interface est écrasée, le code manuel n'est pas regénéré.
Cette approche a son propre Design Patterns, appelé "Generation Gap" qui est expliqué beaucoup plus en détail sur la page de John Vlissides. Il est intéressant de se rappeler également du Design Pattern "Patron de conception" lors de la mise en place de cette approche.

Si l'on souhaite pouvoir ajouter manuellement du comportement à la classe, il faudra prévoir d'exposer ce comportement dans les interfaces. Dans ce cas, il faut aller une étape plus loin et proposer :
  • Une interface générique entièrement générée;
  • Une interface pour le code manuel;
  • Une classe abstraite d'implémentation;
  • Une classe d'implémentation pour le code manuel.
Ce schéma est grandement inspiré de celui proposé dans la documentation de mod4j.

Utilisé par : Le Data Centric Development de FlashBuilder 4  pour générer le code AS3 issus de l'exposition des services (code généré depuis un WSDL par exemple).
C'est également ce principe qui est utilisé dans mod4j pour la génération des fichiers Java sur une grande partie des couches logicielles. 

Limitations
  • Le nombre de fichiers générés pour un seul concept est important. Il double le nombre de fichiers juste pour des raisons de génération.
  • Cette solution est intéressante mais ne peut pas fonctionner seule pour générer intégralement un projet d'entreprise JavaEE ou .NET : Une application d'entreprise nécessite plusieurs langages : Java, XML, JSP, HTML, Javascript, CSS, etc. Le problème est que tous ces fichiers générés ne sont pas tous des langages Orientés Objets. Il faudra donc la coupler avec une autre approche.
Note : pour .NET, il est préférable d'utiliser l'approche "classes partielles" expliquée plus bas (voir 2.3.1).
Re-note : en Java, il peut être intéressant d'utiliser AspectJ (voir 2.3.2).

2.2. L'approche « inclusion »
Cette approche peut être utilisée pour les fichiers qui proposent des concepts d'inclusion.
Elle peut être utilisée par Spring : un fichier de contexte Spring global inclut un fichier intégralement généré, puis un fichier qui contient le code manuel.
On peut supposer une approche équivalente pour le Javascript : le fichier HTML inclus un fichier Javascript entièrement généré et un autre qui contient le code manuel.

Utilisé par : mod4j propose une génération équivalente à l'approche abstraction pour les fichiers Spring.

Limitations :
  • Génération limitée aux représentations textuelles qui proposent des inclusions : Spring, Javascript (autres ?) .


2.3. Approches spécifiques aux langages

2.3.1. .NET et les "classes partielles"
Cette approche est spécifique à .NET et donc aux langages C# et VB.NET à partir de la version 2.0.
La possibilité de découper des classes en plusieurs morceaux et de les ré-assembler lors de la compilation rejoint l'approche "inclusion" mais spécifiquement à la plateforme .NET.
Cette fonctionnalité à été originalement mise en place dans le langage pour faciliter le découpage MVC, mais on peut très bien la mettre en place pour l'ensemble des classes .NET. Chaque classe peut donc être découpée en deux fichiers :
  • Un fichier "Personne.cs" qui contient le code manuel et qui ne sera pas impacté par une regénération ;
  • Un fichier "Personne-generated.cs" qui contient le code généré et qui sera écrasé lors de la prochaine génération.
Utilisé par : l'éditeur graphique de Visual Studio 2005 découpe les classes de cette façon.
Cette approche est également utilisée par SoftFluent Entities Builder pour la génération des applications .NET sous Visual Studio 2008.

Limitations :
  • Cette approche double le nombre de fichiers, mais contrairement à l'approche "abstraction", la génération par classe partielle .NET présente l’intérêt de ne pas doubler le nombre de concepts objets (pas besoin d'avoir des classes abstraites, etc.). D'un point de vue conception, on reste donc propre et c'est un bon point. Dommage que Java, en natif, ne dispose pas d'un mécanisme équivalent.
  • On ne peut pas générer l'intégralité d'un projet d'entreprise .NET avec cette solution. Comme pour la solution abstraction, il faudra prévoir une alternative pour les autres ressources du projet.


2.3.2. AspectJ et l'Inter Type Declaration
Dans un contexte de développement AOP, il est possible d'utiliser l'Inter Type Definition pour découper le code manuel et le code généré. Ici, on reste très axé sur du Java (ou du moins sur des langages Java au sens plus large). Le code généré est placé dans un fichier AspectJ (fichier .aj), le code manuel dans le fichier Java (fichier .java).
Lors de la compilation, le code du fichier AspectJ sera injecté dans le code Java. Lors d'une regénération, seul le fichier AspectJ est écrasé, le fichier Java reste intact.



Utilisé par : Spring Roo sur la plupart des fichiers Java.


A noter : Jboss Forge (qui ressemble étrangement à Spring Roo) ne fait que de la génération One-Time et ne propose donc pas de système équivalent aux fichiers AspectJ proposé par Spring Roo. Même chose pour le framework Play !
pour aller plus loin : ce comparatif explique la différence entre les 3 principaux RAD en Java : Spring Roo, Jboss Forge et Play !


Limitations :
  • Cette approche double le nombre de fichiers, mais contrairement à l'approche "abstraction", la génération par fichier AspectJ présente l’intérêt de ne pas doubler le nombre de concepts objets (pas besoin d'avoir des classes abstraites, etc.). Le fait que le fichier AspectJ soit intégré à la compilation offre également les facilités d'éditions dans l'IDE (complétion) ainsi que la validation statique des types.
  • On reste très (très) dépendant de la librairie AspectJ avec cette approche. La compilation n'est pas standard non plus. Par contre, pour l’exécution, on est indépendant d'AspectJ ( Roo is not Runtime).
  • On ne peut pas générer l'intégralité d'un projet JavaEE avec cette solution. Comme pour la solution abstraction, il faudra prévoir une alternative pour les autres ressources du projet.

3. Pour aller (encore) plus loin
  • Un comparatif des outils de génération de code (pas complet mais qui a le mérite d'exister) : language workbenches.