SOCIÉTÉ

BLOGS

L'effet papillon

11.02.2010
par Olivier Deschanels
Papillon

 En tant que fan de Bénabar, je ne pouvais choisir d'autre titre pour le billet du jour. En effet, la ritournelle petites causes, grandes conséquences est aussi valable dans le monde 4D !
Quelles sont ces petites causes, et quelles conséquences peuvent-elle entraîner ? Le propos que je m'apprête à développer aujourd'hui s'intéresse à ces petits riens qui font toute la différence entre une application correcte et une application performante. Effectivement, comme une maison n'est pas que quatre murs et un toit, un développement 4D n'est pas qu'une suite de méthodes et de formulaires que l'on peut placer un peu au hasard.

 

Commençons par le début et plus exactement par la commande DEBUT SELECTION.
N'avez-vous jamais vu du code tel que ci-dessous :

 

CHERCHER([Personne];[Personne]Ville="Bruxelles")
DEBUT SELECTION([Personne])

Dans ce code, il doit vous paraître évident que la seconde ligne est inutile. En effet, la recherche provoque toujours le changement de sélection courante et d'enregistrement courant. La commande CHERCHER, en outre, fixe le premier enregistrement de la sélection en tant qu'enregistrement courant. Bien sûr, vous savez que la documentation précise noir sur blanc que "Si la sélection courante est vide ou si l'enregistrement courant est déjà le premier enregistrement de la sélection, DEBUT SELECTION ne fait rien." Pour l'observateur qui regarde les données, effectivement cela ne fait rien dans le cas présent. Par contre au niveau du travail de 4D, ce n'est pas totalement neutre et notamment en configuration client-serveur car cette commande provoque bien une requête entre le client et le serveur. Je vois déjà s'éclairer d'un petit sourire amusé le visage de certains qui pensent que ce n'est pas bien grave en soi et qu'il n'y a pas péril en la demeure. Effectivement ce n'est pas bien grave, mais l'accumulation de ce genre d'appels superflus provoque une surcharge inutile qui pourrait empêcher du code plus utile de s'exécuter rapidement.
Je rencontre un emploi équivalent de la commande DEBUT SELECTION dans une application sur deux que j'analyse au quotidien. Mais ce n'est qu'un début ...

Plusieurs indices m'ont mis la puce à l'oreille, j'ouvre un œil ...

Lorsque j'étudie une base 4D, un des premiers points sur ma check-list est la commande CHERCHER DANS SELECTION. Cette commande est une des plus grosses pourvoyeuses d'effet papillon. L'étude de cette commande est un vrai révélateur de la finesse d'écriture du code. Prenons l'exemple suivant, encore une fois très classique :

 

CHERCHER([Objet];[Objet]Propriétaire="Marius")
CHERCHER DANS SELECTION([Objet];[Objet]Nature="Vélo")

Je vous donne tout de suite le code que je préfère dans ce cas :

 

CHERCHER([Objet];[Objet]Propriétaire="Marius";*)
CHERCHER([Objet];&;[Objet]Nature="Vélo")

Vous pensez que ce code est caricatural ... regardez dans les bases autour de vous, et je suis certain que vous trouverez des dizaines de cas comme celui-là.


Pourquoi le premier code ne me plaît pas ? Il y a plusieurs raisons que nous allons détailler :

  • Ce code provoque deux recherches, au lieu d'une seule recherche dans le second cas.
  • Qui dit deux recherches dit deux échanges entre le client et le serveur, donc plus de trafic sur le réseau.
  • Qui dit deux recherches dit deux changements de sélection courante. La sélection qui est créée entre les deux recherches est indispensable pour que la seconde recherche puisse être effectuée ; cependant si sa durée de vie est très brève, son impact est bien présent, au niveau du cache entre autres.
  • Qui dit deux recherches dit deux chargements de l'enregistrement courant. Le chargement d'un enregistrement entre les deux recherches n'est que pure perte de temps. De plus si la table est en lecture-écriture, alors l'enregistrement ainsi chargé doit être libéré par le système avant que la seconde recherche soit effectuée.
  • En scindant sur deux expressions la recherche, vous obligez 4D à suivre votre logique. En utilisant le code permettant de relier deux critères de recherche, nous permettons à 4D d'optimiser le code en fonction de ses règles internes basées sur la logique booléenne et sur sa connaissance des index et de la priorité des index. Ce seul point mérite à lui seul la révision du code...
  • Si par la suite vous désirez faire évoluer votre code pour tirer parti de commandes telles que FIXER DESTINATION RECHERCHE, ou FIXER LIMITE RECHERCHE vous serez dans l'incapacité de les mettre en oeuvre sans avoir à réécrire le code
  • Si vous mettez en place un index composite sur les champs "propriétaire" et "nature", celui-ci sera d'aucun effet dans un cas, et sera un vrai accélérateur dans l'autre cas.

A la vue de cette liste on ne parle plus d'effet papillon, mais d'effet cachalot ! Cependant si vous prenez le soin de reprendre un tant soit peu votre code celui-ci va revivre ; à vous de choisir : Elle est pas belle la vie, Advienne que pourra, Inch' Allah Youpi !

Prenons un troisième exemple :

 

Boucle ($i;1;Enregistrements trouves([Societe]))
    ALLER DANS SELECTION([Societe];$i)
    CHARGER ENREGISTREMENT([Societe])
    ...
    STOCKER ENREGISTREMENT([Societe])
    LIBERER ENREGISTREMENT([Societe])
Fin de boucle

Ce code fait bien trop d'opérations et nous obtenons le même résultat avec le code suivant :

 

Boucle ($i;1;Enregistrements trouves([Societe]))
    ALLER DANS SELECTION([Societe];$i)
    ...
    STOCKER ENREGISTREMENT([Societe])
Fin de boucle
LIBERER ENREGISTREMENT([Societe])

En terme d'échanges entre le client et le serveur la différence est notable. Plus le nombre d'itérations dans la boucle est important, plus le gain sera sensible. La commande ALLER DANS SELECTION charge l'enregistrement ciblé ; ce n'est pas une option. Il est donc inutile de le charger à nouveau. Ce qui serait utile, mais qui n'est pas fait dans ce code, c'est de vérifier que l'enregistrement est bien libre et donc qu'on pourra sauver les modifications. Si par malheur l'enregistrement est verrouillé le code actuel ne le détecte pas. Il faudrait ajouter une structure de contrôle qui elle aurait alors un intérêt a appeler la commande CHARGER ENREGISTREMENT.

La commande LIBERER ENREGISTREMENT ne sert à rien car la commande ALLER DANS SELECTION effectue le même travail au pas suivant de la boucle. Nous avons donc déplacé la commande LIBERER ENREGISTREMENT au delà de la boucle pour libérer l'enregistrement qui reste chargé en fin de boucle. Ici nous réduisons le code à un simple appel au lieu d'un appel dans la boucle.

Par ces deux simples modifications, nous avons réduits de quelques battements d'ailes l'effet papillon.

 

gout

Les grands derrière, les petits devant

Encore un exemple pour vous convaincre ? Le voici :

 

TOUT SELECTIONNER([Societe])
TRIER([Societe];[Societe]Nom;>)
SELECTION VERS TABLEAU([Societe]Nom;◊_societe_nom)

Ici nous trions la sélection avant de recopier les valeurs dans un tableau. Comme nous sommes sur le même champ, il est bien plus performant de faire travailler le client en lui demandant de trier le tableau après avoir récupéré les données plutôt que de charger le serveur en lui demandant une opération supplémentaire inutile. Voici donc le code que je recommande dans cette situation :

 

TOUT SELECTIONNER([Societe])
SELECTION VERS TABLEAU([Societe]Nom;◊_societe_nom)
TRIER TABLEAU(◊_societe_nom)

Dans cet exemple on favorise les petits efforts sur le client plutôt que les grands calculs sur le serveur.

Sans aller pour autant sur la station Mir, il est bon de prendre de temps en temps un peu d'altitude et de regarder son code d'un œil nouveau. Abordons à présent le sujet des génériques. Vous avez forcément dans un coin une générique que vous avez probablement écrite il y a fort longtemps et qui vous apporte satisfaction. Pourquoi mettre en doute le code de ce petit bijou ? C'est vrai que ce code vous sert loyalement depuis des années et que vous avez des choses bien plus importantes à traiter. C'est justement parce que ce code est là depuis des années qu'il mérite une petite cure de jouvence. Vous l'avez écrit en fonction des possibilités de l'époque en terme de commandes, de mémoire, de réseau ... Tout est-il vraiment resté figé depuis ce temps là ? Rien ne peut-il évoluer ?


En donnant un peu d'égard à vos fidèles compagnons, ils ne vous serviront que mieux.

 

Qu'allez-vous trouver dans ces génériques que je pointe du doigt aujourd'hui ? Du code ultra-défensif ("avec ceinture et bretelles" diraient certains) qui coûte à l'usage très cher. Du code obsolète qui pourrait être remplacé avantageusement par la nouvelle commande dédiée à cet usage. Du code faisant trop de requêtes ...
Regardez ensuite l'usage de vos génériques préférées et vérifiez qu'elles ne sont pas redondantes avec le code qui les appelle. Il est fréquent de voir du code où une condition est vérifiée avant l'appel de la générique, puis dans la générique elle même.
Lorsque j'écoute certains développeurs me vanter les mérites de leurs génériques, il me revient en tête ces paroles : Il veut faire coucou de la main ... Et se vautre un peu plus loin.

Mais souvent il y a pire que le générique. Il y a les wrappantes comme il est coutume de les appeler ; c'est vrai que cela fait très chic dans les salons ou l'on cause ! Une wrappante est une méthode qui encapsule une commande pour étendre ses possibilités ou contourner une limitation. Personnellement je n'utilise pratiquement pas de wrappante, par contre j'en rencontre souvent dans mes audits. Lorsque je demande l'intérêt, une réponse très fréquente est : "hum ... attends un peu que je me souvienne". Une wrappante ne doit pas être là à demeure. Une wrappante doit être la cible privilégiée du développeur effectuant une mise à jour de son code, suite, par exemple, à un changement de version. Réduire le nombre de wrappantes est toujours un bienfait pour le code d'une application. Or je trouve des bases dans lesquelles pratiquement tout est mis dans des wrappantes ... je vous laisse imaginer la viscosité induite dans de tels cas.


Il est simple de mettre en place des wrappantes et il est encore plus simple de les oublier par la suite. En effet, comme il ne faut pas se cacher sous les draps, il ne suffit pas de commander des pizzas pour être développeur ! Etre un bon développeur est un art compliqué où il faut utiliser au mieux ce qui est mis à notre disposition malgré les contrariétés que l'on subit. Pour moi, je ne mesure pas la qualité d'un code au nombre de lignes écrites mais bien au contraire à la lisibilité de ce code et son adéquation (voir sa symbiose) avec les possibilités offertes par l'outil.


Relisez mon billet sur les sémaphores. A votre avis combien de fois, ai-je rencontré du code wrappant la fonction sémaphore pour lui adjoindre une temporisation ? Croyez-vous que le code de la wrappante a encore une raison d'être et que ce code est plus performant que le code natif de 4D ? Malheureusement, je rencontre souvent de tels cas.

Benabar dirait voir sans être vu. Wrappantes et génériques sont comme ce voisin du cinquième que l'on croise dans l'escalier mais qu'au fond on ne connaît pas. Prenez votre courage à deux mains ; allez visiter vos voisins et, ainsi revigoré, attaquez vous à vos wrappantes et génériques (et non l'inverse !).

 



Faire le point au contact des éléments

Lorsque j'interroge les développeurs, certains m'affirment avoir une vision très nette de la situation. A l'instar de mon auteur du jour, je répondrai "Pas du tout". Il est très difficile dans une base ayant de nombreux clients d'avoir une vision nette de ce qui s'y passe. Faites l'expérience de lancer l'enregistrement des événements de programmation dans une partie de votre code sur une base en exploitation. Lisez le fichier de log ainsi obtenu et vous serez certainement surpris ! En préparant cet article j'ai de nouveau fait l'expérience et viens de trouver le moyen d'améliorer ce qui me semblait pourtant acquis dans mon propre code. La relecture croisée du code entre développeurs (ou code-review) permet de clarifier nombre de soucis et de prévenir les problèmes. Lorsqu'on est emporté par la fièvre créatrice, il est fréquent que l'on en fasse trop (ou pas assez) car notre vision est focalisée sur l'objectif et n'a pas la possibilité de prendre le recul nécessaire pour englober le code dans son environnement.

Y'a des détails qui trompent pas !

La lecture de la structure d'une base donne une bonne indication de l'effet papillon potentiel. Pour cela j'utilise, en première lecture, des indicateurs très simples tels que le nombre d'index par table ou le type et la nature des liens. Un nombre d'index important dans une table est souvent révélateur d'une méconnaissance de l'utilisation réelle des index par le moteur de 4D. Des liens sur des champs alphanumériques participent aussi activement à l'effet papillon qui nous préoccupe aujourd'hui.

T'as des tics des tremblements

Si à la lecture de ce billet vous avez des doutes sur votre code, j'en suis ravi ! Effectivement, je préfère quelqu'un qui doute et s'assure que tout va bien, que quelqu'un qui campe sur ces certitudes et se prépare à prendre le mur de face ! Mais attention, la chasse aux papillons (qui est une chanson de Brassens et non de Bénabar) est un sport difficile. Car pour obtenir de résultats importants, il ne suffit pas de corriger deux, trois aspects, mais au contraire il faut faire la chasse en permanence jusqu'à ce que cela devienne une seconde nature. Alors, votre code sera étincelant comme dans un show de Maritie et Gilbert Carpentier !

Mais s'énerver c'est légitime, faut se faire respecter des machines !

C'est vrai qu'il m'arrivait fréquemment de m'énerver sur du code car 4D ne faisait pas ce que je voulais. L'expérience aidant, je cherche à présent à travailler dans le sens de 4D quitte à faire au fil des versions l'inverse de ce que je faisais une ou deux versions avant. Mon activité de formateur m'oblige à lire en profondeur et à tester intensément les versions de 4D que je dois présenter, et j'avoue que ce travail est très bénéfique car il éclaire souvent d'une nouvelle lumière des aspects que je n'avais pas perçus lors de la lecture de la documentation. Respecter la logique de 4D, permet de simplifier beaucoup le codage et d'augmenter les performances.


Respecter 4D c'est aussi relire régulièrement la documentation pour se rappeler l'existence de commandes que l'on oublie trop souvent. Prenons par exemple le problème suivant : vous avez une table avec une clef primaire incrémentée via la commande Numérotation automatique. Comment retrouver au mieux le dernier enregistrement créé, et lui seul ? La réponse est simple : il faut retrouver l'enregistrement ayant la plus grande valeur dans la clef primaire. Voici des méthodes (les plus simples) que l'on me propose en général pour résoudre ce cas :

 

TOUT SELECTIONNER ([maTable])
TRIER ([maTable];[maTable]ID;<)
REDUIRE SELECTION([maTable];1)

ou

 

TOUT SELECTIONNER ([maTable])
$id:=Max([maTable]ID)
CHERCHER([maTable];[maTable]ID=$id)

et voici le code que je propose :

 

SCAN INDEX([maTable]ID;1;<)


Si le résultat est fort heureusement identique, le moyen d'arriver au résultat n'est pas du tout le même en terme de travail de la base. Là encore nous avons un bel exemple de l'effet papillon.
Utiliser les commandes adéquates de 4D est un moyen d'optimiser au mieux les ressources à notre disposition et de préserver ce qu'il reste pour d'autres tâches.

Ce sera toujours dimanche et y'aura plus jamais de lundi

Comme Monsieur René, profitez de la vie en profitant des possibilités de 4D. Je me rappelle une publicité pour un magazine qui annonçait "Développeurs 4D, la nuit est faite pour dormir". Effectivement, en cherchant à limiter l'effet papillon, vous aurez moins de soucis et plus de temps pour autre chose. Eviter de lutter contre 4D en respectant son mode de fonctionnement est la voie royale vers le développement durable. J'aurais pu faire l'analogie entre les préceptes que je défends en développement et ceux que l'on nous annonce régulièrement dans le petit écran au sujet de l'écologie, mais nous ne sommes pas à la campagne ...
 

RSS 15 commentaire(s) pour ce billet
C'est vrai que la consultation des wrappantes en général c'est "on s'en fout, on n'y va pas ..."
Je n'ai qu'un seul mot: Grandiose !
Ton billet est pertinent. Tu soulève un problème récurrent en développement et de manière générale dans la vie de tous les jours. Quand ça marche on ne cherche pas plus loin. On est déjà tellement content que ça marche ! C'est la règle en occident, quand d'autres, en Asie par exemple, ont une démarche beaucoup plus riche en remettant plus naturellement en cause le résultat de leur travail. Il ne faudrait jamais se contenter du premier résultat même si il est bon mais plus tôt avoir toujours le soucis de la qualité et de la performance de son code. Avec l'expérience on peut acquérir de bons réflexes et ce genre de document y contribue certainement. merci.

@ Thierry : Merci et bravo pour la citation ad-hoc !

@ P.E. : Merci

@ JM.M. : Merci pour tes compléments d'infomations. Pour avoir diner dernièrement avec mes collègues japonais, je  confirme ta vision de l'Asie. Il m'arrive fréquement de me dire : "C'est pas vrai, c'est moi qui est l'auteur de ces lignes de code" ... Effectivement la pensée évolue avec l'expérience et l'approche aussi.

J'ai une petite question sur le DEBUT SELECTION. J'ai vérifié dans ma plus grosse base et je ne l'utilise pas dans le contexte que tu cites. En revanche je l'utilise après avoir fait UTILISER ENSEMBLE , TRIER et DEBUT SELECTION. C'est mal ?

@ Patrick : Si suite à un UTILISER ENSEMBLE un tri est effectué via la commande TRIER, alors le DEBUT SELECTION ne sert à rien.

OK Merci pour l'info. Je suis en train de modifier des méthodes qui n'ont pas bougé depuis 1995 !!!
Entendu, faut absolument que je monte au grenier afin de retrouver mon épuisette à crevettes heu... à papillons. Merci Olivier
OD, je viens de lire la doc, tu me confirme que le DEBUT SELECTION est également inutile après JOINTURE, SELCTION RETOUR ou CHARGER SUR LIEN?

@ Pierre-Yves : Effectivement après ces trois commandes l'appel de la commande DEBUT SELECTION ne sert à rien.

 

Bonjour, désolé pour le up tardif mais un bon développeur 4D se doit de remettre son code en cause régulierement, n'est-il pas? Le fait d'utiliser TRIER TABLEAU apres SELECTION vers TABLEAU plutot que TRIER avant, et aussi valable sur les champs indéxés ou pas? Le tri d'un index doit quand même être + rapide que celui d'un tableau (surtout en v11). (PS: j'ai bien lu la doc et me rappel que la question n'est valabe que pour 1 seul critère de tri...;)

 @ Pierre-Yves : A mon avis, le TRIER TABLEAU est toujours préférable car il sollicite uniquement le client et libère ainsi le serveur pour d'autres taches. Le tri d'une sélection sur un index n'est pas forcement aussi simple que l'on l'imagine car il ne suffit pas de prendre l'index, mais il faut aussi le réduire aux simples valeurs utiles (celles de la sélection). Si de plus l'index est de type cluster, il faut aller fouiller dans le cluster pour savoir si les valeurs du cluster appartiennent ou non à la sélection. 

Le seul cas ou je me pose la question est lorsque je désire trier l'ensemble de la table et que celle-ci comprend beaucoup d'enregistrements, alors il est possible que le TRI soit plus efficace que le TRIER TABLEAU.

addendum 1: si on fait un TRIER sur un seul critère, mais que l'on a plusieurs tableaux(3,4... n) TRIER TABleau toujours + rapide?
interprétation de la doc pour VALEURS DISTINCTES: si j'ai bien compris il faut un DEBUT SELECTION sauf si on l'utilise après une des commandes sitées ci-dessus. Mais dans le cas ou le champ n'est pas indéxé il faudra faire un CHARGER ENREGISTREMENT (car perte de l'enregistrement courant, §2). Juste?

 # Pierre-Yves : Oui, pour le CHARGER ENREGISTREMENT suite à l'utilisation de la commande VALEURS DISTINCTES sur un champ non indexé. La recherche étant séquentielle elle doit charger les enregistrements les uns après les autres, il est donc logique que l'enregistrement courant soit déchargé.

Je ne vois pas l'intérêt du DEBUT SELECTION.

Poster un nouveau commentaire

  • Les adresses de pages web et de messagerie électronique sont transformées en liens automatiquement.
  • You may use [view:viewname] tags to display listings of nodes.
  • Each email address will be obfuscated in a human readable fashion or (if JavaScript is enabled) replaced with a spamproof clickable link.

Plus d'informations sur les options de formatage