2012 : Conférence "Industrialisation du développement 4D avec les composants" réalisée par A&C Consulting
4D est un environnement de développement rapide. La productivité et la qualité peuvent être améliorées avec, entre autres, la réutilisation de code. Les composants apportent une solution adaptée et élégante pour répondre à ce besoin.
La démarche de structuration des composants, les avantages et inconvénients par rapport aux différentes approches a été présentée.
1. 4 D
S U M M I T
E U R O P E
2 0 1 2
Industrialisation du
développement 4D avec les
composants
Presented by : Bruno LEGAY - A&C Consulting
1
2. INDUSTRIALISATION ?
De quoi parle-t-on lorsqu'on parle d'"industrialisation" ?
L'industrialisation est caractérisée par l'optimisation des processus de fabrications de produits manufacturés. On
est ainsi passé d'une production "artisanale" à une production "industrielle".
Cette évolution remonte au XIXeme siècle (1801) avec la révolution industrielle qui apparaît en Grande Bretagne.
Elle est caractérisée par l'utilisation de machine, la production en série, la standardisation pour abaisser les
coûts de production et améliorer la qualité.
Quelques dates clés et les tendances dans deux domaines spécifiques
•
Automobile :
o 1886 première voiture ;
o 1908 Ford T – Taylorisme : production à la chaîne ;
o 1961 premier robot industriel ;
o 1990 "commonolisation" (partie cachée commune – partie visible personnalisée).
•
Informatique – matériel
o 1943 Colossus
o 1976 Apple I
o 1984 Macintosh - IBM PC/AT
o standardisation des composants (processeurs, mémoire, disques).
On remarque plusieurs tendances : automatisation des taches, standardisation de composants.
Toutefois, ces domaines comportent des différences significatives par rapport au logiciel. La conception d'un
logiciel s'apparente peu à une succession de tâches prédéfinies et répétitives qui peuvent être découpées,
optimisées et enchaînées.
Enfin, ces deux domaines ont pour objectifs de produire des biens matériels alors que l'industrie logicielle relève
plus d'un travail de conception consistant à produire un bien numérique et immatériel. La question de production
en masse ne se pose pas de la même manière.
Qu'en est-il de l'industrialisation appliquée au génie logiciel (utopie ou réalité) ?
Deux axes :
•
•
automatisation de tâches répétitives
standardisation des composants.
2
3. AUTOMATISATION
Automatisation de tâches répétitives
Pourquoi ?
•
•
•
•
cohérence / consistance
qualité (éviter les erreurs humaines)
éviter les dérives
se concentrer sur les tâches à valeur ajoutées.
Génération de librairies, de code compilé, d'exécutables (construction d'applications avec GENERER
APPLICATION, génération d'installeurs, compression, archivage sources, publication, signature md5,
etc…). Possibilité d'utiliser plusieurs "profils" d'application (fichier xml "BuildApp"), générateur
d'installeur "nsis" sur windows.
Les tests unitaires sont destinés à vérifier le bon fonctionnement d'une partie spécifique d'un logiciel
(et ceci en toutes circonstances). Les tests unitaires doivent aussi permettre de vérifier qu'une
modification du logiciel (modification du code) n'introduit pas de régressions (apparition de nouveaux
dysfonctionnements). Les tests unitaires permettent aussi de vérifier rapidement l'impact des
changements liés aux plateformes ou aux environnements. En cas ce bug, le contexte est ajouté dans le
"test" pour valider la correction et vérifier que ce bug ne réapparaitra plus. Cf méthodes XP (Extreme
Programming) ou TDD (Test Driven Development).
Génération de documentation. Les documentations techniques sont difficiles à maintenir. La
documentation la plus à jour et la plus précise doit se situer au niveau du code. On peut s'inspirer de
Javadoc qui est utilisé dans le monde Java. Il s'agit de structurer des informations sur la méthode dans
les commentaires à l'aide de balises (ou "tags"). Les macros permettent alors de préparer cette zone
avec un "modèle". Le développeur n'a qu'à compléter cette zone.
3
4. STANDARDISATION DES COMPOSANTS
Préhistoire des composants
4D Insider est l'ancêtre des composants actuels (apparus en v11).
4D Insider était l'outil de prédilection pour déplacer des objets (méthodes, formulaires, etc…) entre
diverses structures.
Avec 4D Insider, on pouvait créer des librairies ou composants (avec une notion de composants
protégés).
4D Insider avait un certain nombre de limites (l'utilisation des composants n'était pas très souple entre
autres).
4D Insider est mort ! Vive les composants !
4
5. PRINCIPE DES COMPOSANTS
Rappel du fonctionnement des composants
Un composant est une base de données (placée dans le répertoire "Components"). NOTE : à partir de
4D v12, on peut placer des composants dans l'application 4D (comme pour les plugins).
On a une notion de base "hôte" et une (ou plusieurs) base(s) "composant(s)". Pour les sources du
composant on parle parfois de la base "matrice".
Une base interprétée peut utiliser un composant interprété ou compilé. Une base compilée utilise
nécessairement un composant compilé. Attention à la compatibilité des options de compilation
(compilation 64 bits par exemple).
Les modes "Unicode" de la base "hôte" et la base "composant" doivent être compatibles (c'est-à-dire
identique).
Les méthodes du composant visibles par la base "hôte" doivent être "partagées". L'inverse est vrai
aussi, pour qu'un composant appelle/exécute une méthode de la base "hôte", celle-ci doit aussi être
partagée.
Les dépendances des composants doivent être maîtrisées (et documentées).
Certains objets sont utilisables dans un composant, d'autres ne sont pas utilisables.
Objet
Utilisable en mode composant
Méthode
oui
Formulaire projet
oui
Formulaire table
non
Table et champ
non
Trigger
non
Méthode base ("sur ouverture" par exemple)
non
Utilisateur et Groupe
non
Préférences/propriétés de la base
non
De ce fait, certaines commandes n'ont pas de sens (et ne peuvent donc pas être utilisées) comme par
exemple "ECRIRE IMAGE DANS BIBLIOTHEQUE".
5
6. Certains objets sont partagés, d'autres sont cloisonnés.
Objet
Partagé / cloisonné
Variable (process et interprocess)
cloisonné
Variable système (ok, Document, etc…)
cloisonné
Feuille de style
cloisonné
Infobulle
cloisonné
Enumération
cloisonné
Image bibliothèque
cloisonné
Menu (éditeur menu)
cloisonné
Sémaphore
cloisonné
Ensemble
partagé
Sélection temporaire
partagé
Liste hiérarchique
partagé
Menu (cf Créer menu)
partagé
Référence xml
partagé
Référence fichier
partagé
Pointeur
partagé
6
7. AVANTAGES DES COMPOSANTS
Quels sont les avantages d'un composant ?
Les composants permettent de partager et réutiliser le code de manière efficace.
Un composant permet de cloisonner les environnements d'exécution et d'éviter des "collisions". C'est
une alternative possible à l'utilisation systématique de préfixes sur les variables.
Un composant compilé peut permettre des gains de performances significatifs dans une base interprétée
(en développement par exemple).
7
8. CONTRAINTES
Quelles sont les contraintes rencontrées lors de la création de composants ?
L'implémentation de composant nécessite de bien comprendre les mécanismes de dépendances. Par
exemple, si un composant utilise explicitement un plug-in, la base "hôte" devra avoir accès à ce plugin.
On rencontre la même contrainte avec les composants (un composant utilisé explicitement dans la base
"matrice" devra être installé dans la base "hôte").
La documentation est impérative. En effet, il n'est pas possible de consulter le code d'un composant
compilé. Au minimum une description de la méthode avec le détail des paramètres doit figurer dans la
zone "commentaires" de l'explorateur (en v12). Les commentaires situés au début de la méthode d'un
composant ne sont pas visibles depuis la base "hôte" si le composant est compilé (mais ils sont visibles
si le composant est interprété).
8
9. PIEGES
Quelques pièges liés aux composant (et comment les éviter / contourner) ?
La commande "RESOUDRE POINTEUR" pouvait parfois être utilisée pour comparer si deux
pointeurs pointaient bien sur la même variable. Si l'origine des pointeurs est différente, ils peuvent
porter le même nom et pourtant ils pointent sur des variables différentes. La comparaison de pointeurs
(en passant par RESOUDRE POINTEUR est donc fausse).
Il faut simplement comparer directement les pointeurs :
Si($vp_ptr1=$vp_ptr2)
La commande "Pointeur vers" utilisée dans un composant retournera un pointeur vers une variable du
composant.
Une astuce consiste à utiliser une méthode de "rétro-appel" (ou "callback"). Dans la base "hôte", on
crée une fonction qui fera appel à "Pointeur vers" (on oublie pas de partager cette méthode) et on passe
au composant le nom de cette fonction. Le composant pourra alors appeler cette fonction (via la
commande "EXECUTER METHODE").
La création de cette méthode de "callback" peut même être automatisée.
9
10. DEPENDANCES SOUPLES
Comment gérer des dépendances souples ?
On peut avoir besoin d'avoir de composants qui font appel de manière optionnelle à un autre
composant. Soit parce que ce composant n'est nécessaire que dans un contexte (contexte de
développement dans la base "matrice") ou parce que ce composant fournit une fonctionnalité
optionnelle lors de l'utilisation. Dans ce cas, si l'appel à ce composant est effectué de manière explicite,
on aura un message d'erreur à l'ouverture de la base "hôte". Le composant doit détecter de manière
transparente la présence de l'autre composant et agir de manière appropriée. C'est ce qu'on appelle une
dépendance souple.
Pour détecter la présence d'un composant, il y a la commande LISTE COMPOSANTS.
On peut ensuite faire appel aux méthodes du composant via la commande EXECUTER METHODE
pour éviter les problèmes de dépendances fortes.
// LOG_debug ($vt_message)
TABLEAU TEXTE($tt_componentsList;0)
LISTE COMPOSANTS($tt_componentsList)
Si(Chercher dans tableau($tt_componentsList;"LOG_component")>0)
EXECUTER METHODE("LOG_debug";*;$vt_message)
Fin de si
TABLEAU TEXTE($tt_componentsList;0)
10
11. ASTUCES
Quelques astuces
Il est parfois nécessaire de déterminer si le code s'exécute dans le composant ou dans la base "hôte".
Pour cela on peut comparer le résultat de la commande "Fichier structure" avec et sans l'option "*".
Par défaut (sans "*") Fichier structure retourne le nom long/chemin du fichier structure (fichier .4db ou
.4dc) du composant.
Si "*" est passé, Fichier structure retournera le nom long/chemin du fichier structure de la base hôte (si
on est dans un composant)
Si(Fichier structure # Fichier structure(*)) // on est dans un composant
…
Sinon // on est pas dans un composant
…
Fin de si
On peut en faire une fonction, mais attention, par définition cette méthode ne pourra pas s'utiliser
comme une méthode partagée d'un composant. Il faut la copier…
On a parfois besoin de déterminer quelle est la langue du langage 4D utilisée (les commandes sontelles en français ou en anglais) ?
C_ALPHA(2;$va_language)
C_TEXTE($vt_commandNameOfCommandName)
$vt_commandNameOfCommandName:=Nom commande(538) //538 "Command name"
Au cas ou
: ($vt_commandNameOfCommandName="Nom commande")
$va_language:="fr"
: ($vt_commandNameOfCommandName="Command name")
$va_language:="en"
Sinon
$va_language:="en"
Fin de cas
11
12. LES COMPOSANTS FOURNIS PAR 4D
4D fourni quelques composants standard
4D fourni déjà quelques composants 4D SVG : images vectorielles SVG pour le runtime
4D Widgets propose un datePicker et timePicker pour le runtime
4D Pop propose des fonctionnalités au développeur. Composant orienté vers l'environnement de
développement.
Sans oublier tous les composants fournis par Keisuke Miyako
Au delà des composants fournis par 4D, quel est l'intérêt pour un développeur à créer ses propres
composants ?
12
13. POURQUOI CREER UN COMPOSANT
Dans quel cas doit-on envisager de créer un composant ?
Dans un logiciel, on peut déterminer un ratio "glue" / "substance" (cf Alexander Stepanov). La
partie "glue" correspond au code qui ne répond pas directement au besoin du projet et la "substance"
correspond à la partie spécifique (au métier). On peut mesurer ceci sur la base du nombre de lignes. Par
exemple, dans un de nos projets, nous avons effectué récemment des métriques et nous avons constaté
que sur 310 000 lignes de code (méthodes projet) 43,5 % du code était du code générique et que le
reste 56,5 % était du code spécifique.
Objectif d'efficacité : se concentrer sur le métier/fonctionnel et simplifier la ré-utilisation du code
technique générique testé et éprouvé (mise en place, maintenance).
Objectif de qualité : en simplifiant la mise en place (et la mise à jour) de code générique, on facilite la
réutilisation. L'amélioration continue du code générique (ajout de fonctionnalités, optimisation,
correction de bugs) est plus facilement mutualisée entre les projets et contribue à une amélioration de la
qualité globale des applications.
Ces objectifs sont surtout intéressant pour les développeurs qui travaillent sur plusieurs projets.
13
14. QUE PEUT-ON FAIRE AVEC UN COMPOSANT
Un composant, oui, mais pour faire quoi ?
On peut créer des composants pour packager des méthodes bien évidemment (génération de codes
barres), on peut afficher des formulaires (formulaires base) pour l'affichage des thermomètres, une
zone Web area (pour une fenêtre GoogleMaps par exemple) par exemple.
On peut aussi utiliser les composants pour packager des binaires ou outils externes. On peut par
exemple placer les exécutables (OS X et Windows) dans le répertoire "Resources" et les appeler via la
commande LANCER PROCESS EXTERNE. Idem pour des fonctions PHP (scripts php). Par exemple,
7z pour la compression zip, xmllint pour la validation schéma xml avec XInclude, rsync+cygwin,
ImageMagick, wkhtmltopdf, etc…
On peut facilement placer des méta-données dans un composant. Par exemple, on peut placer des
meta-données au format xml dans le répertoire "Resources" qui seront accessibles via une méthode
partagée du composant (par exemple les codes escape url http et/ou html).
Les composants peuvent aussi être utilisés pour wrapper les plugins (4D for OCI par exemple). On peut
créer des composants qui permettent de basculer entre différents plugins similaires (commandes TCP
des Internet Commands, NTK et ITK) ou entre un plug-in et du code 4D pur (ObjectTools, XML
Plugin, etc…).
14
15. CHOIX
Quelles questions doit-on se poser avant de se lancer ?
Quel type de composant va-t-on créer ? Un seul composant avec toutes les méthodes à l'intérieur
(approche monolithique) ou un plusieurs composants (approche modulaire) ?
Dans le cas d'une approche modulaire, comment seront gérées les dépendances entre les composants ?
Composant interprété ou compilé (si oui pour quelle plateformes) ?
Composant Unicode ?
Comment mettre en place, définir et stabiliser ses API (maturation des points d'entrée,
flexibilité/souplesse de fonctionnement avec l'utilisation de paramètres optionnels et callback, stabilité,
pérennité et rétro-compatibilité) ?
15
16. NOTRE CONTEXTE
Le contexte dans lequel nous avons été amenés à mettre en place des composants est le suivant
A&C Consulting est une SSII avec 3 développeurs 4D expérimentés.
Nous travaillons sur de nombreux projets spécifiques.
Nous avons construit au fil des années une bibliothèque de code générique, mais les méthodes de
mutualisation de ce code étaient encore artisanales.
A&C Consulting a une expérience de commercialisation d'un composant. Ce composant est lié au plugin 4D for OCI : ociLib. C'est un composant qui a été mis au point pour les besoins d'un de nos clients
et ensuite proposé à des développeurs tiers qui étaient intéressés.
Nous travaillons depuis plusieurs années avec une approche générique modulaire
16
17. NOTRE APPROCHE
Voici l'approche qui a été retenue :
Dans un premier temps (première phase), nous nous focalisons sur les "couches basses".
Pour ces couches basses, nous avons retenu une approche modulaire (file system, blob, env, http,
html, io, log, objectTools, tableaux, text, zip, etc…)
Nous avons une volonté de limiter les dépendances (quitte à dupliquer du code entre des composants)
pour des raisons de souplesse mais aussi pour éviter les problèmes de compatibilité liées aux versions.
C'est un travail de longue haleine (135 000 lignes de code) qui se fait petit à petit (un ensemble
logique de code générique correspondant à un module est converti en composant et ce code est retiré
des projets pour être remplacé par le nouveau composant). La documentation est impérative et prend du
temps. La re-factorisation prend du temps (module fourre-tout "UTL" qui nécessite d'être réorganiser
de manière plus logique).
Plus tard, nous prévoyons de mettre en place un composant de niveau intermédiaire qui s'appuiera sur
les composants "couches basses" avec des dépendances fortes.
17
18. CONVENTION DE NOMMAGE
Les conventions de nommage des méthodes sont importantes
Nous utilisons un système de préfixe dans les noms des méthodes pour facilement identifier l'origine
d'une méthode (quel composant). Pour chaque composant nous fixons un code mnémotechnique de
deux à quatre lettres :
HTTP_GET => composant "http_component"
FS_macPathToUnixPath => composant "fs_component"
Dans le composant (base "matrice"), les méthodes qui ne sont pas partagées (et invisibles) contiennent
deux "_" après le préfixe. Ceci permet de s'appuyer sur la convention de nommage pour fixer
rapidement les attributs des méthodes (via l'écran "Modifier attributs globalement") et de les trier
dans l'explorateur.
NOTE : la notion de "partagé entre composant et base hôte" n'est pas visible dans l'explorateur des
méthodes tandis que la notion "visible/invisible" est repérable tout de suite, il est donc judicieux de les
fixer de manière similaire/cohérente.
18
19. DOCUMENTATION
Comment gérer la documentation des composants
La documentation est fondamentale pour la mise en place de composant (rappel le code source et les
commentaires d'un composant compilé ne sont pas accessibles).
L'approche Javadoc qui est une solution proposée dans un autre langage pour répondre une
problématique similaire (documentation d'API) est une bonne source d'inspiration
Principe basé sur des "balises" (ou tags) placées dans les commentaires en entête
L'entête défaut (avec les tags) est généré par une macro.
Ensuite, un peu d'API Pack pour automatiser tout ça…
Un composant maison "xdoc" (s'appuyant sur API Pack) est utilisé pour lire le contenu de chaque
méthode, identifier les balises/tags de documentation, effectuer une conversion en xml. Cet xml peut
ensuite être utilisé pour générer différents formats de documentation (rtf, html, pdf, etc…). Pour
l'instant, nous utilisons 4D Write pour générer un texte stylé rtf (par méthode) qui est ensuite placé
dans la zone "Commentaires" de la méthode. Attention : c'est un hack (reverse engineering) : il n'y a
malheureusement pas, à ma connaissance, de moyen officiel pour alimenter cette zone.
19
20. TESTS
Pourquoi tester et valider son composant et comment y arriver ?
Tester son composant avant de le déployer est important si on veut éviter des "aller-retour" superflus.
Les tests permettent de valider le fonctionnement nominal, de vérifier la non-régression, de valider une
optimisation, de valider le comportement lors d'un changement de version de 4D, de tester sur une
plateforme spécifique, etc…).
L'utilisation de tests unitaires permet de valider le fonctionnement du composant en automatisant les
tests.
Les tests unitaires permettent de réduire les tests manuels qu'un développeur consciencieux devrait
faire (parcourir quelques fonctionnalités de l'application pour vérifier le bon fonctionnement).
Pourquoi ? Parce que ces tests sont répétitifs, qu'ils sont chronophages, et qu'ils sont donc rarement
effectués en pratique.
Les tests unitaires permettent d'éviter (ou toutefois de limiter) le nombre d'anomalies/de bugs
contenues dans une application livrée au client.
La mise en place de ces tests unitaires est aussi chronophage… Donc il faut faire preuve de bon sens et
pragmatisme sur les tests unitaires…
Par exemple, il n'est pas prioritaire d'ajouter des tests unitaires sur une méthode simple et qui marche
bien. Par contre, lors de la correction d'un bug lié à un contexte particulier, il est souvent souhaitable
d'ajouter ce contexte dans les tests unitaires en ajoutant un "test case".
Pour les méthodes complexes, l'ajout de tests unitaires permet de garantir leur bon fonctionnement
(vecteur de test md5 par exemple).
20
21. GESTION DES VERSIONS
Comment gérer les versions de ses composants ?
Une approche simple pour gérer les versions de son composant est de créer une fonction :
xxx_componentVersionGet
Cette fonction retourne la version du composant et on peut commenter l'historique du composant dans
le code source de cette méthode (dans les commentaires).
Si le composant génère des fichiers de logs lors de l'exécution, cette méthode peut être appelée à
l'initialisation du composant et le résultat est envoyé dans le fichier de log.
21
22. INITIALISATION
Les composants on parfois besoin d'être initialisés. Comment gérer cette initialisation ?
Les composants contiennent des variables (et des tableaux notamment) qui nécessitent des déclarations
et parfois des initialisations. Ces variables peuvent être des variables “process/globales” ou
“interprocess”. Ces opérations sont effectuées dans des méthodes qui correspondent : initialisation
process ou inteprocess. Attention l'appel à une méthode d'initialisation effectué de manière
erronée/inopinée ne doit pas vider les tableaux déclarés (qui peuvent être alimentés). Il faut donc gérer,
avec un flag booléen (un flag process et un flag interprocess), le fait que l'initialisation du composant a
été effectué (ou pas).
Ces initialisations peuvent être publiques et explicites (c'est au développeur qui utilise le composant de
faire appel à la méthode d'initialisation de manière appropriée) ou cette initialisation peut être privée,
elle doit alors être implicite et transparente.
22
23. MODELE DE COMPOSANT
Lorsqu'on crée régulièrement des composants, on s'aperçoit qu'on utilise souvent des préférences et quelques
méthodes identiques.
Nous avons standardisé l'organisation d'un composant avec trois répertoires :
- build
- src
- test
Des préférences standard sont définies dans la base "modèle de composant".
Les quelques méthodes standard sont pré-installées dans le modèle (xxx_componentVersionGet,
etc…)
Enfin, une petite "checklist" permet de s'assurer qu'on a bien effectué toutes les tâches de
personnalisation du modèle.
23
24. GESTION DES VERSIONS DE 4D
Comment gérer les composants selon les différentes versions de 4D ?
La transposition directe du code n'est pas toujours évidente (par exemple les caractères de
commentaires ` en v11 et // à partir de v12).
Les composants ont principalement été créés et mis au point en v11. On a donc converti ces
composants en v12 en maintenant les deux versions en parallèle. Les corrections ont été reportées
manuellement au cas par cas.
Certaines commandes ou préférences sont spécifiques à une version. Par exemple la notion
"compilation 64 bits" n'existait pas en v11. Il y a des commandes qui changent de nom (attention à
l'export de code en texte) ou qui apparaissent et peuvent nécessiter une amélioration du code pour en
tenir compte (en v13 : GENERER DIGEST, HTTP Client, etc…).
Le code peut toutefois anticiper et s'adapter. On peut, par exemple, dans une méthode v12 tenir
compte d'un nouveau comportement spécifique à la v13 et s'adapter. Lorsque le composant sera
converti en v13, la méthode s'exécutera de manière optimale sans avoir été modifiée.
Hormis les nouveautés apparues en v13, les composants v12 sont compatibles v13. Les composants
v11 sont compatibles v11 uniquement.
24
25. CONCLUSION
Quel bilan peut-on tirer de notre expérience avec les composants ?
En quelques mots : "les composants : ça marche !"
Les composants sont une solution adaptée, intéressante et élégante pour la gestion du code générique
lorsqu'on doit gérer plusieurs projets.
Une attention particulière doit être apportée sur la documentation pour faciliter l'utilisation du
composant.
25