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 &amp; 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 &amp; 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">=&gt;</span> <span class="c1">#&lt;User:0x00007fbdd11499c0 @firstname="Ada", @lastname="Lovelace"&gt;</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">=&gt;</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">&#8617;</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 &amp;&amp; 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">&gt;=</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 '&gt;=' [.g13271.join-r=0+500&lt;138,685,712,-524&gt;|.g13277.join-l=1+500&lt;-362,294,712,-299&gt;] </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 -- '-&lt;&lt; --&lt; -&lt;- &lt;-- &lt;--- &lt;&lt;- &lt;- -&gt; -&gt;&gt; --&gt; ---&gt; -&gt;- &gt;- &gt;&gt;=' '=&lt;&lt; =&lt; =&lt;= &lt;== &lt;=== &lt;&lt;= &lt;= =&gt; =&gt;&gt; ==&gt; ===&gt; =&gt;= &gt;= &gt;&gt;=' '&lt;-&gt; &lt;--&gt; &lt;---&gt; &lt;----&gt; &lt;=&gt; &lt;==&gt; &lt;===&gt; &lt;====&gt; :: ::: __' '&lt;~~ %F{red}&lt;/ &lt;/&gt; /&gt;%f ~~&gt; == != %F{blue}/= ~=%f &lt;&gt; === !== !=== %F{blue}=/= =!=%f' '&lt;: := *= *+ &lt;* &lt;*&gt; *&gt; &lt;| &lt;|&gt; |&gt; &lt;. &lt;.&gt; .&gt; +* =* =: :&gt;' '(* *) %F{red}/* */%f [| |] {| |} ++ +++ %F{red}\/ /\%f \- -| &lt;!-- &lt;!---' </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">&amp;&amp;</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">&gt;</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">=&gt;</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">&gt;</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">=&gt;</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">=&gt;</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">&lt;</span><span class="o">=&gt;</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">&lt;=&gt;</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">&lt;=&gt;</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">&gt;</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">=&gt;</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">&gt;</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">=&gt;</span> <span class="c1">#&lt;Money:0x00007faee7162f68 @amount=730, @currency="JPY"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</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">=&gt;</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">&gt;</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">&gt;</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">=&gt;</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">&lt;</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">&gt;</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">=&gt;</span> <span class="c1">#&lt;Invoice id: nil, total_amount: 0.5e1, total_currency: "EUR"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total</span> <span class="o">=&gt;</span> <span class="c1">#&lt;Money:0x00007f1d1006b038 @amount=5, @currency="EUR"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</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">=&gt;</span> <span class="c1">#&lt;Money:0x000055eca216b658 @amount=500, @currency="JPY"&gt;</span> <span class="n">irb</span><span class="p">(</span><span class="n">main</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total_amounnt</span> <span class="o">=&gt;</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">&gt;</span> <span class="n">invoice</span><span class="p">.</span><span class="nf">total_currency</span> <span class="o">=&gt;</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 &amp; 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">&lt;</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">&lt;</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">&lt;</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 &amp; 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 &amp; 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">&amp;</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">&lt;</span><span class="k">null</span><span class="o">&gt;</span> <span class="o">|</span> <span class="o">&lt;</span><span class="k">null</span><span class="o">&gt;</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">&lt;</span><span class="k">null</span><span class="o">&gt;</span> <span class="o">|</span> <span class="o">&lt;</span><span class="k">null</span><span class="o">&gt;</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 &amp; 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 &amp; set</em> pour le stockage mutable, <em>read &amp; 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 &amp; 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 &amp; 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 &amp; 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">[:\&lt;:</span><span class="p">]</span><span class="sr">]tldr</span><span class="p">[</span><span class="sr">[:\&gt;:</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">[[:\&lt;:]]</code> et <code class="highlighter-rouge">[[:\&gt;:]]</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">\&lt;</span>:]]man[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]man[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]man[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</span>:]] <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span><span class="s2">tldr</span><span class="se">\&gt;</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&amp;&amp;</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">&amp;&amp;</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">&amp;&amp;</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">\&lt;</span><span class="s2">tldr</span><span class="se">\&gt;</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&amp;&amp;</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">\&lt;</span>:]]tldr[[:<span class="se">\&gt;</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">\&lt;</span><span class="s2">tldr</span><span class="se">\&gt;</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">?&lt;=</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">\\&lt;</code> <code class="highlighter-rouge">\\&gt;</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">[[:&lt;:]]</code> <code class="highlighter-rouge">[[:&gt;:]]</code></td> <td style="text-align: left"><code class="highlighter-rouge">(?&lt;=\W)(?=\w)│(?&lt;=\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">[^[:&lt;:]]</code> <code class="highlighter-rouge">[^[:&gt;:]]</code></td> <td style="text-align: left"><code class="highlighter-rouge">(?&lt;=\W)(?=\W)│(?&lt;=\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">][!"#$%&amp;'()\*+,./:;&lt;=&gt;?@\^_{│}~-</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">&lt;</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"># =&gt; 'John'</span> <span class="n">h</span><span class="p">[</span><span class="ss">:girl</span><span class="p">]</span> <span class="c1"># =&gt; 'Mary'</span> <span class="n">h</span><span class="p">[</span><span class="ss">:dog</span><span class="p">]</span> <span class="c1"># =&gt; 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"># =&gt; 'John'</span> <span class="n">h</span><span class="p">.</span><span class="nf">girl</span> <span class="c1"># =&gt; 'Mary'</span> <span class="n">h</span><span class="p">.</span><span class="nf">dog</span> <span class="c1"># =&gt; 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"># =&gt; 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">&lt;</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">&amp;defaults</span> <span class="na">timeout</span><span class="pi">:</span> <span class="s">&lt;%= ENV.fetch("API_CUSTOM_TIMEOUT", 20).to_i %&gt;</span> <span class="na">development</span><span class="pi">:</span> <span class="na">&lt;&lt;</span><span class="pi">:</span> <span class="nv">*defaults</span> <span class="na">url</span><span class="pi">:</span> <span class="s">&lt;%= ENV.fetch("API_CUSTOM_URL", "https://custom-dev.api.example.org/api/v2") %&gt;</span> <span class="na">test</span><span class="pi">:</span> <span class="na">&lt;&lt;</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">&lt;&lt;</span><span class="pi">:</span> <span class="nv">*defaults</span> <span class="na">url</span><span class="pi">:</span> <span class="s">&lt;%= ENV.fetch("API_CUSTOM_URL", "https://custom.api.custom.org/api/v2") %&gt;</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">&lt;</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