Bridgetown2024-07-09T10:56:30+02:00https://code.strigo.cc/feed.xmlStrigo CodePérégrinations d'un artisan logiciel. Où l'on parle d'architecture logiciel, de regex, de Ruby, de Vim, et bien d'autres sujets passionnants.zRAM, téléchargez de la RAM ! ou presque…2024-05-12T00:00:00+02:002024-05-12T00:00:00+02:00repo://posts.collection/_posts/2024-05-12-zram-telechargez-de-la-ram-ou-presque.md<p>Vous souvenez-vous de ces pubs que l’on voyait sur nos navigateurs au début des
années 2000 ? Celles qui nous proposaient de « télécharger de la RAM » ! Eh bien
c’est devenu réalité (ou presque) avec <a href="https://fr.wikipedia.org/wiki/ZRam" target="_blank" rel="external">zRAM</a> !</p>
<p>zRAM est un module du noyau Linux qui augmente la performance d’un ordinateur en
gérant une mémoire virtuelle (ou mémoire d’échange, <em>swap</em> en anglais) dans la
mémoire vive (RAM) plutôt que sur le disque dur comme c’est traditionnellement
le cas. Pour le dire autrement, zRAM utilise un peu de la RAM pour y stocker une
mémoire d’échange compressée. Grace à la compression, l’espace occupé en mémoire
correspond généralement au tiers de la mémoire allouée. Et c’est là toute
l’astuce ! D’une part, l’accès à la mémoire vive est bien plus rapide qu’un
accès disque, d’autre part la compression/décompression de la mémoire vive
prenant moins de temps que l’utilisation du disque dur sous forme de mémoire
virtuelle, cela permet d’augmenter la réactivité d’un système GNU/Linux.</p>
<h2 id="situation-de-départ">Situation de départ</h2>
<p>Pour éprouver les gains de performance promis par zRAM, j’ai sous la main le
candidat idéal : la compilation de la police de caractères <a href="https://typeof.net/Iosevka/" target="_blank" rel="external">Iosevka</a>
dont je vous avais déjà parlé dans l’article « <a href="/linux/2023/12/27/ligatures-dans-le-terminal/">Ligatures dans le terminal</a> ».</p>
<p>En l’état actuel des choses, disposant d’un processeur à 12 cœurs, et avec une
partition d’échange de 3,8Go sur le disque dur, il me faut compiler cette police
en limitant le nombre de processus concurrents (via l’option <code class="highlighter-rouge">--jCmd 6</code>) et en
réduisant fortement la priorité de ce traitement grâce à la commande <code class="highlighter-rouge">nice</code>.</p>
<p>La commande qui me sert à compiler la police est donc la suivante :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nice -n 10 npm run build -- --jCmd=6 ttf::IosevkaCustom
</code></pre></div></div>
<p>Sans cela, le système gèle totalement pendant plusieurs minutes durant
la compilation, avant d’être en mesure de me rendre la main.</p>
<h2 id="jouons-avec-zram">Jouons avec zRAM</h2>
<p>Tout d’abord, installons les outils nécessaires à la manipulation de zRAM.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install zram-tools # installation de zram
</code></pre></div></div>
<p>Dès lors, un périphérique zRAM est créé, comme on peut le voir avec la commande
<code class="highlighter-rouge">zramctl</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle 256M 0B 0B 0B 12
</code></pre></div></div>
<p>Par défaut, il a une taille de 256Mo, ce qui est bien trop faible vu que nous
disposons de 16Go de RAM. Supprimons donc ce périphérique, pour en créer un à
notre convenance, disons de 8Go.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo zramctl --reset /dev/zram0 # suppression du périphérique zRAM par défaut
sudo zramctl --find --size 8G # création d'un périphérique zRAM de 8Go
</code></pre></div></div>
<p>Voyons ce que cela donne :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle 8G 0B 0B 0B 12
</code></pre></div></div>
<p>Parfait ! Il nous faut à présent faire de ce périphérique une mémoire d’échange,
puis l’activer.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkswap /dev/zram0 # faire de ce périphérique une mémoire d'échange (swap)
sudo swapon -p 100 /dev/zram0 # activer la mémoire d'échange avec une priorité de 100
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle 8G 0B 0B 0B 12 [SWAP]
</code></pre></div></div>
<p>Nous avons à présent deux partitions <em>swap</em> : <code class="highlighter-rouge">/dev/zram0</code> que nous venons
d’activer, et <code class="highlighter-rouge">/dev/dm-1</code> notre partition sur disque.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo swapon
NAME TYPE SIZE USED PRIO
/dev/zram0 partition 8,7G 590,5M 100
/dev/dm-1 partition 3,8G 0B -2
</code></pre></div></div>
<p>Nous pouvons désactiver cette dernière pour que notre système ne soit pas tenté
d’écrire sur cette partition.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo swapoff /dev/dm-1 # désactive la mémoire d'échange sur disque
</code></pre></div></div>
<p>Et voilà !</p>
<h2 id="lépreuve-du-feu">L’épreuve du feu</h2>
<p>Faisons un test, et sans filet : levons toute limitation ! La commande que l’on
s’apprête à lancer sera donc la suivante :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm run build -- ttf::IosevkaCustom
</code></pre></div></div>
<p>Le résultat est probant ! La compilation de la police, qui auparavant saturait
la mémoire, la <em>swap</em> et les processeurs en générant de trop nombreux accès
disque, s’effectue dorénavant sans heurt, en 1min 51s. Alors oui ça <em>swap</em> ! Aux
alentours de 570Mo (2.77G non compressés) sur les 8Go autorisés ; mais aucun gel
d’écran, tout est fluide ! Et ce alors même que les limitations sur le nombre de
processus parallèles autorisés et sur la priorité du traitement ont été levées !
Merveilleux !</p>
<h2 id="persistance">Persistance</h2>
<p>Nous avons constaté que zRAM tenait ses promesses, seulement voilà, ce que nous
avons fait là ne sera pas persisté au redémarrage du système, à moins de nous y
prendre un peu différemment.</p>
<p>Pour persister ces changements, il est nécessaire d’après la <a href="https://wiki.debian.org/ZRam" target="_blank" rel="external">documentation de
Debian</a>, d’éditer le fichier
<code class="highlighter-rouge">/etc/default/zramswap</code> ; dans notre cas comme suit :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/default/zramswap
SIZE=8192
PRIORITY=100
</code></pre></div></div>
<p>Nous pourrions aussi lui indiquer un pourcentage de mémoire vive utilisable par
zRAM avec <code class="highlighter-rouge">PERCENT</code> (qui prendra le dessus sur <code class="highlighter-rouge">SIZE</code>) ; et pourquoi pas changer
d’algorithme de compressions avec <code class="highlighter-rouge">ALGO</code>, pour opter par exemple pour <code class="highlighter-rouge">zstd</code> qui
est annoncé comme plus rapide et offrant un meilleur taux de compression que
<code class="highlighter-rouge">lzo</code>, l’algorithme par défaut. Attention cependant à vérifier que cet
algorithme soit bien supporté par le système.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo cat /sys/block/zram0/comp_algorithm
lzo [lzo-rle] lz4 lz4hc zstd
</code></pre></div></div>
<p>Une fois le fichier <code class="highlighter-rouge">/etc/default/zramswap</code> mis à jour et sauvegardé, il reste à
redémarrer le service <code class="highlighter-rouge">zramswap</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo service zramswap reload
</code></pre></div></div>
<p>Enfin, pour éviter que la partition <em>swap</em> sur disque soit montée au démarrage,
il suffit de commenter la ligne correspondante dans le fichier <code class="highlighter-rouge">/etc/fstab</code> et
redémarrer le démon <code class="highlighter-rouge">systemctl</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ sudo systemctl daemon-reload
</code></pre></div></div>
<p>Au prochain redémarrage, notre nouvelle mémoire d’échange zRAM sera activée et
effective pour notre plus grand plaisir !</p>
<h2 id="bonus">Bonus</h2>
<p>Petit cadeau pour finir, il est possible de personnaliser le moniteur système
<a href="https://fr.wikipedia.org/wiki/Htop" target="_blank" rel="external">htop</a> pour lui faire afficher les accès
disque, les accès réseau, ainsi que la consommation de la mémoire zRAM ! Tout
cela depuis htop lui-même, sans avoir besoin d’éditer le fichier de
configuration à la main ! Pressez la touche <code class="highlighter-rouge">F2</code>, et laissez-vous guider !</p>François VantommeDe l’utilisation des variables d’instance, et de l’intérêt des accesseurs2024-03-09T00:00:00+01:002024-03-09T00:00:00+01:00repo://posts.collection/_posts/2024-03-09-de-l-utilisation-des-variables-d-instance.md<p>Nous sommes nombreux⋅ses à nous interroger sur la pertinence de l’utilisation
directe de variables d’instances au sein d’une classe, ou s’il ne faudrait pas
au contraire passer systématiquement par des accesseurs ? Ruby, comme à son
habitude, nous laisse une grande liberté à ce sujet. Il nous faut alors nous
demander quelles sont les implications et les incidences d’un choix ou d’un
autre ? Que véhicule chacun de ces choix en matière d’intention ? Quelles sont
les recommandations de la communauté et, plus largement, en ce qui concerne
l’art de la programmation orientée objet ?</p>
<h2 id="de-quoi-parle-t-on">De quoi parle-t-on ?</h2>
<p>Prenons dès à présent un exemple pour poser le cadre.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">firstnmae</span><span class="p">:,</span> <span class="n">lastname</span><span class="p">:)</span>
<span class="vi">@firstname</span> <span class="o">=</span> <span class="n">firstname</span>
<span class="vi">@lastname</span> <span class="o">=</span> <span class="n">lastname</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">fullname</span>
<span class="s2">"</span><span class="si">#{</span><span class="vi">@firstname</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="vi">@lastname</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Dans l’exemple ci-dessus, nous utilisons les variables d’instance dans la
méthode <code class="highlighter-rouge">User#fullname</code>. L’auteur⋅ice de ce code s’arrêta, le regarda, et cela
lui sembla juste et bon.</p>
<p>Mais… car oui, il y a un mais, les paroles de Dave Thomas & Andy Hunt, auteurs
de “<em>The Pragmatic Programmer</em>” lui revinrent à l’esprit ! Elles disaient ceci:</p>
<blockquote>
<p>[…] <strong>whenever a module exposes a data structure, you’re coupling all the code
that uses that structure to the implementation of that module</strong>. Where
possible, always use accessor functions to read and write the attributes of
objects. It will make it easier to add functionality in the future.</p>
</blockquote>
<p>Et de poursuivre :</p>
<blockquote>
<p>This use of accessor functions ties in with Meyer’s Uniform Access principle,
described in <em>Object-Oriented Software Construction</em><sup id="fnref:Mey97" role="doc-noteref"><a href="#fn:Mey97" class="footnote" rel="footnote">1</a></sup>, which states
that:</p>
<blockquote>
<p>All services offered by a module should be available through a uniform
notation, which does not betray whether they are implemented through storage
or through computation.</p>
</blockquote>
<p>— The Pragmatic Programmer, Dave Thomas & Andy Hunt</p>
</blockquote>
<p>Et effectivement, nous exposons ici les structures de données portées par notre
classe ! Admettons alors, pour filer notre exemple, que nous souhaitions nous
assurer que la méthode <code class="highlighter-rouge">User#fullname</code> retourne les noms en majuscules et les
prénoms ornés d’une majuscule sur la seule première lettre. En poursuivant sur
notre lancée, nous ferions certainement ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="k">def</span> <span class="nf">fullname</span>
<span class="s2">"</span><span class="si">#{</span><span class="vi">@firstname</span><span class="p">.</span><span class="nf">titleize</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="vi">@lastname</span><span class="p">.</span><span class="nf">capitalize</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Pourquoi pas, mais nous voyons dores et déjà poindre une faiblesse dans cette
approche !</p>
<h2 id="découpler-pour-préparer-lavenir">Découpler pour préparer l’avenir</h2>
<p>En effet, la prochaine demande d’évolution stipulera, dans un souci
d’harmonisation et de cohérence, que les noms et prénoms soient toujours
représentés sous cette forme, qu’ils fussent affichés sous leur forme concaténée
ou individuellement. Dès lors, on se voit obligé de changer d’approche.
Commençons par un petit réusinage :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="nb">attr_reader</span> <span class="ss">:firstname</span><span class="p">,</span> <span class="ss">:lastname</span>
<span class="k">def</span> <span class="nf">fullname</span>
<span class="s2">"</span><span class="si">#{</span><span class="n">firstname</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">lastname</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Pour rappel, le réusinage ne doit en aucun cas changer le comportement initial.
Ce n’est pas le cas ici, puisqu’on retrouve le comportement premier, sans
gestion de la casse. Mais cela nous permet, pour les besoins de l’exercice, de
mettre l’accent sur un point : si nous étions partis dès le début sur cette
voie, celle de l’utilisation systématique d’accesseurs, alors pour répondre au
besoin ici exprimé nous n’aurions eu qu’à adapter nos accesseurs, et cela
présente deux avantages ! Le premier est que, comme l’entièreté du code repose
sur ces accesseurs (l’interface que nous présentons au monde), il n’est
nécessaire de réaliser de changement qu’au seul endroit de leur déclaration, et
non à une myriade d’endroits éparpillés dans la classe. Le second est que cela
nous permet de distinguer structure de données et manipulation de celle-ci ; en
d’autres termes, il nous est possible de présenter la donnée de différentes
manière et de conserver l’originale intacte. Cette dernière affirmation n’est
vérifiée qu’à une seule condition, tout traitement de la donnée fournie à
l’instantiation de l’objet se fera en dehors de l’initialiseur, celui-ci devant
rester le plus basique possible. Prenons un exemple pour illustrer ce point. Au
lieu d’écrire ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="nb">attr_reader</span> <span class="ss">:firstname</span><span class="p">,</span> <span class="ss">:lastname</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">firstname</span><span class="p">:,</span> <span class="n">lastname</span><span class="p">:)</span>
<span class="vi">@firstname</span> <span class="o">=</span> <span class="n">firstname</span><span class="p">.</span><span class="nf">titleize</span>
<span class="vi">@lastname</span> <span class="o">=</span> <span class="n">lastname</span><span class="p">.</span><span class="nf">capitalize</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Nous lui préférerons cela :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">firstname</span><span class="p">:,</span> <span class="n">lastname</span><span class="p">:)</span>
<span class="vi">@firstname</span> <span class="o">=</span> <span class="n">firstname</span>
<span class="vi">@lastname</span> <span class="o">=</span> <span class="n">lastname</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">firstname</span>
<span class="vi">@firstname</span><span class="p">.</span><span class="nf">titleize</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">lastname</span>
<span class="vi">@lastname</span><span class="p">.</span><span class="nf">capitalize</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Dès lors, nous avons la possibilité d’auditer notre code avec beaucoup de
précision, car aucune information n’est perdue. Cela est notamment très utile
lorsque nous avons besoin diagnostiquer un comportement et de distinguer une
donnée mal formée à l’initialisation de l’objet ou un mésusage lors de sa
manipulation.</p>
<p>D’aucuns me rétorqueront qu’en utilisant les variables d’instances, on a
l’assurance que celle-ci ne seront pas utilisées en dehors de la classe qui les
héberge. C’est un point intéressant, mais partiellement faux tant Ruby a une
notion très laxiste de la visibilité.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">firstname: </span><span class="s2">"Ada"</span><span class="p">,</span> <span class="ss">lastname: </span><span class="s2">"Lovelace"</span><span class="p">)</span>
<span class="o">=></span> <span class="c1">#<User:0x00007fbdd11499c0 @firstname="Ada", @lastname="Lovelace"></span>
<span class="n">user</span><span class="p">.</span><span class="nf">instance_variable_get</span><span class="p">(</span><span class="ss">:@firstname</span><span class="p">)</span>
<span class="o">=></span> <span class="s2">"Ada"</span>
</code></pre></div></div>
<p>Par ailleurs, il est tout à fait possible et recommandé de rendre nos accesseurs
privés, non pas pour empêcher strictement leur utilisation — on vient de voir
qu’il est très facile de contourner cela — mais pour exprimer notre intention de
ne pas voir ceux-ci utilisés en dehors de leur classe.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">firstname</span><span class="p">:,</span> <span class="n">lastname</span><span class="p">:)</span>
<span class="vi">@firstname</span> <span class="o">=</span> <span class="n">firstname</span>
<span class="vi">@lastname</span> <span class="o">=</span> <span class="n">lastname</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="nb">attr_reader</span> <span class="ss">:firstname</span><span class="p">,</span> <span class="ss">:lastname</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Et si l’arobase qui préfixe une variable d’instance permet d’un seul coup d’œil
de différencier cette dernière d’une méthode, portant ainsi à notre
compréhension que nous sommes en train de manipuler un attribut de notre objet,
cette même arobase ne nous permet en aucun cas de distinguer un attribut assigné
à l’initialisation d’une variable servant à la mémoïsation.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">User</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">firstname</span><span class="p">:,</span> <span class="n">lastname</span><span class="p">:)</span>
<span class="vi">@firstname</span> <span class="o">=</span> <span class="n">firstname</span>
<span class="vi">@lastname</span> <span class="o">=</span> <span class="n">lastname</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">fullname</span>
<span class="vi">@fullname</span> <span class="o">||=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">firstname</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">lastname</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Ici <code class="highlighter-rouge">@fullname</code> est déclaré pour de la mémoïsation et il serait bien mal avisé
de l’utiliser explicitement plutôt que de passer par la méthode <code class="highlighter-rouge">User#fullname</code>.</p>
<h2 id="résumons-nous">Résumons-nous</h2>
<p>Les accesseurs servent, comme le rappelle Sandy Metz dans “<em>99 Bottles of OOP</em>”,
à encapsuler les données primitives de nos objets. Ce faisant, nous n’exposons
pas les entrailles de nos objets, leurs structures de données internes, au lieu
de cela nous présentons une interface harmonieuse, tout en nous préparant pour
l’avenir qui ne manquera pas de nous bousculer !</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:Mey97" role="doc-endnote">
<p>Bertrand Meyer. Object-Oriented Software Construction. Prentice Hall, Upper Saddle River, NJ, Second, 1997. <a href="#fnref:Mey97" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>François VantommeLigatures dans le terminal2023-12-27T00:00:00+01:002023-12-27T00:00:00+01:00repo://posts.collection/_posts/2023-12-27-ligatures-dans-le-terminal.md<p>Le support des ligatures de la police <a href="https://typeof.net/Iosevka/" target="_blank" rel="external">Iosevka</a>
dans l’émulateur de terminal <a href="https://sw.kovidgoyal.net/kitty/" target="_blank" rel="external">Kitty</a> a
nécessité la collaboration des auteurs de ces deux projets, et n’est pas
nativement présent dans Debian Bullseye (11). Voyons ensemble comment concilier
ces deux-là pour avoir de jolies ligatures dans notre terminal.</p>
<h2 id="iosevka">Iosevka</h2>
<p>Cette police n’est pas incluse dans les dépôts Debian, mais doit être compilée
ou <a href="https://github.com/be5invis/Iosevka/releases/" target="_blank" rel="external">récupérée depuis son dépôt Git</a>.
Iosevka offrant une grande liberté de personnalisation, c’est pourquoi nous
opterons ici pour la compilation.</p>
<h3 id="compilation">Compilation</h3>
<p>Tout d’abord, clonons le dépôt Git.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/be5invis/Iosevka.git
</code></pre></div></div>
<p>Ensuite, copions le fichier de configuration d’exemple.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd Iosevka
cp private-build-plans.sample.toml private-build-plans.toml
</code></pre></div></div>
<p>Nous pouvons à présent adapter <code class="highlighter-rouge">private-build-plans.toml</code> à nos besoins. Vous
pouvez <a href="https://github.com/akarzim/dotfiles/blob/master/home/fonts/iosevka/private-build-plans.toml" target="_blank" rel="external">vous inspirer du mien</a>, ou
si vous préférez, il est aussi possible d’utiliser <a href="https://typeof.net/Iosevka/customizer" target="_blank" rel="external">l’outil de
personnalisation</a>.</p>
<p>À présent, passons à la compilation.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install && npm run build -- --jCmd=9 ttf::IosevkaCustom
</code></pre></div></div>
<p>Notez l’option <code class="highlighter-rouge">--jCmd=9</code> qui offre la possibilité de n’utiliser qu’un nombre
donné de processus en parallèle, ici neuf, ce qui permet de ne pas monopoliser
les 12 cœurs de mon processeur et ainsi de ne pas mettre l’ordinateur au tapis
lors de la compilation !</p>
<h3 id="installation">Installation</h3>
<p>Pour <a href="https://wiki.debian.org/fr/Fonts" target="_blank" rel="external">installer notre police</a>
fraichement compilée, il nous faut tout d’abord la déposer dans le dossier
idoine ; dans notre cas, ce sera <code class="highlighter-rouge">~/.local/share/fonts</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp -R ./dist/IosevkaCustom/TTF ~/.local/share/fonts/IosevkaCustom/
</code></pre></div></div>
<p>Ensuite, il nous faut régénérer le cache des polices.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fc-cache -fv
</code></pre></div></div>
<p>Nous pouvons constater que notre police est bien prise en charge par le système
grace à la commande <code class="highlighter-rouge">fc-list</code> :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>fc-list | grep IosevkaCustom
</code></pre></div></div>
<h3 id="et-nos-ligatures">Et nos ligatures ?</h3>
<p>Faisons un peu d’introspection, voyons comment sont prises en compte nos
ligatures. Pour cela, nous aurons besoin d’un petit utilitaire, <code class="highlighter-rouge">hb-shape</code>,
fourni par la bibliothèque <code class="highlighter-rouge">harfbuzz</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install libharfbuzz-bin
</code></pre></div></div>
<p>Et demandons-lui de nous décomposer la ligature <code class="highlighter-rouge">>=</code> :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hb-shape --show-extents --cluster-level=1 --shapers=ot --features "calt=0,dlig=1" ~/.local/share/fonts/IosevkaCustom/TTF/IosevkaCustom-Regular.ttf '>='
[.g13271.join-r=0+500<138,685,712,-524>|.g13277.join-l=1+500<-362,294,712,-299>]
</code></pre></div></div>
<p>Tout semble se passer pour le mieux ! On constate en effet que les caractères
composant cette ligature utilisent les suffixes <code class="highlighter-rouge">.join-r</code> et <code class="highlighter-rouge">.join-l</code> définis
par Renzhi Li aka. Belleve Invis, l’auteur de Iosevka, dans <a href="https://github.com/be5invis/Iosevka/issues/1007#issuecomment-843425402" target="_blank" rel="external">un échange avec
Kovid Goyal</a>, l’auteur de Kitty.</p>
<p>Il est temps à présent d’installer Kitty !</p>
<h2 id="kitty">Kitty</h2>
<h3 id="installation-1">Installation</h3>
<p>Sous Debian Bullseye (11), l’installation se fait tout naturellement via le
gestionnaire de paquet <code class="highlighter-rouge">apt</code> :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install kitty
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kitty --version
kitty 0.19.3 created by Kovid Goyal
</code></pre></div></div>
<p>Nous voilà avec un Kitty prêt à l’emploi ! Sauf que… le support des ligatures de
la police Iosevka n’a été implémenté qu’au moyen du commit
<a href="https://github.com/kovidgoyal/kitty/commit/e01bb09e8c38d420b015fdeadacda37bd684a47c" target="_blank" rel="external">e01bb09</a> du 4 juin 2021 présent dans la
version <a href="https://sw.kovidgoyal.net/kitty/changelog/#id32" target="_blank" rel="external">0.21.0</a>.</p>
<h3 id="montée-de-version">Montée de version</h3>
<p>Fort heureusement, la dernière version stable de Debian, <a href="https://packages.debian.org/search?keywords=kitty" target="_blank" rel="external">Bookworm (12), inclus
la version 0.26.5 de Kitty</a> !</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Paquet kitty
bullseye (oldstable) (x11): émulateur de terminal, rapide, multifonction, basé sur le GPU
0.19.3-1: amd64 arm64 armel armhf i386 mips64el mipsel ppc64el s390x
bookworm (stable) (x11): émulateur de terminal, rapide, multifonction, basé sur le GPU
0.26.5-5: amd64 arm64 armel armhf i386 mips64el mipsel ppc64el s390x
</code></pre></div></div>
<p>Il nous suffit donc d’indiquer à notre système que nous souhaitons bénéficier de
la version présente sur la liste stable, plutôt que celle proposée par défaut.
C’est là qu’entre en jeu <code class="highlighter-rouge">apt-pinning</code> !</p>
<h3 id="épinglage">Épinglage</h3>
<p>Avez-vous déjà été agacé par le fait que Debian Stable semble toujours ne pas
être à jour ?</p>
<p>Voici comment faire en sorte qu’<code class="highlighter-rouge">apt</code> mélange différentes sources (<em>oldstable</em>,
<em>stable</em>, <em>testing</em> ou <em>unstable</em>). Cela vous permettra de faire fonctionner un
système essentiellement stable, tout en ayant la possibilité d’installer les
dernières versions des paquets qui vous intéressent le plus. Cette technique se
nomme épinglage, aussi appelée <a href="https://jaqque.sbih.org/kplug/apt-pinning.html" target="_blank" rel="external"><em>apt pinning</em></a>.</p>
<h4 id="sourceslist">sources.list</h4>
<p>La première étape consiste à configurer votre <code class="highlighter-rouge">/etc/apt/sources.list</code> pour y
inclure les sources <code class="highlighter-rouge">stables</code>, celles de Debian Bookworm (12).</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/apt/sources.list
# Debian Bullseye
deb http://deb.debian.org/debian bullseye main contrib non-free
deb http://security.debian.org/debian-security bullseye-security main contrib non-free
deb http://deb.debian.org/debian bullseye-updates main contrib non-free
deb http://deb.debian.org/debian bullseye-backports main contrib non-free
# Debian Stable
deb http://deb.debian.org/debian stable main contrib non-free
</code></pre></div></div>
<h4 id="preferences">preferences</h4>
<p>L’étape suivante consiste à créer/éditer votre fichier <code class="highlighter-rouge">/etc/apt/preferences</code>.
Les <a href="https://manpages.debian.org/bookworm/apt/apt_preferences.5.fr.html" target="_blank" rel="external">préférences</a> sont l’endroit où
l’épinglage <code class="highlighter-rouge">apt</code> a lieu. Normalement, c’est la version la plus élevée d’un
paquet disponible qui l’emporte, mais nous allons passer outre en indiquant que
pour tout paquet, on souhaite prioriser <code class="highlighter-rouge">oldstable</code>, sauf pour Kitty, qu’on ira
chercher dans <code class="highlighter-rouge">stable</code>. Pour ce faire, ce dernier aura sa propre règle avec une
priorité d’épinglage plus haute que celle de la règle par défaut.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/apt/preferences
Package: *
Pin: release a=oldstable
Pin-Priority: 700
Package: kitty
Pin: release a=stable
Pin-Priority: 750
</code></pre></div></div>
<h4 id="apt-update">apt update</h4>
<p>Nous sommes maintenant prêts à utiliser <code class="highlighter-rouge">apt update</code>. Cela ajoutera les nouveaux
dépôts à la liste d’<code class="highlighter-rouge">apt</code>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt update
</code></pre></div></div>
<p>Il n’y a plus qu’à installer notre nouvelle version de Kitty.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt -t stable install kitty
</code></pre></div></div>
<p>Notez qu’ici on précise <code class="highlighter-rouge">-t stable</code> pour installer les versions stables des
dépendances de Kitty. Si nous n’avions pas fait cela, <code class="highlighter-rouge">apt</code> aurait râlé, car il
n’aurait pas su résoudre ces dépendances.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kitty --version
kitty 0.26.5 created by Kovid Goyal
</code></pre></div></div>
<h2 id="kitty--iosevka--">Kitty + Iosevka = ❤</h2>
<h3 id="configuration-de-kitty">Configuration de Kitty</h3>
<p>Kitty nous permet de configurer finement le support des polices via
l’instruction <a href="https://sw.kovidgoyal.net/kitty/conf/#opt-kitty.font_features" target="_blank" rel="external">font_features</a>. Cela nous
permet d’activer ou de désactiver des fonctionnalités OpenType supportées par
notre police. Ici, après avoir opté pour notre police Iosevka Custom, nous
précisons à Kitty que nous souhaitons désactiver les alternatives contextuelles
<code class="highlighter-rouge">calt</code>, activer les ligatures discrétionnaires <code class="highlighter-rouge">dlig</code>, et activer le <a href="https://github.com/githubnext/monaspace/blob/main/docs/Texture%20Healing.md" target="_blank" rel="external"><em>texture
healing</em></a> <code class="highlighter-rouge">TXTR</code> tout récemment
<a href="https://github.com/be5invis/Iosevka/commit/cfb3826680f2241219cdd9b0afdf0d6d381dcca7" target="_blank" rel="external">supporté par Iosevka</a> à titre
expérimental.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># ~/.config/kitty/kitty.conf
font_family Iosevka Custom
bold_font Iosevka Custom Bold
italic_font Iosevka Custom Italic
bold_italic_font Iosevka Custom Bold Italic
font_features Iosevka-Custom -calt +dlig +TXTR
</code></pre></div></div>
<h3 id="rendu-final">Rendu final</h3>
<p>Faisons un petit essai :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>print -rlP -- '-<< --< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>=' '=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=' '<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __' '<~~ %F{red}</ </> />%f ~~> == != %F{blue}/= ~=%f <> === !== !=== %F{blue}=/= =!=%f' '<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>' '(* *) %F{red}/* */%f [| |] {| |} ++ +++ %F{red}\/ /\%f \- -| <!-- <!---'
</code></pre></div></div>
<p>Et voici le résultat !</p>
<p><img src="/images/2023/12/27/kitty-iosevka-ligatures.png" alt="kitty iosevka ligatures" class="mx-auto" loading="lazy" /></p>François VantommeRails et les value objects2022-11-25T00:00:00+01:002022-11-25T00:00:00+01:00repo://posts.collection/_posts/2022-11-25-rails-et-les-value-objects.md<p>Les entrailles d’un framework cachent parfois des bouts de code fort
intéressants ! C’est le cas de la méthode <code class="highlighter-rouge">composed_of</code> du module
<code class="highlighter-rouge">ActiveRecord::Aggregations</code> qui par plusieurs aspects va nous intéresser
aujourd’hui : elle nous permet d’introduire une notion importante d’architecture
logiciel, les <em>value objects</em> ; et de revenir sur 10 ans de rebondissements
autour de cette méthode ! Sortez les popcorns 🍿</p>
<h2 id="une-vie-mouvementée">Une vie mouvementée</h2>
<p>Nous sommes en juin 2012, Rails arbore fièrement sa version 3.2 ! Et <a href="https://blog.plataformatec.com.br/2012/06/about-the-composed_of-removal/" target="_blank" rel="external">dans un
post</a>, faisant suite à une <a href="https://github.com/rails/rails/pull/6743" target="_blank" rel="external"><abbr title="Pull Request">PR</abbr> de Steve
Klabnik</a>, Rafael França nous explique
pourquoi <code class="highlighter-rouge">composed_of</code> sera prochainement déprécié, puis retiré à compter de la
version 4.0 du framework.</p>
<p>Les raisons sont une complexité superflue pour une méthode rarement utilisée
qu’on pourrait qualifier de cosmétique (nous y reviendrons), et de multiples
bugs relatifs à cette méthode dans le framework à l’époque.</p>
<p>Seulement, tout ne se passa pas comme prévu, et deux mois plus tard, en août
2012…</p>
<blockquote>
<p>We have decided to stop introducing <abbr title="Application Programming Interface">API</abbr> deprecations in all point releases
going forward. From now on, it’ll only happen in majors/minors.</p>
<p>— @Rails, Twitter, 1er août 2012</p>
</blockquote>
<p>La décision fut alors prise de réintroduire cette méthode, toujours présente à
ce jour dans la version 7.0 de Rails ! Cette méthode et la documentation qui lui
est associée reçoivent d’ailleurs toujours des améliorations, comme le montre
cette <a href="https://github.com/rails/rails/pull/45877" target="_blank" rel="external"><abbr title="Pull Request">PR</abbr> de Neil Carvalho</a> datant de
septembre 2022.</p>
<p>Mais alors, à quoi peut bien servir cette méthode méconnue qui a bien failli
disparaitre ?</p>
<h2 id="déclarez-vos-objets-de-valeur">Déclarez vos objets de valeur</h2>
<p>La méthode <code class="highlighter-rouge">composed_of</code> du module <code class="highlighter-rouge">ActiveRecord::Aggregations</code> permet de
manipuler des <em>value objects</em>, c’est-à-dire des objets ayant pour seule vocation
que de véhiculer une valeur. Un <em>value object</em> a la particularité d’être
identifiable par la valeur qu’il véhicule et non pas par un identifiant. En
d’autres termes, deux <em>value objects</em> sont égaux s’ils représentent la même
valeur. Autre condition nécessaire, <a href="https://wiki.c2.com/?ValueObjectsShouldBeImmutable=" target="_blank" rel="external">un <em>value object</em> se doit d’être
immuable</a>. La notion de <em>value object</em> est
très présente dans la littérature portant sur le Domain Driven Design. Un
excellent article de <a href="https://www.sitepoint.com/ddd-for-rails-developers-part-2-entities-and-values/" target="_blank" rel="external">Victor Savkin</a> fait
d’ailleurs le lien entre Rails et <abbr title="Domain Driven Design">DDD</abbr>.</p>
<h3 id="un-cas-dusage">Un cas d’usage</h3>
<p>Prenons un exemple qui parlera à tout le monde : la manipulation de valeurs
monétaires. Il arrive assez fréquemment que l’on ait à manipuler des montants et
des devises, que ce soit dans le cadre d’une application e-commerce, ou tout
simplement l’établissement d’une facture. Dans ce cas, nous avons pris
l’habitude de stocker en base, dans deux champs distincts mais étroitement liés,
ce montant (appelons-le <code class="highlighter-rouge">amount</code>) et la devise associée (nommons-la <code class="highlighter-rouge">currency</code>).</p>
<p>Un <em>value object</em> nous permettra ici de manipuler ces deux informations au sein
d’une même représentation. Nous pourrions imaginer la chose comme ceci, par
exemple :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Money</span>
<span class="nb">attr_reader</span> <span class="ss">:amount</span><span class="p">,</span> <span class="ss">:currency</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">amount</span><span class="p">,</span> <span class="n">currency</span> <span class="o">=</span> <span class="s2">"EUR"</span><span class="p">)</span>
<span class="vi">@amount</span> <span class="o">=</span> <span class="n">amount</span>
<span class="vi">@currency</span> <span class="o">=</span> <span class="n">currency</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Nous avons là un objet <code class="highlighter-rouge">Money</code> qui nous permet de manipuler des valeurs
monétaires, et nous assure de toujours conserver ce lien entre montant et
devise, l’un n’allant pas sans l’autre d’un point de vue fonctionnel. Seulement,
il nous manque un petit quelque chose pour en faire un <em>value object</em> : nous
avons besoin de définir l’égalité entre deux objets de cette classe !</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Money</span>
<span class="kp">include</span> <span class="no">Comparable</span>
<span class="c1"># …</span>
<span class="k">def</span> <span class="nf">==</span><span class="p">(</span><span class="n">other_money</span><span class="p">)</span>
<span class="n">amount</span> <span class="o">==</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">amount</span> <span class="o">&&</span> <span class="n">currency</span> <span class="o">==</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">currency</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Grace au module <code class="highlighter-rouge">Comparable</code> que l’on vient d’inclure, et à la méthode <code class="highlighter-rouge">==</code>,
nous voici en mesure de comparer deux objets de la classe <code class="highlighter-rouge">Money</code> :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">==</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span>
<span class="o">=></span> <span class="kp">true</span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">!=</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"USD"</span><span class="p">)</span>
<span class="o">=></span> <span class="kp">true</span>
</code></pre></div></div>
<p>Mais un <em>value object</em> ne se limite pas forcément à l’encapsulation d’une ou
plusieurs valeurs, il peut aussi présenter un ensemble de méthodes qui lui sont
propres ! Ici nous pourrions par exemple souhaiter convertir un montant dans une
autre devise, ou encore comparer deux montants déclarés dans des devises
différentes.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Money</span>
<span class="no">EXCHANGE_RATES</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"EUR_TO_JPY"</span> <span class="o">=></span> <span class="mi">146</span> <span class="p">}</span>
<span class="c1"># …</span>
<span class="k">def</span> <span class="nf">exchange_to</span><span class="p">(</span><span class="n">other_currency</span><span class="p">)</span>
<span class="n">exchanged_amount</span> <span class="o">=</span> <span class="p">(</span><span class="n">amount</span> <span class="o">*</span> <span class="no">EXCHANGE_RATES</span><span class="p">[</span><span class="s2">"</span><span class="si">#{</span><span class="n">currency</span><span class="si">}</span><span class="s2">_TO_</span><span class="si">#{</span><span class="n">other_currency</span><span class="si">}</span><span class="s2">"</span><span class="p">]).</span><span class="nf">floor</span>
<span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">exchanged_amount</span><span class="p">,</span> <span class="n">other_currency</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf"><</span><span class="o">=></span><span class="p">(</span><span class="n">other_money</span><span class="p">)</span>
<span class="k">if</span> <span class="n">currency</span> <span class="o">==</span> <span class="n">other_money</span><span class="p">.</span><span class="nf">currency</span>
<span class="n">amount</span> <span class="o"><=></span> <span class="n">other_money</span><span class="p">.</span><span class="nf">amount</span>
<span class="k">else</span>
<span class="n">amount</span> <span class="o"><=></span> <span class="n">other_money</span><span class="p">.</span><span class="nf">exchange_to</span><span class="p">(</span><span class="n">currency</span><span class="p">).</span><span class="nf">amount</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Notons que notre objet est immuable, la méthode <code class="highlighter-rouge">exchange_to</code> retourne donc une
nouvelle instance de notre classe <code class="highlighter-rouge">Money</code>.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">==</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span>
<span class="o">=></span> <span class="kp">true</span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">).</span><span class="nf">exchange_to</span><span class="p">(</span><span class="s2">"JPY"</span><span class="p">)</span>
<span class="o">=></span> <span class="c1">#<Money:0x00007faee7162f68 @amount=730, @currency="JPY"></span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">==</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">730</span><span class="p">,</span> <span class="s2">"JPY"</span><span class="p">)</span>
<span class="o">=></span> <span class="kp">true</span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">)</span> <span class="o">></span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="s2">"JPY"</span><span class="p">)</span>
<span class="o">=></span> <span class="kp">true</span>
</code></pre></div></div>
<h3 id="et-composed_of-dans-tout-ça">Et composed_of dans tout ça ?</h3>
<p>La méthode de classe <code class="highlighter-rouge">composed_of</code> appliquée sur un modèle <code class="highlighter-rouge">ActiveRecord</code> nous
permet de lier les attributs de celui-ci pour les manipuler sous la forme d’un
<em>value object</em>. Voici un exemple d’utilisation de notre classe <code class="highlighter-rouge">Money</code> :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># == Schema Information</span>
<span class="c1">#</span>
<span class="c1"># Table name: invoices</span>
<span class="c1">#</span>
<span class="c1"># id :integer not null, primary key</span>
<span class="c1"># total_amount :decimal(, )</span>
<span class="c1"># total_currency :string</span>
<span class="k">class</span> <span class="nc">Invoice</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">composed_of</span> <span class="ss">:total</span><span class="p">,</span>
<span class="ss">class_name: </span><span class="s2">"Money"</span><span class="p">,</span>
<span class="ss">mapping: </span><span class="p">{</span> <span class="ss">total_amount: :amount</span><span class="p">,</span> <span class="ss">total_currency: :currency</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Ainsi, nous pouvons directement utiliser une instance de la classe <code class="highlighter-rouge">Money</code> à
travers l’attribut <code class="highlighter-rouge">total</code>, et ce en lecture comme en écriture !</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="n">invoice</span> <span class="o">=</span> <span class="no">Invoice</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">total: </span><span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">))</span>
<span class="o">=></span> <span class="c1">#<Invoice id: nil, total_amount: 0.5e1, total_currency: "EUR"></span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total</span>
<span class="o">=></span> <span class="c1">#<Money:0x00007f1d1006b038 @amount=5, @currency="EUR"></span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total</span> <span class="o">=</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">500</span><span class="p">,</span> <span class="s2">"JPY"</span><span class="p">)</span>
<span class="o">=></span> <span class="c1">#<Money:0x000055eca216b658 @amount=500, @currency="JPY"></span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total_amounnt</span>
<span class="o">=></span> <span class="mf">0.5e3</span>
<span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">></span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total_currency</span>
<span class="o">=></span> <span class="s2">"JPY"</span>
</code></pre></div></div>
<p>Très utile cette méthode, et cela clarifie par la même occasion notre
intention ! Notre code s’en trouve plus explicite, et plus facile à comprendre
et à maintenir. De plus, nous limitons les responsabilités de notre modèle en
cloisonnant dans des <em>value objects</em> les méthodes qui leur sont propres.</p>
<p>Mais alors, pourquoi vouloir la supprimer de Rails ?</p>
<h2 id="valeur-ajoutée--maintenabilité">Valeur ajoutée & maintenabilité</h2>
<p>Tout est dans la mesure. Cette méthode n’est au final qu’un sucre syntaxique,
une fonctionnalité cosmétique, et celle-ci a un coût, notamment en termes de
maintenabilité pour l’équipe de développement du framework. Ce coût est loin
d’être négligeable, à en croire les multiples remontées de bugs qui lui sont
imputées, et il convient dans ce cas de peser le pour et le contre afin de
choisir entre conserver cette fonctionnalité ou la supprimer.</p>
<p>L’un des arguments de poids à l’encontre de cette méthode, est le fait de devoir
lui passer des <em>procs</em> et des <em>hashes</em> pour obtenir magiquement un comportement
qui pourrait être décrit de manière bien plus explicite avec un simple objet
Ruby. Arrêtons-nous un moment pour prendre deux exemples.</p>
<p>Dans le cas le plus simple, celui d’un attribut unique, nous pourrions nous
contenter d’un <em>serializer</em>. Admettons que dans notre exemple précédent, nous
ayons choisi de faire fi de la devise. Nous pourrions ainsi écrire ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">MoneySerializer</span>
<span class="k">def</span> <span class="nf">dump</span><span class="p">(</span><span class="n">money</span><span class="p">)</span>
<span class="n">money</span><span class="p">.</span><span class="nf">amount</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">load</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span>
<span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Invoice</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">serialize</span> <span class="ss">:total_amount</span><span class="p">,</span> <span class="no">MoneySerializer</span><span class="p">.</span><span class="nf">new</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Autre approche, nous pourrions aussi faire appel à de simples accesseurs, comme
ceci par exemple :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Invoice</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="k">def</span> <span class="nf">total</span>
<span class="vi">@total</span> <span class="o">||=</span> <span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">total_amount</span><span class="p">,</span> <span class="n">total_currency</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">total</span><span class="o">=</span><span class="p">(</span><span class="n">money</span><span class="p">)</span>
<span class="nb">self</span><span class="p">[</span><span class="ss">:total_amount</span><span class="p">]</span> <span class="o">=</span> <span class="n">money</span><span class="p">.</span><span class="nf">amount</span>
<span class="nb">self</span><span class="p">[</span><span class="ss">:total_currency</span><span class="p">]</span> <span class="o">=</span> <span class="n">money</span><span class="p">.</span><span class="nf">currency</span>
<span class="vi">@total</span> <span class="o">=</span> <span class="n">money</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Ces deux exemples nous montrent à quel point il est facile d’obtenir le même
résultat, sans la magie de <code class="highlighter-rouge">composed_of</code>, mais surtout avec beaucoup plus de
clarté, j’en veux pour preuve cet exemple tiré de la documentation
d’ActiveRecord :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NetworkResource</span> <span class="o"><</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
<span class="n">composed_of</span> <span class="ss">:cidr</span><span class="p">,</span>
<span class="ss">class_name: </span><span class="s1">'NetAddr::CIDR'</span><span class="p">,</span>
<span class="ss">mapping: </span><span class="p">[</span> <span class="sx">%w(network_address network)</span><span class="p">,</span> <span class="sx">%w(cidr_range bits)</span> <span class="p">],</span>
<span class="ss">allow_nil: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">constructor: </span><span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">network_address</span><span class="p">,</span> <span class="n">cidr_range</span><span class="o">|</span> <span class="no">NetAddr</span><span class="o">::</span><span class="no">CIDR</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">network_address</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">cidr_range</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span> <span class="p">},</span>
<span class="ss">converter: </span><span class="no">Proc</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">value</span><span class="o">|</span> <span class="no">NetAddr</span><span class="o">::</span><span class="no">CIDR</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">value</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span> <span class="p">?</span> <span class="n">value</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span> <span class="p">:</span> <span class="n">value</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>On comprend rapidement ici que maintenir ce code et le tester sera des plus
pénibles !</p>
<p>Ceci étant, dans sa configuration la plus simple, ce petit sucre syntaxique
reste attirant à l’œil et, sans convaincre celles et ceux fortement attachés aux
principes du Domain Driven Design, devrait séduire les plus Rails-istes d’entre
nous — Il suffit de ne pas être trop regardant de ce qu’il y a sous le capot ;)</p>
<h3 id="petit-bonus">Petit bonus</h3>
<p>Puisque nous parlons d’ActiveRecord, qu’en est-il du requêtage de ces
attributs ? Eh bien tout semble se passer le plus intuitivement du monde :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Invoice</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">total: </span><span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="mi">42</span><span class="p">,</span> <span class="s2">"EUR"</span><span class="p">))</span>
</code></pre></div></div>
<p>Si vous avez choisi de vous passer de <code class="highlighter-rouge">composed_of</code>, il s’agira simplement
d’être explicite là aussi, à l’aide d’une méthode de classe par exemple :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">costing</span><span class="p">(</span><span class="n">money</span><span class="p">)</span>
<span class="n">where</span><span class="p">(</span><span class="ss">total_amount: </span><span class="n">money</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span> <span class="ss">total_currency: </span><span class="n">money</span><span class="p">.</span><span class="nf">currency</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Le coût d’un code explicite ne semble pas excessif. Surtout au regard des <a href="https://github.com/rails/rails/pull/6743/files" target="_blank" rel="external">768
lignes de code</a> nécessaires à cette
fonctionnalité cosmétique.</p>
<h2 id="du-discernement">Du discernement</h2>
<p>Cet exemple nous montre une nouvelle fois à quel point <a href="/rails/2020/11/12/rails-n-est-pas-simple/">Rails n’est pas
simple !</a> Il nous faut donc rester sur nos gardes, et prendre la mesure des
choix techniques que nous faisons. Aussi insignifiants qu’ils puissent nous
paraitre à première vue, leurs répercussions peuvent être considérable avec le
temps, en particulier sur la maintenabilité, la pérennité et la testabilité de
nos applications.</p>François VantommeRecherche plein texte avec PostgreSQL2022-09-29T00:00:00+02:002022-09-29T00:00:00+02:00repo://posts.collection/_posts/2022-09-29-recherche-plein-texte-avec-postgresql.md<p>J’ai récemment eu l’opportunité de travailler pour un client qui souhaitait
mettre en place une recherche plus pertinente sur son logiciel. L’occasion rêvée
de regarder du côté de la recherche plein texte (<em>full-text</em>) proposée
nativement par PostgreSQL !</p>
<h2 id="révélez-votre-meilleur-profil">Révélez votre meilleur profil</h2>
<p>La recherche était effectuée sur des profils : un intitulé, une description,
rien de bien exotique.</p>
<p>Historiquement la recherche de profil se faisait très sommairement sur la base
de mots-clés et remontait des résultats peu pertinents. On recherchait, via
Ransack, le terme exact faisant tout ou partie d’un mot, dans l’intitulé et
la description des profils.</p>
<p>Par ailleurs, les mots-clés n’étaient utilisés que pour filtrer les résultats,
le tri, lui, était effectué selon le critère de tri choisi (par défaut : la date
de publication, les plus récents en premier).</p>
<p>Exemple : une recherche avec le mot clé « app » fera ressortir les profils dans
lesquels figure le mot <code class="highlighter-rouge">app</code>, mais aussi <code class="highlighter-rouge">application</code>, <code class="highlighter-rouge">appétit</code> ou encore
<code class="highlighter-rouge">rapport</code>.</p>
<h2 id="recherche-plein-texte">Recherche plein texte</h2>
<p>On comprend dès lors que cela manque de précision et que la pertinence n’est pas
au rendez-vous. C’est là que PostgreSQL entre en jeu, et notamment ce que l’on
nomme la recherche plein texte. Voici ce que nous en dit la documentation :</p>
<blockquote>
<p>La recherche plein texte (ou plus simplement la recherche de texte) permet de
sélectionner des <strong>documents</strong> en langage naturel qui satisfont une
<strong>requête</strong> et, en option, de les trier par intérêt suivant cette requête. Le
type le plus fréquent de recherche concerne la récupération de tous les
documents contenant les <strong>termes de recherche</strong> indiqués et de les renvoyer
dans un ordre dépendant de leur <strong>similarité</strong> par rapport à la requête.</p>
<p>— Chapitre 12. Recherche plein texte</p>
</blockquote>
<p>Il va donc nous être possible de formuler une requête <abbr title="Structured Query Language">SQL</abbr>, un peu velue certes,
qui nous permettra d’exprimer ce qui pour nous est digne d’intérêt.</p>
<h3 id="un-peu-de-vocabulaire">Un peu de vocabulaire</h3>
<p>À ce niveau, il est important de s’arrêter quelques instants sur le vocabulaire
pour comprendre de quoi nous parlons exactement.</p>
<p>Un <strong>document</strong> est l’unité de recherche, c’est-à-dire ce sur quoi nous
souhaitons effectuer notre recherche. Cela peut être un simple champ d’une table
de la base de données, ou la concaténation de plusieurs champs, éventuellement
issus de plusieurs tables. Dans notre exemple, ce qui nous intéresse c’est
l’intitulé et la description des profils.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="p">(</span><span class="n">coalesce</span><span class="p">(</span><span class="nv">"profiles"</span><span class="p">.</span><span class="nv">"label"</span><span class="p">::</span><span class="nb">text</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span> <span class="o">||</span> <span class="n">coalesce</span><span class="p">(</span><span class="nv">"profiles"</span><span class="p">.</span><span class="nv">"description"</span><span class="p">::</span><span class="nb">text</span><span class="p">,</span> <span class="s1">''</span><span class="p">))</span> <span class="k">AS</span> <span class="n">document</span>
<span class="k">FROM</span> <span class="nv">"profiles"</span>
</code></pre></div></div>
<p>On utilise ici <code class="highlighter-rouge">coalesce()</code> pour éviter de manipuler <code class="highlighter-rouge">NULL</code> lors de la
concaténation, ce qui conduirait à un résultat nul pour l’ensemble du document.</p>
<p>Une <strong>requête</strong> (<code class="highlighter-rouge">tsquery</code>) se fait sur un document (<code class="highlighter-rouge">tsvector</code>) à l’aide de
l’opérateur <code class="highlighter-rouge">@@</code>.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">to_tsvector</span><span class="p">(</span><span class="s1">'Portez ce vieux whisky au juge blond qui fume'</span><span class="p">)</span> <span class="o">@@</span> <span class="n">to_tsquery</span><span class="p">(</span><span class="s1">'vieux & juge'</span><span class="p">)</span>
<span class="o">+</span><span class="c1">----------+</span>
<span class="o">|</span> <span class="o">?</span><span class="k">column</span><span class="o">?</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">----------|</span>
<span class="o">|</span> <span class="k">True</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">----------+</span>
</code></pre></div></div>
<p>Remarquez que l’on utilise ici <code class="highlighter-rouge">to_tsvector()</code> et <code class="highlighter-rouge">to_tsquery()</code>. En effet, nous
ne manipulons pas de simples textes. Un <code class="highlighter-rouge">tsquery</code> contient des termes de
recherche qui doivent déjà être des lexèmes normalisés, et peut combiner
plusieurs termes en utilisant les opérateurs AND, OR, NOT et FOLLOWED BY.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">to_tsquery</span><span class="p">(</span><span class="s1">'vieux & whisky'</span><span class="p">)</span>
<span class="o">+</span><span class="c1">--------------------+</span>
<span class="o">|</span> <span class="n">to_tsquery</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">--------------------|</span>
<span class="o">|</span> <span class="s1">'vieux'</span> <span class="o">&</span> <span class="s1">'whiski'</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">--------------------+</span>
</code></pre></div></div>
<p>Notez le mot <code class="highlighter-rouge">whisky</code> remplacé par le lexème <code class="highlighter-rouge">whiski</code>.</p>
<p>Pour pouvoir faire usage de ce <code class="highlighter-rouge">tsquery</code>, notre document lui sera présenté sous la
forme d’un <code class="highlighter-rouge">tsvector</code>, c’est-à-dire une version pré-traitée et compacte de
celui-ci.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="n">to_tsvector</span><span class="p">(</span><span class="s1">'Portez ce vieux whisky au juge blond qui fume'</span><span class="p">)</span>
<span class="o">+</span><span class="c1">-----------------------------------------------------------------------------------+</span>
<span class="o">|</span> <span class="n">to_tsvector</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-----------------------------------------------------------------------------------|</span>
<span class="o">|</span> <span class="s1">'au'</span><span class="p">:</span><span class="mi">5</span> <span class="s1">'blond'</span><span class="p">:</span><span class="mi">7</span> <span class="s1">'ce'</span><span class="p">:</span><span class="mi">2</span> <span class="s1">'fume'</span><span class="p">:</span><span class="mi">9</span> <span class="s1">'juge'</span><span class="p">:</span><span class="mi">6</span> <span class="s1">'portez'</span><span class="p">:</span><span class="mi">1</span> <span class="s1">'qui'</span><span class="p">:</span><span class="mi">8</span> <span class="s1">'vieux'</span><span class="p">:</span><span class="mi">3</span> <span class="s1">'whiski'</span><span class="p">:</span><span class="mi">4</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-----------------------------------------------------------------------------------+</span>
</code></pre></div></div>
<p>Notre document est ici découpé en lexèmes présentés dans l’ordre alphabétique et
suivis des indices auxquels on les retrouve dans le document. Pourtant, ces
lexèmes n’ont pas l’air si normalisés que ça… c’est parce que la recherche plein
texte se base sur une configuration qui par défaut considère qu’il s’agit d’un
document en anglais. Sans entrer trop vite dans le détail de la configuration,
sachez qu’on peut préciser un argument supplémentaire à nos fonctions
<code class="highlighter-rouge">to_tsquery()</code> et <code class="highlighter-rouge">to_tsvector()</code>, voyez :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- to_tsvector([ config regconfig, ] document text) returns tsvector</span>
<span class="k">SELECT</span> <span class="n">to_tsvector</span><span class="p">(</span><span class="s1">'french'</span><span class="p">,</span> <span class="s1">'Portez ce vieux whisky au juge blond qui fume'</span><span class="p">)</span>
<span class="o">+</span><span class="c1">---------------------------------------------------------+</span>
<span class="o">|</span> <span class="n">to_tsvector</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">---------------------------------------------------------|</span>
<span class="o">|</span> <span class="s1">'blond'</span><span class="p">:</span><span class="mi">7</span> <span class="s1">'fum'</span><span class="p">:</span><span class="mi">9</span> <span class="s1">'jug'</span><span class="p">:</span><span class="mi">6</span> <span class="s1">'port'</span><span class="p">:</span><span class="mi">1</span> <span class="s1">'vieux'</span><span class="p">:</span><span class="mi">3</span> <span class="s1">'whisky'</span><span class="p">:</span><span class="mi">4</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">---------------------------------------------------------+</span>
</code></pre></div></div>
<p>On comprend à présent que l’opérateur <code class="highlighter-rouge">@@</code> cherchera dans ce <code class="highlighter-rouge">tsvector</code> la
présence (ou l’absence) de certains lexèmes décrits par notre <code class="highlighter-rouge">tsquery</code>.
L’intérêt de passer par des lexèmes normalisés est de pouvoir découvrir les
différentes formes d’un même mot sans avoir à toutes les préciser.</p>
<p>Les plus attentifs d’entre vous auront remarqué la disparition des termes
« ce », « au » et « qui ». Il s’agit là d’un des effets de la configuration
choisie qui ignore tout simplement certains mots jugés trop génériques et non
pertinents.</p>
<h2 id="une-recherche-aux-petits-oignons">Une recherche aux petits oignons</h2>
<p>Mais alors, comment fonctionne cette configuration ?</p>
<blockquote>
<p>En interne, la fonction to_tsvector appelle un <strong>analyseur</strong> qui casse le
texte en <strong>jetons</strong> et affecte un <strong>type</strong> à chaque jeton. Pour chaque jeton,
une liste de <strong>dictionnaires</strong> est consultée, liste pouvant varier suivant le
type de jeton. Le premier dictionnaire qui reconnaît le jeton émet un ou
plusieurs <strong>lexèmes</strong> pour représenter le jeton. Le choix de l’analyseur, des
dictionnaires et des types de jetons à indexer est déterminé par la
configuration de recherche plein texte sélectionnée. Il est possible d’avoir
plusieurs configurations pour la même base, et des configurations prédéfinies
sont disponibles pour différentes langues.</p>
<p>— 12.3. Contrôler la recherche plein texte</p>
</blockquote>
<p>Ainsi, il nous est possible de choisir une configuration préexistante, comme
<code class="highlighter-rouge">french</code> dans l’exemple précédent, mais aussi d’élaborer une configuration
spécialement adaptée à nos besoins. Et cela tombe bien, car nous en aurons
justement besoin ! Prenons un exemple.</p>
<h3 id="cas-particulier-c-c-net">Cas particulier : C++, C#, .net</h3>
<p>La recherche par dictionnaire, lorsque celle-ci est effectuée à l’aide de l’une
des configurations mises à notre disposition, ignore un certain nombre de
symboles (espaces, ponctuation) et les mots jugés non pertinents (<em>stopwords</em> ;
particules, mots de liaison). Or, certains langages de programmation, qui
peuvent très bien faire l’objet d’une recherche, contiennent l’un ou l’autre,
voire les deux !</p>
<p>Pour palier cela, il nous faut constituer un <strong>thésaurus</strong> personnalisé, le
porter à la connaissance de PostgreSQL, le lier à un dictionnaire lui aussi
personnalisé, car il ne devra pas discriminer les <em>stopwords</em>, et enfin altérer
la configuration du français pour les caractères ASCII et les symboles.</p>
<p>Reprenons. Les dictionnaires sont utilisés pour éliminer les mots qui ne
devraient pas être considérés dans une recherche et pour normaliser des mots qui
peuvent prendre des formes diverses. Il existe différents types de dictionnaires :</p>
<ol>
<li>Termes courants (<em>stopwords</em>)</li>
<li>Dictionnaire simple</li>
<li>Dictionnaire des synonymes</li>
<li>Dictionnaire thésaurus</li>
<li>Dictionnaire Ispell</li>
<li>Dictionnaire Snowball</li>
</ol>
<p>Une configuration définira ainsi la correspondance entre un type de jeton et
un ou plusieurs dictionnaires. En ce qui nous concerne, nous avons besoin d’un
thésaurus pour pouvoir associer un lexème à un ensemble de jetons.</p>
<p>Notre thésaurus devra se trouver dans <code class="highlighter-rouge">/usr/local/share/postgresql/tsearch_data/</code>
et nous le nommerons <code class="highlighter-rouge">prog_thesaurus.ths</code>. Sa syntaxe est plutôt transparente,
voyez vous-même :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a + : aplus
a # : asharp
c - - : cminusminus
c + + : cplusplus
c/c + + : cplusplus
c # : csharp
. net : dotnet
f # : fsharp
f * : fstar
j + + : jplusplus
j # : jsharp
m # : msharp
q # : qsharp
r + + : rplusplus
xbase + + : xbaseplusplus
x + + : xplusplus
x # : xsharp
z + + : zplusplus
</code></pre></div></div>
<p>Il nous faut maintenant instruire PostgreSQL de l’existence de ce thésaurus.
Mais nous allons faire face à un petit souci : certains des jetons utilisés dans
notre thésaurus sont des <em>stopwords</em> ! Pour que notre thésaurus puisse les
prendre en considération, il faut que ceux-ci ne soient pas ignorés par le
dictionnaire de base. Ce dictionnaire (Snowball) est basé sur un algorithme de
<em>stemming</em> qui sait comment réduire les variantes standard d’un mot vers une
base, ou <em>stem</em>, en rapport avec la langue.</p>
<p>Observons tout d’abord le comportement actuel.</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">ts_debug</span><span class="p">(</span><span class="s1">'french'</span><span class="p">,</span> <span class="s1">'c++'</span><span class="p">);</span>
<span class="o">+</span><span class="c1">-----------+-----------------+-------+---------------+-------------+---------+</span>
<span class="o">|</span> <span class="k">alias</span> <span class="o">|</span> <span class="n">description</span> <span class="o">|</span> <span class="n">token</span> <span class="o">|</span> <span class="n">dictionaries</span> <span class="o">|</span> <span class="k">dictionary</span> <span class="o">|</span> <span class="n">lexemes</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-----------+-----------------+-------+---------------+-------------+---------|</span>
<span class="o">|</span> <span class="n">asciiword</span> <span class="o">|</span> <span class="n">Word</span><span class="p">,</span> <span class="k">all</span> <span class="n">ASCII</span> <span class="o">|</span> <span class="k">c</span> <span class="o">|</span> <span class="p">{</span><span class="n">french_stem</span><span class="p">}</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span> <span class="p">[]</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">blank</span> <span class="o">|</span> <span class="k">Space</span> <span class="n">symbols</span> <span class="o">|</span> <span class="o">+</span> <span class="o">|</span> <span class="p">{}</span> <span class="o">|</span> <span class="o"><</span><span class="k">null</span><span class="o">></span> <span class="o">|</span> <span class="o"><</span><span class="k">null</span><span class="o">></span> <span class="o">|</span>
<span class="o">|</span> <span class="n">blank</span> <span class="o">|</span> <span class="k">Space</span> <span class="n">symbols</span> <span class="o">|</span> <span class="o">+</span> <span class="o">|</span> <span class="p">{}</span> <span class="o">|</span> <span class="o"><</span><span class="k">null</span><span class="o">></span> <span class="o">|</span> <span class="o"><</span><span class="k">null</span><span class="o">></span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-----------+-----------------+-------+---------------+-------------+---------+</span>
</code></pre></div></div>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">plainto_tsquery</span><span class="p">(</span><span class="s1">'french'</span><span class="p">,</span><span class="s1">'c++'</span><span class="p">)</span>
<span class="n">NOTICE</span><span class="p">:</span> <span class="nb">text</span><span class="o">-</span><span class="k">search</span> <span class="n">query</span> <span class="k">contains</span> <span class="k">only</span> <span class="n">stop</span> <span class="n">words</span> <span class="k">or</span> <span class="n">does</span> <span class="k">not</span> <span class="n">contain</span> <span class="n">lexemes</span><span class="p">,</span> <span class="n">ignored</span>
<span class="o">+</span><span class="c1">-----------------+</span>
<span class="o">|</span> <span class="n">plainto_tsquery</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-----------------|</span>
<span class="o">|</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-----------------+</span>
</code></pre></div></div>
<p>Effectivement, nous nous trouvons là dans une situation cocasse où l’ensemble
des jetons de notre requête sont ignorés : le premier étant un <em>stopword</em>, les
suivants des symboles.</p>
<p>Créons donc un nouveau dictionnaire Snowball sans <em>stopwords</em> :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo_stem</span> <span class="p">(</span>
<span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">pg_catalog</span><span class="p">.</span><span class="n">snowball</span><span class="p">,</span>
<span class="k">LANGUAGE</span> <span class="o">=</span> <span class="s1">'french'</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Déclarons à présent notre thésaurus qui s’appuiera sur notre nouveau
dictionnaire <code class="highlighter-rouge">french_strigo_stem</code> :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">prog_thesaurus</span> <span class="p">(</span>
<span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">pg_catalog</span><span class="p">.</span><span class="n">thesaurus</span><span class="p">,</span>
<span class="n">DICTFILE</span> <span class="o">=</span> <span class="s1">'prog_thesaurus'</span><span class="p">,</span>
<span class="k">DICTIONARY</span> <span class="o">=</span> <span class="s1">'public.french_strigo_stem'</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Pour éviter toute mauvaise surprise, clonons la configuration <code class="highlighter-rouge">french</code> ; c’est
cette réplique que nous altèrerons par la suite :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo</span> <span class="p">(</span>
<span class="k">COPY</span> <span class="o">=</span> <span class="n">french</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Voyons quels dictionnaires sont définis par notre configuration :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">\</span><span class="n">dF</span><span class="o">+</span> <span class="n">french_strigo</span>
<span class="nb">Text</span> <span class="k">search</span> <span class="n">configuration</span> <span class="nv">"pg_catalog.french_strigo"</span>
<span class="n">Parser</span><span class="p">:</span> <span class="nv">"pg_catalog.default"</span>
<span class="o">+</span><span class="c1">-----------------+--------------+</span>
<span class="o">|</span> <span class="n">Token</span> <span class="o">|</span> <span class="n">Dictionaries</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-----------------+--------------|</span>
<span class="o">|</span> <span class="n">asciihword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">asciiword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">email</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">file</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="nb">float</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="k">host</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword_asciipart</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword_numpart</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword_part</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">numhword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">numword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">sfloat</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">uint</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">url</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">url_path</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="k">version</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">word</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-----------------+--------------+</span>
</code></pre></div></div>
<p>Modifions à présent notre configuration pour y lier les types de jetons de notre
choix à notre thésaurus. L’ordre de déclaration des dictionnaires a ici une
importance, on prendra garde à positionner notre thésaurus en tête de liste :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo</span>
<span class="k">ALTER</span> <span class="n">MAPPING</span> <span class="k">FOR</span> <span class="n">asciiword</span><span class="p">,</span> <span class="n">asciihword</span><span class="p">,</span> <span class="n">hword_asciipart</span><span class="p">,</span> <span class="n">word</span>
<span class="k">WITH</span> <span class="n">prog_thesaurus</span><span class="p">,</span> <span class="n">french_strigo_stem</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo</span>
<span class="k">DROP</span> <span class="n">MAPPING</span> <span class="n">IF</span> <span class="k">EXISTS</span> <span class="k">FOR</span> <span class="n">blank</span><span class="p">;</span>
<span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="n">CONFIGURATION</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo</span>
<span class="k">ALTER</span> <span class="n">MAPPING</span> <span class="k">FOR</span> <span class="n">file</span><span class="p">,</span> <span class="k">host</span>
<span class="k">WITH</span> <span class="n">prog_thesaurus</span><span class="p">,</span> <span class="k">simple</span><span class="p">;</span>
</code></pre></div></div>
<p>Pourquoi altérer <code class="highlighter-rouge">file</code>, me direz-vous ? Parce que <code class="highlighter-rouge">c/c</code> dans « c/c++ » est
considéré comme un jeton de type <code class="highlighter-rouge">file</code>.</p>
<p>Si l’on observe notre configuration, elle ressemble à présent à ceci :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">\</span><span class="n">dF</span><span class="o">+</span> <span class="n">french_strigo</span>
<span class="nb">Text</span> <span class="k">search</span> <span class="n">configuration</span> <span class="nv">"public.french_strigo"</span>
<span class="n">Parser</span><span class="p">:</span> <span class="nv">"pg_catalog.default"</span>
<span class="o">+</span><span class="c1">-----------------+------------------------------------+</span>
<span class="o">|</span> <span class="n">Token</span> <span class="o">|</span> <span class="n">Dictionaries</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-----------------+------------------------------------|</span>
<span class="o">|</span> <span class="n">asciihword</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_strigo_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">asciiword</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_strigo_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">email</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">file</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="nb">float</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="k">host</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword_asciipart</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_strigo_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword_numpart</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">hword_part</span> <span class="o">|</span> <span class="n">french_stem</span> <span class="o">|</span>
<span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">numhword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">numword</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">sfloat</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">uint</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">url</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">url_path</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="k">version</span> <span class="o">|</span> <span class="k">simple</span> <span class="o">|</span>
<span class="o">|</span> <span class="n">word</span> <span class="o">|</span> <span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_strigo_stem</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-----------------+------------------------------------+</span>
</code></pre></div></div>
<p>Parfait ! Si l’on teste à présent notre nouvelle configuration :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">ts_debug</span><span class="p">(</span><span class="s1">'french_strigo'</span><span class="p">,</span> <span class="s1">'c++'</span><span class="p">);</span>
<span class="o">+</span><span class="c1">-------+-------------------+-------+-------------------------------------+----------------+---------------+</span>
<span class="o">|</span> <span class="k">alias</span> <span class="o">|</span> <span class="n">description</span> <span class="o">|</span> <span class="n">token</span> <span class="o">|</span> <span class="n">dictionaries</span> <span class="o">|</span> <span class="k">dictionary</span> <span class="o">|</span> <span class="n">lexemes</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-------+-------------------+-------+-------------------------------------+----------------+---------------|</span>
<span class="o">|</span> <span class="n">word</span> <span class="o">|</span> <span class="n">Word</span><span class="p">,</span> <span class="k">all</span> <span class="n">letters</span> <span class="o">|</span> <span class="k">c</span><span class="o">++</span> <span class="o">|</span> <span class="p">{</span><span class="n">prog_thesaurus</span><span class="p">,</span><span class="n">french_strigo_stem</span><span class="p">}</span> <span class="o">|</span> <span class="n">prog_thesaurus</span> <span class="o">|</span> <span class="p">[</span><span class="s1">'cplusplus'</span><span class="p">]</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-------+-------------------+-------+-------------------------------------+----------------+---------------+</span>
</code></pre></div></div>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">plainto_tsquery</span><span class="p">(</span><span class="s1">'french_strigo'</span><span class="p">,</span><span class="s1">'c++'</span><span class="p">)</span>
<span class="o">+</span><span class="c1">-----------------+</span>
<span class="o">|</span> <span class="n">plainto_tsquery</span> <span class="o">|</span>
<span class="o">|</span><span class="c1">-----------------|</span>
<span class="o">|</span> <span class="s1">'cplusplus'</span> <span class="o">|</span>
<span class="o">+</span><span class="c1">-----------------+</span>
</code></pre></div></div>
<p>Excellent ! Nous observons à présent que notre thésaurus a reconnu « c++ » comme
étant un jeton et lui a substitué le lexème « cplusplus ».</p>
<h2 id="stop-ou-encore">Stop ou encore ?</h2>
<p>Je tiens à attirer votre attention sur le fait qu’ignorer purement et simplement
les <em>stopwords</em> n’est peut-être pas souhaitable en conditions réelles. En effet,
les <em>stopwords</em> ont leur intérêt dans la mesure de pertinence des résultats
retournés. Mais rien ne nous empêche de déclarer notre propre dictionnaire de
<em>stopwords</em> en y excluant ceux qui entrent en conflit avec notre thésaurus !</p>
<p>Pour cela on peut s’inspirer du dictionnaire <code class="highlighter-rouge">french.stop</code>.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /usr/local/share/postgresql/tsearch_data/
<span class="nb">cp </span>french.stop french_strigo.stop
<span class="c"># modifier french_strigo.stop</span>
</code></pre></div></div>
<p>Il nous suffit alors de préciser le dictionnaire <em>stopwords</em> à utiliser :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo_stem</span> <span class="p">(</span>
<span class="k">TEMPLATE</span> <span class="o">=</span> <span class="n">pg_catalog</span><span class="p">.</span><span class="n">snowball</span><span class="p">,</span>
<span class="k">LANGUAGE</span> <span class="o">=</span> <span class="s1">'french'</span><span class="p">,</span>
<span class="n">STOPWORDS</span> <span class="o">=</span> <span class="s1">'french_strigo'</span>
<span class="p">);</span>
</code></pre></div></div>
<p>Si vous êtes amené à mettre à jour l’un de vos dictionnaires, pensez bien à
recharger votre configuration ! Pour cela, voici une petite astuce :</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">prog_thesaurus</span> <span class="p">(</span> <span class="n">dummy</span> <span class="p">);</span>
<span class="k">ALTER</span> <span class="nb">TEXT</span> <span class="k">SEARCH</span> <span class="k">DICTIONARY</span> <span class="k">public</span><span class="p">.</span><span class="n">french_strigo_stem</span> <span class="p">(</span> <span class="n">dummy</span> <span class="p">);</span>
</code></pre></div></div>
<h2 id="et-maintenant">Et maintenant ?</h2>
<p>Nous venons de voir les bases de la recherche plein texte et de sa
configuration. Cela fait déjà de nombreuses notions à assimiler, et encore, nous
n’avons fait que les survoler ! Pour approfondir cela, je vous invite à
consulter la <a href="https://www.postgresql.org/docs/current/textsearch.html" target="_blank" rel="external">documentation officielle</a>
ou sa <a href="https://docs.postgresql.fr/current/textsearch.html" target="_blank" rel="external">version française</a>, riches
d’exemples et de détails.</p>
<p>Dans un prochain article, nous aborderons un autre aspect important de la
recherche plein texte : la pondération. S’ensuivra un dernier article pour
clore cette série, il mettra l’accent sur l’indexation et <code class="highlighter-rouge">pg_search</code>, une gem
Ruby nous permettant de créer des <em>scopes</em> ActiveRecord qui tirent parti de la
recherche plein texte de PostgreSQL.</p>François VantommeOrthogonalité2022-04-08T00:00:00+02:002022-04-08T00:00:00+02:00repo://posts.collection/_posts/2022-04-08-orthogonalit%C3%A9.md<blockquote>
<p>L’orthogonalité est un concept essentiel si l’on veut produire des systèmes
faciles à concevoir, à construire, à tester et à étendre. Cependant, le
concept d’orthogonalité est rarement enseigné directement. Il s’agit souvent
d’une caractéristique implicite de diverses autres méthodes et techniques que
vous apprenez. C’est une erreur. Une fois que vous aurez appris à appliquer
directement le principe d’orthogonalité, vous constaterez une amélioration
immédiate de la qualité des systèmes que vous produisez.</p>
<p>— Dave Thomas, Andy Hunt. « The Pragmatic Programmer »</p>
</blockquote>
<p>Ainsi commence le chapitre 10 du livre « The Pragmatic Programmer » de Dave
Thomas & Andy Hunt. Excellent livre dont nous avons déjà parlé dans les articles
<a href="/architecture/2020/07/02/duplication-ou-coincidence/">Duplication ou coïncidence ?</a> et <a href="/git/2021/03/12/l-art-de-l-aiguisage/">L’art de l’aiguisage</a>. Aujourd’hui
nous allons donc parler d’orthogonalité, de ce qui se cache derrière cette
notion, et des bénéfices que peuvent nous apporter sa compréhension et sa mise en
pratique !</p>
<h2 id="quest-ce-que-lorthogonalité">Qu’est-ce que l’orthogonalité ?</h2>
<p>Il s’agit d’une analogie avec les vecteurs orthogonaux en algèbre linéaire :
aucun des vecteurs d’un ensemble orthogonal ne dépend des autres et tous sont
nécessaires pour décrire l’espace vectoriel dans son ensemble.</p>
<blockquote>
<p>L’orthogonalité signifie que les caractéristiques peuvent être utilisées dans
n’importe quelle combinaison, que les combinaisons ont toutes un sens et que
la signification d’une caractéristique donnée est cohérente, indépendamment
des autres caractéristiques avec lesquelles elle est combinée.</p>
<p>— Michael Scott. « Programming Language Pragmatics »</p>
</blockquote>
<p>Ainsi, lorsqu’on parle d’orthogonalité en ingénierie logicielle, on fait
référence aux notions de découplage, mais aussi de composabilité, de
prédictibilité et de cohérence.</p>
<h2 id="limiter-les-effets-de-bord">Limiter les effets de bord</h2>
<p>Penser en termes d’orthogonalité nous amène à concevoir des composants
respectant les principes <abbr title="Single responsibility ; Open/closed ; Liskov substitution ; Interface segregation ; Dependency inversion principles">SOLID</abbr> promus par Robert C. Martin. On va leur octroyer
une responsabilité unique, favoriser l’injection de dépendance, ou encore
les rendre déterministes, limitant ainsi les effets de bord.</p>
<p>Ce faisant, il est bien plus simple de tester nos composants puisque leur
périmètre est restreint au strict nécessaire ; leur comportement prédictible,
car ils sont insensibles aux perturbations exogènes ; et leurs dépendances quasi
inexistantes, il sera donc plus aisé de mettre sur pied un jeu de tests de façon
à reproduire les différents scénarios pouvant se présenter.</p>
<p>Non seulement nos tests sont plus simples à écrire, mais nos composants,
découplés les uns des autres, sont aussi plus facilement modifiables,
adaptables, voire remplaçables !</p>
<h3 id="effets-algébriques">Effets algébriques</h3>
<p>Une approche pratique pour contenir les effets de bord nous vient de la
programmation fonctionnelle et se nomme <strong>effet algébrique</strong>.</p>
<blockquote>
<p>Les effets algébriques sont une approche des effets computationnels basée sur
le principe que le comportement impur d’une fonction découle d’un ensemble
d’opérations telles que <em>get & set</em> pour le stockage mutable, <em>read & print</em>
pour les entrées/sorties interactives, ou raise pour les exceptions. Cela
donne naturellement lieu à des <em>handlers</em> non seulement pour les exceptions,
mais aussi pour tout autre effet qui, entre autres, peut capturer la
redirection de flux, la rétrospection, le <em>multithreading</em> coopératif et les
continuations délimitées.</p>
<p>— Matija Pretnar. « An Introduction to Algebraic Effects and Handlers »</p>
</blockquote>
<p>Une implémentation de cette approche existe en Ruby via la gem <a href="https://dry-rb.org/gems/dry-effects/" target="_blank" rel="external">dry-effects</a>
qui, pour ne rien gâcher, est livrée avec une documentation fournie et
éclairante sur son potentiel.</p>
<h2 id="gagner-en-productivité">Gagner en productivité</h2>
<p>Bien qu’assez exigeant et demandant un peu d’exercice, penser en termes
d’orthogonalité peut assez vite s’avérer payant. En effet, l’empreinte de nos
composants étant réduite, y apporter des modifications en sera d’autant plus
aisé. Car il va de soi que sans couplage ni effet de bord, nous serons plus
facilement enclins à réaliser des changements que nous saurons localisés.
Sérénité et productivité vont de pair !</p>
<p>Par ailleurs, il nous sera aussi très facile de chaîner nos composants pour
répondre à des besoins plus complexes. En permettant la composabilité,
l’orthogonalité favorise ainsi la réutilisabilité de nos composants. Sans cela,
nous ferions face à des composants dont les responsabilités se chevauchent,
voire à des incompatibilités structurelles.</p>
<h3 id="composabilité">Composabilité</h3>
<p>En Ruby, la gem <a href="https://dry-rb.org/gems/dry-transformer/" target="_blank" rel="external">dry-transformer</a>, dont nous
avons déjà parlé ici-même pour présenter une manière élégante de manipuler des
structures de données, est un exemple de méthodes simples et composables pour
couvrir un grand nombre de scénarios.</p>
<h2 id="réduire-les-risques">Réduire les risques</h2>
<p>Restreindre le périmètre, les responsabilités et les dépendances nous permet
aussi d’éviter l’effet domino ! Un composant risque moins de propager un
comportement inattendu s’il est isolé des autres et n’a pas d’effet de bord.</p>
<p>De la même manière, dans un système orthogonal il devient assez aisé de
substituer une dépendance à une autre. Changer d’<abbr title="Object Relational Mapping">ORM</abbr>, de <abbr title="Système de Gestion de Bases de Données">SGBD</abbr>, voire de
<em>framework</em> devrait être du domaine du possible — moyennant un certain effort,
voire un effort certain, mais néanmoins possible — et idéalement n’avoir aucun
impact sur notre code métier.</p>
<h2 id="une-architecture-résiliente">Une architecture résiliente</h2>
<p>Sans forcément l’avoir pensé en ces termes précis, nous sommes déjà habitués à
concevoir des systèmes orthogonaux. Le découpage en 7 couches du modèle <abbr title="Open Systems Interconnection">OSI</abbr> en
est un exemple, l’architecture hexagonale en est un autre, ou encore les
microservices pour en prendre un troisième. Toutes ces architectures ont en
commun, sans jamais vraiment l’exprimer en ces termes, une recherche
d’orthogonalité.</p>
<p>Ainsi, une architecture orthogonale sera résiliente, dans le sens où sa capacité
à absorber un changement sera élevée et que cela ne génèrera pas de soubresauts
à travers toute l’application.</p>
<h2 id="du-choix-de-ses-outils">Du choix de ses outils</h2>
<p><a href="/git/2021/03/12/l-art-de-l-aiguisage/">L’art de l’aiguisage</a> nous l’a appris, le choix de ses outils est
primordial pour qui recherche précision et efficacité. Ainsi, choisir des
bibliothèques ou autres greffons qui respectent le principe d’orthogonalité nous
sera d’une grande aide pour la maintenabilité et la testabilité de nos
applications. Les programmes GNU/Linux en sont un bon exemple, comme le fait
remarquer Eric Steven Raymond dans l’extrait suivant de « The Art of Unix
Programming ».</p>
<blockquote>
<p>L’orthogonalité signifie également que plusieurs programmes n’ont pas les
mêmes fonctions. Par exemple, sous GNU/Linux, la sélection raffinée de
fichiers n’est effectuée que par le programme find. D’autre part, find ne peut
que sélectionner des fichiers et n’a pas de fonctions supplémentaires ; il
peut cependant être combiné avec toutes les autres commandes. Le programme tar
peut combiner plusieurs fichiers en une archive ; pour la compression, il est
combiné avec gzip. gzip ne peut compresser qu’un seul fichier et ne peut ni
sélectionner ni combiner des fichiers.</p>
<p>— Eric Steven Raymond. « The Art of Unix Programming »</p>
</blockquote>
<h2 id="un-code-découplé">Un code découplé</h2>
<p>Alors comment concevoir de tels programmes ? Au quotidien, il est difficile de
conserver l’orthogonalité de son code sans y apporter une attention
particulière. Nos <em>frameworks</em> ne nous y encourage pas toujours non plus, ce qui
n’arrange rien. Cependant, et sans dénaturer l’esprit et la philosophie du
<em>framework</em> que l’on utilise, nous pouvons tâcher de <a href="/rails/2020/11/12/rails-n-est-pas-simple/">préserver un
découplage</a> quand l’occasion nous en est donnée. Il est par exemple
préférable de laisser un objet gérer lui-même son état interne. Limiter
l’exposition de nos objets sur l’extérieur est là aussi une pratique qui va dans
le sens recherché.</p>
<p>Prenons un exemple inspiré du livre de Dave Thomas & Andy Hunt. Selon vous,
laquelle de ces deux classes respecte le mieux le principe d’orthogonalité ?</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Split1</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">fileName</span><span class="p">)</span> <span class="c1"># accède au fichier en lecture</span>
<span class="k">def</span> <span class="nf">readNextLine</span><span class="p">()</span> <span class="c1"># lit la prochaine ligne</span>
<span class="k">def</span> <span class="nf">getWord</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="c1"># retourne le n-ième mot de la ligne courante</span>
<span class="k">end</span>
</code></pre></div></div>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">Split2</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="c1"># découpe la ligne en mots</span>
<span class="k">def</span> <span class="nf">getWord</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="c1"># retourne le n-ième mot de la ligne courante</span>
<span class="k">end</span>
</code></pre></div></div>
<p>La seconde, en effet, puisqu’elle se concentre sur une seule tâche (découper une
ligne en mots) sans s’inquiéter d’où provient cette ligne. Ainsi, non seulement
on réduit le couplage, mais on améliore aussi sa composabilité, tout simplifiant
l’écriture de tests.</p>
<h2 id="un-code-contextualisé">Un code contextualisé</h2>
<p>Inscrire son code dans un contexte est également un excellent moyen d’éviter les
couplages non désirés, le cumul des responsabilités, et les effets de bord. En
contextualisant son code, on s’évite l’usage de variables globales, partagées et
modifiables par tous. Ce faisant, on s’offre aussi la possibilité de respecter
le principe <abbr title="Don't Repeat Yourself">DRY</abbr> ; lorsque nos méthodes et fonctions sont contextualisées, il est
plus facile de <a href="/architecture/2020/07/02/duplication-ou-coincidence/">discerner la duplication de la coïncidence</a>.</p>
<blockquote>
<p><abbr title="Don't Repeat Yourself">DRY</abbr> concerne la duplication de la connaissance, de l’intention. Il s’agit
d’exprimer la même chose à deux endroits différents, peut-être de deux
manières totalement différentes.</p>
<p>— Dave Thomas, Andy Hunt. « The Pragmatic Programmer »</p>
</blockquote>
<h2 id="un-code-facile-à-tester">Un code facile à tester</h2>
<p>Avez-vous déjà eu à tester une méthode qui manipule toute une arborescence
d’objets complexes, fait des appels en base de données et interroge une API
externe, tout en étant sensible à une ou plusieurs variables d’environnement ?
C’est une torture ! Et c’est là tout ce qu’on souhaite s’éviter en visant
l’orthogonalité ! Réduire le nombre de dépendances et les inverser quand elles
sont nécessaires permet une meilleure maitrise des cas de figures pouvant se
présenter. Limiter l’exposition de nos objets réduit drastiquement le nombre de
situations à tester. Favoriser la composabilité et le déterminisme de nos
composants permet de faire face à un grand nombre de situations sans pour autant
multiplier les tests.</p>
<h2 id="pour-aller-plus-loin">Pour aller plus loin</h2>
<p>Nous venons de le voir, cette notion d’orthogonalité se cache partout ! Et une
fois identifiée, elle se révèlera être votre meilleure alliée pour concevoir des
applications pérennes et résilientes.</p>
<p>Si vous souhaitez creuser davantage le sujet, je vous invite à la lecture de
« The Pragmatic Programmer » de Dave Thomas & Andy Hunt ; ainsi que « <a href="http://www.catb.org/~esr/writings/taoup/html/ch04s02.html#orthogonality" target="_blank" rel="external">The Art
of Unix Programming</a> » d’Eric Steven
Raymond, dont le chapitre 4 aborde ce sujet, et plus largement la notion de
modularité, du point de vue du développement applicatif sous Unix.</p>François VantommeRegex, ZSH & Darwin2021-11-18T00:00:00+01:002021-11-18T00:00:00+01:00repo://posts.collection/_posts/2021-11-18-regex-zsh-darwin.md<p>Comme promis dans l’article portant sur <a href="/regex/2021/11/12/des-regex-qui-ont-la-classe/">les classes de caractères des
regex</a>, me revoilà avec, cette fois, un exemple concret que je vais exécuter
froidement devant vous !</p>
<p>En passant d’un macOS à une Debian GNU/Linux, je me suis aperçu d’un
comportement inattendu sur un script ZSH de ma confection en ce qui concerne les
regex, et plus particulièrement les classes de caractères.</p>
<h2 id="le-contexte">Le contexte</h2>
<p>L’idée était de détecter la présence d’un mot dans une chaîne de caractères.
Prenons la chaîne suivante en exemple :</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bat exa fd fzf git htop ncdu neovim ripgrep tig tldr tmux tree watch z zplug
</code></pre></div></div>
<p>Mettons maintenant que nous recherchions la présence du mot « tldr » dans cette
liste. D’après ce que nous savons des classes de caractères, nous pouvons par
exemple écrire la regex suivante :</p>
<div class="language-sed highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">/</span><span class="p">[</span><span class="sr">[:\<:</span><span class="p">]</span><span class="sr">]tldr</span><span class="p">[</span><span class="sr">[:\>:</span><span class="p">]</span><span class="sr">]</span><span class="kn">/</span>
</code></pre></div></div>
<p>Les classes de caractères <code class="highlighter-rouge">[[:\<:]]</code> et <code class="highlighter-rouge">[[:\>:]]</code> représentent respectivement
le début et la fin d’un mot. Cela nous permet de nous assurer de ne pas tomber
sur une suite de caractères au milieu d’un mot. On peut ainsi rechercher « z »
sans tomber sur « fzf » ou « zplug », pour reprendre notre exemple.</p>
<h2 id="lost-in-the-shell">Lost in the Shell</h2>
<p>Voyons à présent ce que cela donne quand on utilise notre petite regex dans le
contexte de ZSH.</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">uname
</span>Darwin
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]man[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">false</span>
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">true</span>
</code></pre></div></div>
<p>Tout semble se passer pour le mieux ! Essayons sous GNU/Linux :</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">uname
</span>Linux
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
zsh: failed to compile regex: Nom de classe de caractères invalide
<span class="nb">false</span>
</code></pre></div></div>
<p>Outch ! Mais que se passe-t-il ?</p>
<h2 id="lets-read-the-famous-manual">Let’s Read The Famous Manual!</h2>
<p>Un petit tour dans la documentation de ZSH devrait nous aiguiller… voyons voir.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>REMATCH_PCRE
If set, regular expression matching with the =~ operator will use
Perl-Compatible Regular Expressions from the PCRE library. (The zsh/pcre
module must be available.) If not set, regular expressions will use the
extended regexp syntax provided by the system libraries.
</code></pre></div></div>
<p>Il semblerait qu’une option nous permettrait d’imposer une bibliothèque
compatible Perl (<abbr title="Perl Compatible Regular Expressions">PCRE</abbr>). Si cette option n’est pas définie, nous sommes
dépendants de la bibliothèque système. Allons-y !</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">uname
</span>Darwin
❯ setopt rematch_pcre
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]man[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">false</span>
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">true</span>
❯ unsetopt rematch_pcre
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">true</span>
</code></pre></div></div>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">uname
</span>Linux
❯ setopt rematch_pcre
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]man[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">false</span>
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">true</span>
❯ unsetopt rematch_pcre
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
zsh: failed to compile regex: Nom de classe de caractères invalide
<span class="nb">false</span>
</code></pre></div></div>
<p>Parfait, ça semble faire le boulot ! Cela dit, si nous nous rappelons bien du
<a href="/regex/2021/11/12/des-regex-qui-ont-la-classe#un-manque-de-standardisation">tableau présenté dans l’article précédent</a>, il existe d’autres manières de
délimiter un mot :</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">uname
</span>Linux
❯ unsetopt rematch_pcre
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="s2">"</span><span class="se">\<</span><span class="s2">tldr</span><span class="se">\></span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">true</span>
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="s2">"</span><span class="se">\b</span><span class="s2">tldr</span><span class="se">\b</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">true</span>
</code></pre></div></div>
<p>Mais là, manque de chance, c’est macOS qui flanche :</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">uname
</span>Darwin
❯ unsetopt rematch_pcre
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="s2">"</span><span class="se">\b</span><span class="s2">tldr</span><span class="se">\b</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">false</span>
❯ <span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="s2">"</span><span class="se">\<</span><span class="s2">tldr</span><span class="se">\></span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"true"</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">"false"</span>
<span class="nb">false</span>
</code></pre></div></div>
<h2 id="la-solution">La solution</h2>
<p>À la vue de ces comportements bigarrés, la meilleure option qui s’offre à nous
est de nous assurer qu’un moteur <abbr title="Perl Compatible Regular Expressions">PCRE</abbr> sera utilisé, ou d’utiliser une regex de
repli dans le cas contraire. Ce qui pourrait donner ceci :</p>
<div class="language-zsh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="o">[[</span> <span class="nt">-o</span> rematchpcre <span class="o">||</span> <span class="s2">"</span><span class="nv">$OSTYPE</span><span class="s2">"</span> <span class="o">==</span> darwin<span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
<span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="o">[[</span>:<span class="se">\<</span>:]]tldr[[:<span class="se">\></span>:]] <span class="o">]]</span>
<span class="k">else</span>
<span class="o">[[</span> <span class="s2">"bat tldr zplug"</span> <span class="o">=</span>~ <span class="s2">"</span><span class="se">\<</span><span class="s2">tldr</span><span class="se">\></span><span class="s2">"</span> <span class="o">]]</span>
<span class="k">fi</span>
</code></pre></div></div>
<p>On considère ici que si l’option ZSH <code class="highlighter-rouge">rematchpcre</code> est activée ou si le système
d’exploitation est macOS (<code class="highlighter-rouge">darwin</code> de son petit nom), alors on pourra utiliser
notre regex compatible Perl.</p>
<p>En espérant que ce petit retour d’expérience vous aura appris une ou deux choses
et donné l’envie de lire le fameux manuel quand une question vous taraude !</p>François VantommeDes regex qui ont la classe !2021-11-12T00:00:00+01:002021-11-12T00:00:00+01:00repo://posts.collection/_posts/2021-11-12-des-regex-qui-ont-la-classe.md<p>Parlons regex ! Expressions rationnelles. Alors oui, les plus assidus me feront
remarquer qu’on a déjà abordé ce thème à plusieurs reprises ; on a abordé le
sujet des <a href="/regex/2020/04/02/regex-gourmand-feneant-possesif/">quantificateurs</a>, des <a href="/regex/2020/04/09/regex-attrapez-les-tous/">groupes de capture</a> et on a même <a href="/regex/2019/10/24/exprimez-vos-%C3%A9motions-en-regex/">joué
avec les emojis</a> ! Mais il reste encore des aspects de ce fabuleux outil que
sont les regex à aborder, et aujourd’hui je vous propose de nous pencher sur les
classes de caractères.</p>
<h2 id="classes-de-caractères">Classes de caractères</h2>
<p>Une classe de caractère, c’est tout simplement un ensemble de caractères
manipulé comme un tout ; généralement parce qu’ils partagent un trait commun. Au
sein d’une expression rationnelle, on note une classe de caractère entre
crochets, comme ceci :</p>
<ul>
<li><code class="highlighter-rouge">[0123456789]</code> une simple liste de caractères ;</li>
<li><code class="highlighter-rouge">[0-9]</code> un intervalle ;</li>
<li><code class="highlighter-rouge">[A-Za-z0-9_]</code> un mix de tout ça ;</li>
<li><code class="highlighter-rouge">\w</code> ou <code class="highlighter-rouge">[[:word:]]</code> une classe prédéfinie ;</li>
<li><code class="highlighter-rouge">[^0-9]</code> une négation (caractérisée par la présence d’un circonflexe en tête de liste).</li>
</ul>
<h2 id="moteur">Moteur !</h2>
<p>Il existe différentes implémentations de moteurs de regex, dits moteurs <abbr title="Nondeterministic Finite Automaton">NFA</abbr>
piloté par le motif ou <abbr title="Deterministic Finite Automaton">DFA</abbr> piloté par l’entrée. Suivant le langage et
l’environnement dans lequel vous évoluez, de petites différences pourront être
constatées. En l’occurrence, tous les moteurs ne supportent pas les mêmes
classes de caractères prédéfinies.</p>
<p>Le moteur le plus complet à ce jour étant celui de Perl. Celui-ci permet, par
exemple, de rechercher le mot le plus long d’une chaîne de caractères à l’aide
de <em>boundaries, possessive quantifiers, positive lookahead, negative lookahead,
positive lookbehind</em>… on a sorti tout l’attirail !</p>
<div class="language-sed highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">/</span><span class="se">\b</span><span class="p">(</span><span class="se">\w</span><span class="sr">++</span><span class="p">)(</span><span class="sr">?=</span><span class="p">(</span><span class="o">.*</span><span class="p">))(</span><span class="sr">?!</span><span class="p">(</span><span class="o">.*</span><span class="se">\W</span><span class="p">)</span><span class="se">\b</span><span class="p">((</span><span class="sr">?<=</span><span class="p">(</span><span class="sr">?=</span><span class="p">(</span><span class="sr">?=</span><span class="se">\1\2</span><span class="o">$</span><span class="p">)(</span><span class="sr">?:</span><span class="p">(</span><span class="sr">?=</span><span class="se">\w</span><span class="o">*</span><span class="sr">+</span><span class="se">\3</span><span class="p">(</span><span class="se">\5</span><span class="sr">?+</span><span class="se">\w</span><span class="p">))</span><span class="se">\w</span><span class="p">)</span><span class="sr">++</span><span class="se">\b</span><span class="sr">|</span><span class="p">(</span><span class="sr">?4</span><span class="p">))</span><span class="o">.</span><span class="p">)))</span><span class="kn">/</span>
</code></pre></div></div>
<p>Aïe, ça pique !</p>
<h2 id="un-manque-de-standardisation">Un manque de standardisation</h2>
<p>Les classes prédéfinies sont fort utiles, seulement elles sont peu portables du
fait du manque de standardisation entre les différents moteurs de regex. Voici
un petit aperçu.</p>
<table class="whitespace-nowrap text-sm">
<thead>
<tr>
<th style="text-align: left">Vim</th>
<th style="text-align: left">JS</th>
<th style="text-align: left">Ruby, Elixir, PHP</th>
<th style="text-align: left">ASCII</th>
<th style="text-align: left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:ascii:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">[\x00-\x7F]</code></td>
<td style="text-align: left">Caractères ASCII</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:alnum:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">A-Za-z0-9</code></td>
<td style="text-align: left">Caractères alphanumériques</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\w</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\w</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\w</code> ou <code class="highlighter-rouge">[[:word:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">A-Za-z0-9_</code></td>
<td style="text-align: left">Caractères alphanumériques, et « _ »</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\W</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\W</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\W</code> ou <code class="highlighter-rouge">[^[:word:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">^A-Za-z0-9_</code></td>
<td style="text-align: left">Caractères ne composant pas les mots</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\a</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:alpha:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">A-Za-z</code></td>
<td style="text-align: left">Caractères alphabétiques</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\s</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:blank:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\t</code></td>
<td style="text-align: left">Espace et tabulation</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\\<</code> <code class="highlighter-rouge">\\></code></td>
<td style="text-align: left"><code class="highlighter-rouge">\b</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\b</code> ou <code class="highlighter-rouge">[[:<:]]</code> <code class="highlighter-rouge">[[:>:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">(?<=\W)(?=\w)│(?<=\w)(?=\W)</code></td>
<td style="text-align: left">Positions de début et fin de mots</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">\B</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\B</code> ou <code class="highlighter-rouge">[^[:<:]]</code> <code class="highlighter-rouge">[^[:>:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">(?<=\W)(?=\W)│(?<=\w)(?=\w)</code></td>
<td style="text-align: left">Positions ni en début ni en fin de mot</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:cnrtl:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\x00-\x1F\x7F</code></td>
<td style="text-align: left">Caractères de contrôle</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\d</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\d</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\d</code> ou <code class="highlighter-rouge">[[:digit:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">0-9</code></td>
<td style="text-align: left">Chiffres décimaux</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\D</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\D</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\D</code> ou <code class="highlighter-rouge">[^[:digit:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">^0-9</code></td>
<td style="text-align: left">Autre qu’un chiffre décimal</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:graph:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\x21-\x7E</code></td>
<td style="text-align: left">Caractères visibles</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\l</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:lower:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">a-z</code></td>
<td style="text-align: left">Lettres en minuscule</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\p</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:print:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\x20-\x7E</code></td>
<td style="text-align: left">Caractères imprimables</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:punct:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">][!"#$%&'()\*+,./:;<=>?@\^_{│}~-</code></td>
<td style="text-align: left">Caractères de ponctuation</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\_s</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\s</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\s</code> ou <code class="highlighter-rouge">[[:space:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\t\r\n\v\f</code></td>
<td style="text-align: left">Caractères d’espacement</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\S</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\S</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\S</code> ou <code class="highlighter-rouge">[^[:space:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">^ \t\r\n\v\f</code></td>
<td style="text-align: left">Autre qu’un caractère d’espacement</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">\v</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\v</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left">Caractère d’espacement vertical</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">\V</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left">Autre qu’un caractère d’espacement vertical</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\u</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">[[:upper:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">A-Z</code></td>
<td style="text-align: left">Lettres capitales</td>
</tr>
<tr>
<td style="text-align: left"><code class="highlighter-rouge">\x</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\x</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\h</code> ou <code class="highlighter-rouge">[[:xdigit:]]</code></td>
<td style="text-align: left"><code class="highlighter-rouge">A-Fa-f0-9</code></td>
<td style="text-align: left">Chiffres hexadécimaux</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">\H</code> ou <code class="highlighter-rouge">[^[:xdigit:]]</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left">Autre qu’un chiffre hexadécimal</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">\A</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\A</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left">Début de chaîne de caractère</td>
</tr>
<tr>
<td style="text-align: left"> </td>
<td style="text-align: left"><code class="highlighter-rouge">\z</code></td>
<td style="text-align: left"><code class="highlighter-rouge">\z</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left">Fin de chaîne de caractère</td>
</tr>
</tbody>
</table>
<h2 id="propriétés-unicode">Propriétés Unicode</h2>
<p>Mais les classes de caractères ne se limitent pas à celles listées ci-dessus. Il
est en effet possible de tirer profit des propriétés Unicode. Voici quelques
exemples pour toucher du doigt le potentiel de ces classes.</p>
<h3 id="jeux-de-caractères">Jeux de caractères</h3>
<p>Il est par exemple possible de <a href="https://regex101.com/r/VcUSTO/4" target="_blank" rel="external">rechercher n’importe quel caractère grec</a> :</p>
<div class="language-sed highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">/</span><span class="se">\p</span><span class="sr">{Greek}</span><span class="kn">/</span>
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2ΠR valent mieux qu'un caillou
^
</code></pre></div></div>
<h3 id="symboles-monétaires">Symboles monétaires</h3>
<p>Ou encore, de <a href="https://regex101.com/r/vdDg8o/1" target="_blank" rel="external">retrouver les symboles monétaires</a> dans une chaîne :</p>
<div class="language-sed highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">/</span><span class="se">\p</span><span class="sr">{Sc}</span><span class="kn">/</span><span class="k">g</span>
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Vous pouvez payer en €uro en £ivre ou en ¥en. Et même en ₿itcoin !
^ ^ ^ ^
</code></pre></div></div>
<h3 id="tirets-de-ponctuation">Tirets de ponctuation</h3>
<p>Voire de <a href="https://regex101.com/r/cuxRKJ/2" target="_blank" rel="external">détecter n’importe quel tiret de ponctuation</a> :</p>
<div class="language-sed highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">/</span><span class="se">\p</span><span class="sr">{Pd}</span><span class="kn">/</span><span class="k">g</span>
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Vous êtes plutôt trait d'union (‐ U+2010), signe moins (- U+002D), tiret demi-cadratin (– U+2013), tiret cadratin (— U+2014) ou tiret numérique (‒ U+2012) ?
^ ^ ^ ^ ^
</code></pre></div></div>
<h3 id="exclusion">Exclusion</h3>
<p>Il est même possible d’<a href="https://regex101.com/r/2y4uFm/3" target="_blank" rel="external">exclure une sous-classe</a>.
Par exemple, voici comment retrouver tous les caractères de ponctuation et
symboles, excepté les tirets :</p>
<div class="language-sed highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">/</span><span class="p">(</span><span class="sr">?!</span><span class="se">\p</span><span class="sr">{Pd}</span><span class="p">)[</span><span class="sr">\p{P}\p{S}</span><span class="p">]</span><span class="kn">/</span><span class="k">g</span>
</code></pre></div></div>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>schöner co-operative two_words!@#$%^
^ ^^^^^^
</code></pre></div></div>
<p>Il existe près de <strong>40</strong> de ces propriétés Unicode ! Si la curiosité vous pique,
je vous invite à aller faire un tour du côté de <a href="http://erlang.org/doc/man/re.html#sect7" target="_blank" rel="external">la documentation
d’Erlang</a> traitant de ce sujet.</p>
<h2 id="à-suivre">À suivre…</h2>
<p>Dans <a href="/regex/2021/11/18/regex-zsh-darwin/">un prochain article</a>, je vous présenterai un cas concret de non portabilité
d’une expression rationnelle et comment j’ai dû ruser pour arriver à mes fins !</p>François VantommmeConfiguration applicative2021-10-01T00:00:00+02:002021-10-01T00:00:00+02:00repo://posts.collection/_posts/2021-10-01-configuration-applicative.md<p>Lorsqu’il s’agit de configurer une application Rails, chez Synbioz, on aime
bien y apporter une grande souplesse pour pouvoir nous adapter à de multiples
situations. C’est pourquoi on favorise l’usage de variables d’environnement.
Pour accéder à ces variables, on pourra utiliser <code class="highlighter-rouge">ENV.fetch("ma_variable")</code> si
sa présence est obligatoire, ou <code class="highlighter-rouge">ENV["ma_variable"]</code> si elle est optionnelle.</p>
<p>Se pose alors la question des variables booléennes. Par convention, nous avons
choisi de favoriser “0” ou “1” au détriment d’autres valeurs comme “true”,
“FALSE”, “yes”, “f”, etc. Ainsi, une variable d’environnement booléenne sera
récupérée via <code class="highlighter-rouge">ENV.fetch("ma_variable").to_i.positive?</code>.</p>
<h2 id="fail-fast">Fail fast</h2>
<blockquote>
<p>The most annoying aspect of software development, for me, is debugging. I
don’t mind the kinds of bugs that yield to a few minutes’ inspection. The bugs
I hate are the ones that show up only after hours of successful operation,
under unusual circumstances, or whose stack traces lead to dead ends.
Fortunately, there’s a simple technique that will dramatically reduce the
number of these bugs in your software. It won’t reduce the overall number of
bugs, at least not at first, but it’ll make most defects much easier to find.
The technique is to build your software to “fail fast.”</p>
<p>— Jim Shore</p>
</blockquote>
<p>Dans l’idéal, quel que soit le framework ou le langage, il est préférable de
récupérer l’ensemble des variables d’environnement utiles à l’application au
démarrage de celle-ci, de manière centralisée pour faciliter la prise de
connaissance de ces variables et leur mise à jour. Ainsi, si une variable est
manquante au démarrage, on pourra faire planter l’application dès son lancement
avec un message explicite. Ceci évite d’avoir des plantages aléatoires à
l’exécution ; à l’envoi d’un courriel ou lors d’un appel à une <abbr title="Application Programming Language">API</abbr> par exemple.</p>
<h2 id="configuration-x">Configuration X</h2>
<p>Dans le cas d’une application Rails, on va centraliser la récupération des
variables d’environnement dans le fichier <code class="highlighter-rouge">config/application.rb</code>. On a donc
notre point centralisé, chargé au démarrage de l’application qui va nous
permettre d’être robuste face aux variables d’environnement manquantes.</p>
<p>Rails prévoit un mécanisme pour stocker toutes les informations de configuration
transversales à l’application. Cela nous évite de passer par un système maison,
ou pire, des variables globales. <code class="highlighter-rouge">Rails.configuration.x</code> permet de stocker
l’ensemble des données de configuration pour une instance donnée et de récupérer
très facilement ces infos depuis n’importe où dans l’application.</p>
<p>L’implémentation de <code class="highlighter-rouge">Rails.configuration.x</code> mérite qu’on s’y attarde ! Il s’agit
d’une instance de la classe <code class="highlighter-rouge">Custom</code> déclarée comme ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># railties/lib/rails/application/configuration.rb</span>
<span class="k">module</span> <span class="nn">Rails</span>
<span class="k">class</span> <span class="nc">Application</span>
<span class="k">class</span> <span class="nc">Configuration</span> <span class="o"><</span> <span class="o">::</span><span class="no">Rails</span><span class="o">::</span><span class="no">Engine</span><span class="o">::</span><span class="no">Configuration</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="o">*</span><span class="p">)</span>
<span class="vi">@x</span> <span class="o">=</span> <span class="no">Custom</span><span class="p">.</span><span class="nf">new</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Custom</span> <span class="c1">#:nodoc:</span>
<span class="k">def</span> <span class="nf">initialize</span>
<span class="vi">@configurations</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">method_missing</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">method</span><span class="p">.</span><span class="nf">end_with?</span><span class="p">(</span><span class="s2">"="</span><span class="p">)</span>
<span class="vi">@configurations</span><span class="p">[</span><span class="ss">:"</span><span class="si">#{</span><span class="nb">method</span><span class="p">[</span><span class="mi">0</span><span class="o">..-</span><span class="mi">2</span><span class="p">]</span><span class="si">}</span><span class="ss">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="nf">first</span>
<span class="k">else</span>
<span class="vi">@configurations</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nb">method</span><span class="p">)</span> <span class="p">{</span>
<span class="vi">@configurations</span><span class="p">[</span><span class="nb">method</span><span class="p">]</span> <span class="o">=</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">OrderedOptions</span><span class="p">.</span><span class="nf">new</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">respond_to_missing?</span><span class="p">(</span><span class="n">symbol</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span>
<span class="kp">true</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>On observe que la technique consiste à faire usage de la méthode
<code class="highlighter-rouge">method_missing</code>, nous offrant ainsi la possibilité de récupérer ou d’affecter
une valeur via n’importe quelle méthode de notre choix sur cet objet. On
remarque que si la clé <code class="highlighter-rouge">foo</code> n’existe pas dans le dictionnaire <code class="highlighter-rouge">@configurations</code>,
c’est-à-dire la première fois qu’on fait appel à <code class="highlighter-rouge">Rails.configuration.foo</code>, une
nouvelle instance d’<code class="highlighter-rouge">ActiveSupport::OrderedOptions.new</code> est créée. Il s’agit
d’une classe qui hérite de la classe <code class="highlighter-rouge">Hash</code> et qui fournit des accesseurs
dynamiques.</p>
<p>Avec un <code class="highlighter-rouge">Hash</code>, les paires clé-valeur sont généralement manipulées comme ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">h</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">h</span><span class="p">[</span><span class="ss">:boy</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'John'</span>
<span class="n">h</span><span class="p">[</span><span class="ss">:girl</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'Mary'</span>
<span class="n">h</span><span class="p">[</span><span class="ss">:boy</span><span class="p">]</span> <span class="c1"># => 'John'</span>
<span class="n">h</span><span class="p">[</span><span class="ss">:girl</span><span class="p">]</span> <span class="c1"># => 'Mary'</span>
<span class="n">h</span><span class="p">[</span><span class="ss">:dog</span><span class="p">]</span> <span class="c1"># => nil</span>
</code></pre></div></div>
<p>En utilisant un <code class="highlighter-rouge">OrderedOptions</code>, l’exemple ci-dessus peut être écrit comme
ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">h</span> <span class="o">=</span> <span class="no">ActiveSupport</span><span class="o">::</span><span class="no">OrderedOptions</span><span class="p">.</span><span class="nf">new</span>
<span class="n">h</span><span class="p">.</span><span class="nf">boy</span> <span class="o">=</span> <span class="s1">'John'</span>
<span class="n">h</span><span class="p">.</span><span class="nf">girl</span> <span class="o">=</span> <span class="s1">'Mary'</span>
<span class="n">h</span><span class="p">.</span><span class="nf">boy</span> <span class="c1"># => 'John'</span>
<span class="n">h</span><span class="p">.</span><span class="nf">girl</span> <span class="c1"># => 'Mary'</span>
<span class="n">h</span><span class="p">.</span><span class="nf">dog</span> <span class="c1"># => nil</span>
</code></pre></div></div>
<p>Il est aussi possible de lever une exception si la valeur est manquante :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">h</span><span class="p">.</span><span class="nf">dog!</span> <span class="c1"># => raises KeyError: :dog is blank</span>
</code></pre></div></div>
<p>Dans ce contexte, l’utilisation conjointe de <code class="highlighter-rouge">method_missing</code> et
<code class="highlighter-rouge">OrderedOptions</code> nous offre une grande souplesse à l’usage. C’est une approche
intéressante, notamment dans le cas d’un framework ou d’une bibliothèque
généraliste, mais coûteuse et déconseillée pour implémenter un code métier aux
règles de gestion bien connues et maîtrisées.</p>
<p>Remarquons ici une bonne pratique souvent oubliée lorsqu’on fait usage de
<code class="highlighter-rouge">method_missing</code> : implémenter également <code class="highlighter-rouge">respond_to_missing?</code> de manière à
indiquer si la méthode que l’on s’apprête à utiliser est implémentée ou non à la
volée par <code class="highlighter-rouge">method_missing</code>. Dans notre cas, on répondra toujours oui (<code class="highlighter-rouge">true</code>)
parce que notre implémentation de <code class="highlighter-rouge">method_missing</code> se comportera toujours comme
un accesseur, peu importe le nom de la méthode qu’on lui passe en argument.</p>
<h2 id="à-lusage">À l’usage</h2>
<p>Dans les faits, en suivant les recommandations précédentes, nous pourrions nous
retrouver avec une configuration applicative qui ressemble à ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/application.rb</span>
<span class="k">module</span> <span class="nn">MyApp</span>
<span class="k">class</span> <span class="nc">Application</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Application</span>
<span class="c1"># …</span>
<span class="n">config</span><span class="p">.</span><span class="nf">x</span><span class="p">.</span><span class="nf">api_url</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"API_URL"</span><span class="p">)</span>
<span class="n">config</span><span class="p">.</span><span class="nf">x</span><span class="p">.</span><span class="nf">api_scheme</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"API_SCHEME"</span><span class="p">,</span> <span class="s2">"http"</span><span class="p">)</span>
<span class="n">config</span><span class="p">.</span><span class="nf">x</span><span class="p">.</span><span class="nf">enable_foo</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"ENABLE_FOO"</span><span class="p">,</span> <span class="mi">0</span><span class="p">).</span><span class="nf">to_i</span><span class="p">.</span><span class="nf">positive?</span>
<span class="c1"># …</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Et l’utiliser de cette manière dans notre application :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">x</span><span class="p">.</span><span class="nf">api_url</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">x</span><span class="p">.</span><span class="nf">enable_foo</span> <span class="o">==</span> <span class="kp">true</span>
</code></pre></div></div>
<h2 id="allons-un-peu-plus-loin">Allons un peu plus loin</h2>
<p>Rails nous offre un outil supplémentaire qui peut s’avérer fort utile, j’ai
nommé <code class="highlighter-rouge">config_for</code>. Il s’agit d’un moyen de charger une configuration
applicative à partir d’un fichier <abbr title="Yet Another Markup Language">YAML</abbr>. Cerise sur le gâteau, l’environnement
courant de Rails est pris en compte ! Voici un petit exemple :</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/api_custom.yml</span>
<span class="na">defaults</span><span class="pi">:</span> <span class="nl">&defaults</span>
<span class="na">timeout</span><span class="pi">:</span> <span class="s"><%= ENV.fetch("API_CUSTOM_TIMEOUT", 20).to_i %></span>
<span class="na">development</span><span class="pi">:</span>
<span class="na"><<</span><span class="pi">:</span> <span class="nv">*defaults</span>
<span class="na">url</span><span class="pi">:</span> <span class="s"><%= ENV.fetch("API_CUSTOM_URL", "https://custom-dev.api.example.org/api/v2") %></span>
<span class="na">test</span><span class="pi">:</span>
<span class="na"><<</span><span class="pi">:</span> <span class="nv">*defaults</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">https://custom-test.api.custom.org/api/v2</span>
<span class="na">production</span><span class="pi">:</span>
<span class="na"><<</span><span class="pi">:</span> <span class="nv">*defaults</span>
<span class="na">url</span><span class="pi">:</span> <span class="s"><%= ENV.fetch("API_CUSTOM_URL", "https://custom.api.custom.org/api/v2") %></span>
</code></pre></div></div>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># config/application.rb</span>
<span class="k">class</span> <span class="nc">Application</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Application</span>
<span class="c1"># Custom Configuration</span>
<span class="n">config</span><span class="p">.</span><span class="nf">x</span><span class="p">.</span><span class="nf">api_custom</span> <span class="o">=</span> <span class="n">config_for</span><span class="p">(</span><span class="ss">:api_custom</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>À présent, nous pouvons faire appel à notre configuration :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">api_custom</span><span class="p">.</span><span class="nf">timeout</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">configuration</span><span class="p">.</span><span class="nf">api_custom</span><span class="p">.</span><span class="nf">url</span>
</code></pre></div></div>
<p>Avouez que c’est bien pratique ! Ainsi notre configuration applicative est à la
fois centralisée et contextualisée ; fini les variables de configuration
obscures qui surgissent d’on ne sait où !</p>
<h2 id="ressources">Ressources</h2>
<ul>
<li><a href="https://guides.rubyonrails.org/configuring.html#custom-configuration" target="_blank" rel="external" hreflang="en">Rails custom configuration</a></li>
<li><a href="https://www.martinfowler.com/ieeeSoftware/failFast.pdf" target="_blank" rel="external" hreflang="en">Fail Fast, <em>by Jim Shore</em></a></li>
</ul>François VantommeFaut-il coder en français ?2021-06-04T00:00:00+02:002021-06-04T00:00:00+02:00repo://posts.collection/_posts/2021-06-04-faut-il-coder-en-fran%C3%A7ais.md<p>Voici une question qui me taraude depuis quelque temps et à laquelle je
souhaite apporter le fruit de mes réflexions et des échanges que j’ai pu avoir
avec mes consœurs et confrères. Alors, faut-il coder en français ? Eh bien, ça
dépend…</p>
<blockquote>
<p>Oui ça évidemment, on vous demande de répondre par oui ou par non, alors « ça
dépend » ça dépasse !</p>
<p>— Le Père-Noël est une ordure</p>
</blockquote>
<p>C’est-à-dire que la question est complexe et demande d’être contextualisée pour
pouvoir y apporter des éléments de réponse ou, à tout le moins, des axes de
réflexion.</p>
<h2 id="au-service-de-la-france">Au service de la France</h2>
<p>Le contexte le plus évident qui nous vient à l’esprit quand on commence à se
poser ce genre de question, c’est celui d’un projet réalisé par une équipe
francophone, pour un commanditaire francophone, à destination d’un public
francophone. Là, on en arrive vite à se demander pourquoi coder en anglais ?
Quel intérêt à cela ? Qu’a-t-on à y gagner ?</p>
<p>C’est vrai que dans ce genre de situation, la question semble légitime.
L’anglais n’étant généralement pas la langue maternelle d’un francophone, il en
maîtrisera moins les subtilités. De plus, il y a fort à parier que la majorité
des communications et échanges au sein de l’équipe, mais aussi avec le client et
les utilisateurs se feront en français. Et ce n’est pas sans incidence, car si
le choix de la terminologie s’est porté sur l’anglais, ça implique alors pour
bien se comprendre en toute situation de tenir à jour un lexique bilingue, de
manière à partager un vocabulaire commun. Si au contraire, le français avait été
employé pour nommer toute chose dans l’application, nous aurions pu éviter cette
étape de traduction parfois maladroite pour ne pas dire casse-gueule.</p>
<blockquote>
<p>MOÏSE, Qu’est-ce qui vous a pris de répondre au téléphone ?<br />
ANDRÉ MERLAUX, Eh bien (hésitant), le téléphone sonnait. Et j’ai décroché.<br />
MOÏSE, La logique m’échappe. (silence) Je ne comprends pas.<br />
ANDRÉ MERLAUX, Le téléphone… (silence) J’ai pensé que…<br />
MOÏSE, Vous n’êtes pas à la Sécurité sociale Merlaux. La moindre information
mal interprétée peut déclencher une guerre mondiale. (silence) Une guerre
mondiale !</p>
<p>— Au service de la France, saison 1, épisode 1</p>
</blockquote>
<p>Soyons honnêtes, il est très rare qu’un lexique soit défini, et encore plus rare
qu’il soit partagé entre les équipes métier chez le client et les équipes
techniques chez le prestataire. Alors maintenir un lexique bilingue relève de
l’utopie ! Pourtant il est crucial de se comprendre. Lorsqu’on échange avec
notre client, lorsqu’on souhaite réaliser un produit qui répond à ses besoins,
il est nécessaire de <em>comprendre</em> ces besoins. Pour ce faire, il nous faut
impérativement parler un langage commun. Et ce langage doit être celui du
client. Il ne nous appartient pas en tant que prestataire d’imposer à notre
client un vocabulaire qui lui est étranger. Notre métier consiste à comprendre
le sien, puis à le modéliser le plus fidèlement possible sous la forme d’un
programme informatique. À la lecture du code, tout ce qui fait la spécificité,
la particularité, la nature unique du métier de notre client doit transpirer. Si
votre client exerce dans le domaine médical, par exemple, il conviendra de
parler de patient et non d’utilisateur.</p>
<p>Ce langage commun est appelé <a href="http://institut-agile.fr/ubiquitous.html" target="_blank" rel="external"><em>ubiquitous language</em></a>
par Eric Evans dans son livre « Domain Driven Design » ; un langage omniprésent
donc, à la fois dans les échanges, mais également dans le code source.</p>
<h2 id="zazie-dans-le-métro">Zazie dans le métro</h2>
<p>Tout bien considéré, on se dit pourquoi pas tenter la chose ! Allons-y, faisons
transparaitre le vocabulaire métier dans notre code ! Oui mais…</p>
<blockquote>
<p>– glup, c’est d’un compliqué… Ah! enfin, des mots que tout le monde connaît…
vestalat… vésulien… vétilleux…euse… ça y est! Le voilà! Et en haut d’une page
encore. Vêtir. Y a même un accent circonchose. Oui: vêtir. Je vêts… là, vous
voyez si je m’esprimais bien tout à l’heure. Tu vêts, il vêt, nous vêtons,
vous vêtez… vous vêtez… c’est pourtant vrai… vous vêtez… marant… positivement
marant… Tiens… Et dévêtir?… regardons dévêtir… voyons voir… déversement…
déversoir… dévêtir… Le vlà. Dévêtir vé té se conje comme vêtir. On dit donc
dévêtez-vous. Eh bien, hurla-t-il brusquement, eh bien, ma toute belle,
dévêtez-vous! Et en vitesse! A poil!</p>
<p>— Zazie dans le métro de Raymond Queneau</p>
</blockquote>
<p>Les errances du langage et une syntaxe malmenée font la singularité de cette
œuvre de Raymond Queneau. C’est aussi ce qui caractérise les courageuses
tentatives de l’emploi du français dans des bases de code, le génie en moins.
Oui, ne nous mentons pas, quand on code en français, ça donne généralement
quelque chose comme ça :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">describe</span> <span class="s2">"DossierMailer"</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"creates a commentaire"</span> <span class="k">do</span>
<span class="n">expect</span><span class="p">(</span><span class="no">DossierMailer</span><span class="p">).</span>
<span class="nf">to</span> <span class="n">have_received</span><span class="p">(</span><span class="ss">:notify_new_commentaire_to_instructeur</span><span class="p">).</span>
<span class="nf">with</span><span class="p">(</span><span class="n">dossier</span><span class="p">,</span> <span class="n">instructeur_with_instant_message</span><span class="p">.</span><span class="nf">email</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Aïe, ça pique les yeux. On a ici un exemple où l’effort a été fait d’utiliser le
vocabulaire métier. On y retrouve « Dossier », « commentaire » ou encore
« instructeur ». Mais l’effort s’arrête là puisqu’on se retrouve avec un mélange
maladroit de français et d’anglais qui dessert l’objectif. Tout d’abord, la
lecture s’en trouve malaisée, le cerveau fait des nœuds, ne sachant plus s’il
lit un mot anglais ou français. Essayons de faire un peu mieux :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">describe</span> <span class="s2">"ExpediteurDeDossier"</span> <span class="k">do</span>
<span class="n">it</span> <span class="s2">"crée un commentaire"</span> <span class="k">do</span>
<span class="n">expect</span><span class="p">(</span><span class="no">ExpediteurDeDossier</span><span class="p">).</span>
<span class="nf">to</span> <span class="n">have_received</span><span class="p">(</span><span class="ss">:notifier_d_un_nouveau_commentaire_a_l_instructeur</span><span class="p">).</span>
<span class="nf">with</span><span class="p">(</span><span class="n">dossier</span><span class="p">,</span> <span class="n">instructeur_avec_message_instantane</span><span class="p">.</span><span class="nf">courriel</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Ce n’est là que mon ressenti, mais je ne suis pas certain qu’on ait clarifié
grand-chose… En fait, on butte sur plusieurs contraintes du langage et autres
particularités de la langue. Tout d’abord, l’évidence : les mots clés du
langage, ici Ruby, et des <abbr title="Domain Specific Language">DSL</abbr> comme celui de RSpec sont en anglais. Quand bien
même vous feriez l’effort de traduire le <abbr title="Domain Specific Language">DSL</abbr> de RSpec en français, il vous
serait impossible de redéfinir <a href="https://docs.ruby-lang.org/en/3.0.0/doc/keywords_rdoc.html" target="_blank" rel="external">les mots réservés du langage</a>.</p>
<p>Ensuite, la langue. On s’aperçoit, dès lors qu’on aborde des considérations
techniques, que le vocabulaire associé est très souvent en anglais et il peut
être difficile de trouver une traduction fidèle en français. Prenez « <em>mailer</em> »
dans l’exemple ci-dessus, que j’ai maladroitement traduit par « expéditeur »,
faute de mieux. On remarque ici une perte de sens. On comprend bien que quelque
chose sera envoyé, mais le fait que ce soit un courriel est totalement passé
sous silence. Plutôt qu’« expéditeur de dossier », il aurait été plus correct de
parler d’« expéditeur de courriel concernant un dossier ». Si vous aviez dans
l’idée de vous limiter à des lignes de 80 caractères, vous pouvez oublier !</p>
<h2 id="lempire-des-signes">L’empire des signes</h2>
<p>Notez aussi l’absence des diacritiques ! Ruby, comme bien d’autres langages, a
beau supporter parfaitement UTF-8, on ne voit pour ainsi dire jamais un seul
caractère accentué lorsque les variables sont en français. Et pourquoi diable !?
Rien ne nous empêche de nommer nos variables <code class="highlighter-rouge">Expéditeur</code> ou
<code class="highlighter-rouge">message_instantané</code>, pour reprendre l’exemple ci-dessus. Et en effet, c’est
syntaxiquement valide du point de vue du langage. Mais j’y vois bien une
contre-indication ou du moins quelque argument qu’on pourrait y opposer : ces
caractères ne sont pas toujours facilement accessibles au clavier, cela va
dépendre de sa disposition et si vous avez opté pour Qwerty, Dvorak ou Colemak,
les touches accentuées vous seront moins facilement accessibles. Et je ne parle
même pas des ligatures qu’on trouve par exemple dans <code class="highlighter-rouge">sœur</code> ou <code class="highlighter-rouge">ex_æquo</code> !</p>
<p>Maintenant, laissons là notre contexte franco-centré et imaginons que nous
collaborions à un projet <em>open source</em> <a href="https://github.com/makoto/japanize/" target="_blank" rel="external">initié par un Japonais</a>.
En parcourant le code, on pourrait y lire ceci :</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">module</span> <span class="nn">Japanize</span>
<span class="k">class</span> <span class="nc">Parser</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">sequence</span><span class="p">)</span>
<span class="vi">@sequence</span> <span class="o">=</span> <span class="n">sequence</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">parse</span>
<span class="vi">@sequence</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s1">' '</span><span class="p">).</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
<span class="n">s</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sr">/</span><span class="si">#{</span><span class="err">助詞</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s2">"|"</span><span class="p">)</span><span class="si">}</span><span class="sr">/</span><span class="p">)</span>
<span class="k">end</span><span class="p">.</span><span class="nf">flatten</span><span class="p">.</span><span class="nf">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
<span class="k">if</span> <span class="err">動詞</span><span class="p">[</span><span class="n">s</span><span class="p">]</span>
<span class="err">動詞</span><span class="p">[</span><span class="n">s</span><span class="p">]</span>
<span class="k">elsif</span> <span class="err">数字</span><span class="p">[</span><span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span>
<span class="no">NumberConverter</span><span class="p">.</span><span class="nf">convert</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Ah tout de suite, à moins d’être japonisant, on fait moins le malin ! Même la
coloration syntaxique du blog botte en touche ! Et pour cause : bien que ce code
soit totalement valide et fonctionnel, l’utilisation de kanjis pour nommer les
méthodes nous place dans une situation pour le moins inconfortable.</p>
<p>Lorsque Yukihiro “Matz” Matsumoto a commencé à développer Ruby en 1995, il a
utilisé des mots-clés anglais, mais il écrivait toute la documentation en
japonais ! En 2001, quand est publié <a href="https://ruby-doc.com/docs/ProgrammingRuby/" target="_blank" rel="external">Programming Ruby</a>,
la quasi-totalité de la documentation était encore en japonais. C’est pourquoi
ce langage fut si peu utilisé en dehors de son archipel natal durant ses
premières années. Mais à présent, c’est un langage utilisé à travers le monde,
et le fait qu’il soit né au Japon n’a qu’un intérêt historique. Si le langage
avait utilisé des mots-clés en hiragana, il aurait eu beaucoup plus de mal à
gagner en popularité.</p>
<p>Ce qui nous amène à l’internationalisation. Les choses évoluent ; les contextes
aussi. Il se peut qu’un projet soit amené à rencontrer un public plus vaste que
prévu. Autant quand il s’agit d’un projet français à destination d’un ministère
ou d’une collectivité territoriale, on peut raisonnablement penser que la langue
vernaculaire sera le français ; autant quand il s’agit d’une suite bureautique,
on a de bonnes raisons de mettre cette affirmation en doute.</p>
<h2 id="the-office">The Office</h2>
<p>Une évolution de contexte, c’est justement ce qui est arrivé à StarOffice.
Initialement développé par l’entreprise allemande Star Division, ce projet a été
libéré en 2000 suite au rachat de l’entreprise par Sun, donnant ainsi naissance
à OpenOffice et ouvrant la porte à une communauté internationale de
contributeurs. Seul hic, la base de code comportait quelque 100 000 lignes de
commentaires en allemand ! <a href="https://bugs.documentfoundation.org/show_bug.cgi?id=39468" target="_blank" rel="external">La traduction de ces commentaires en anglais</a>
s’est étalée de 2011 à 2018.</p>
<p>Et on ne parle ici que de documentation ! Imaginez si les classes, les méthodes,
les variables et autres constantes étaient elles aussi formulées en allemand…
ç’aurait été une autre paire de manche !</p>
<h2 id="alors-quoi">Alors quoi ?</h2>
<p>Nous venons de voir que choisir de coder et documenter en français peut avoir de
forts impacts, tant positifs que négatifs. C’est pourquoi il est important que
ce choix soit fait en conscience. Et si vous optez pour le français, faites-le
pleinement ! Cela évitera des résultats hybrides dont tout le monde se serait
bien passé. Écrivez votre code en français, dans la limite des possibilités
offertes par votre langage et des conventions de nommage. Mais rédigez aussi vos
commits, vos tests et votre documentation en français.</p>
<p>Ce faisant, les échanges avec le métier seront plus fluides. Pas besoin de
traduire le langage métier en langage technique, et vice-versa. La terminologie
gagnera aussi en précision. Lorsqu’il s’agit d’un domaine métier ayant un
vocabulaire riche et précis (légal, médical…), et que celui-ci vous est
transmis en français, trouver une correspondance en anglais peut s’avérer
délicat voire hasardeux quand on n’est ni bilingue, ni du métier. Au final, le
code et tout ce qui aura été produit traduiront avec une grande fidélité le
besoin exprimé par le client si l’on s’épargne la phase de traduction.</p>
<p>Si au contraire vous optez pour l’anglais, c’est correct. Mais soyez rigoureux
et vigilant ; veillez à ce que des termes français ne se glissent pas au beau
milieu de votre code ou d’une description de <em>merge request</em>. Efforcez-vous
aussi à rédiger dans un anglais correct plutôt qu’un <em>globish</em> saupoudré
de franglicismes.</p>
<p>Rédigé en français, le code n’est plus accessible aux non-francophones. À
l’inverse, rédigé en anglais il sera peut-être moins abordable pour qui n’est
pas à l’aise avec cette langue. Dans les deux cas, cela pose la question de
l’inclusion et de l’intégration au sein de votre entreprise ou communauté.</p>
<p>Peu importe votre choix, assumez-le, faites-le vôtre, et itérez pour trouver le
compromis qui, selon votre contexte, vous permettra d’en tirer le meilleur bénéfice.</p>François Vantomme