<?xml version='1.0' encoding='UTF-8'?>
<rss version='2.0'>
<channel>
<title>clavier ouvert</title>
<link>https://adfoucart.be/clavier</link>
<description>Blog d'enseignement d'Adrien Foucart. Toutes les opinions présentées ici n'engagent que moi. Blog garanti sans pub, sans traqueurs, et 100% rédigé par un humain.</description>
<language>fr-be</language>

<item>
    <title>2026.04.03 C Orienté Objet | 2</title>
    <link>https://adfoucart.be/clavier/2026.04.03.html</link>
    <description>
        <h2><span class='emoji'>🟨</span>2026.04.03<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2026.04.03.html">C
Orienté Objet | 2</a></span></h2>
        <p>Nous avons lors du <a href="./2026.04.01.html">premier
        épisode</a> créé une “classe” <code>Person</code>, mais elle se
        sent bien seule. Les objets sont fait pour communiquer, avoir
        des relations, s’envoyer des messages. Pour l’instant, nous
        n’avons rien de tout ça. Commençons.</p>
        <h2 id="une-table-de-personnes">Une table de personnes</h2>
        <p>Je voudrais avoir un tableau de personnes, avec la
        possibilité d’y ajouter et d’en retirer des éléments, de
        supprimer ou récupérer des éléments par leur index, de savoir si
        la table contient une certaine personne, et d’en afficher le
        contenu. On a du boulot. Commençons par écrire
        <code>personsTable.h</code> pour définir tout ça proprement:</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdbool.h&gt;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&quot;person.h&quot;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> PersonsTable PersonsTable<span class="op">;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>PersonsTable<span class="op">*</span> PersonsTable_make<span class="op">();</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> PersonsTable_destroy<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">);</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> PersonsTable_add_person<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> Person<span class="op">*</span> p<span class="op">);</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span> PersonsTable_remove<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> <span class="dt">int</span> idx<span class="op">);</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>Person<span class="op">*</span> PersonsTable_get<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> <span class="dt">int</span> idx<span class="op">);</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span> PersonsTable_contains<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> Person<span class="op">*</span> p<span class="op">);</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> PersonsTable_print<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">);</span></span></code></pre></div>
        <p>Il y a de nombreuses façons d’implémenter ces
        fonctionnalités. L’idée est donc ici que les
        <em>utilisateurs</em> de ma “classe” ne devront pas s’en
        préoccuper. Et si je veux changer, ça ne devrait pas les
        affecter.</p>
        <p>En particulier, j’ai deux grands choix à faire ici:</p>
        <ul>
        <li>Quelle structure de donnée utiliser pour la “table”?</li>
        <li>Quelle est la définition de l’<em>égalité</em> pour mes
        Personnes (c’est-à-dire: comment je détermine si ma table
        contient une certaine Personne).</li>
        </ul>
        <p>Cette seconde question va me faire évoluer la classe
        <code>Person</code>. Commençons par là.</p>
        <h2 id="égalité-des-personnes">Égalité des personnes</h2>
        <p>Je dois rajouter une méthode dans <code>person.h</code>:</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span> Person_equals<span class="op">(</span>Person<span class="op">*</span> p<span class="op">,</span> Person<span class="op">*</span> other<span class="op">);</span></span></code></pre></div>
        <p>Et évidemment l’implémenter dans <code>person.c</code>. Je
        peux utiliser <a
        href="https://en.cppreference.com/w/c/string/byte/strcmp"><code>strcmp</code></a>
        pour comparer des chaînes de caractères. Je prendrai donc la
        définition de l’égalité ici comme: “a le même nom et le même
        prénom”. J’aurais pu à la place choisir de vérifier si l’adresse
        des deux objets en mémoire était la même, mais ce serait fort
        limitant. Évidemment, dans un vrai système, j’aurais besoin de
        quelque chose de plus “identifiant” que le nom et le prénom,
        mais on va rester simple pour l’instant. <code>strcmp</code>
        compare deux chaînes de caractères (“null-terminated”,
        c’est-à-dire qu’il y a un caractère nul <code>\0</code> pour
        marquer la fin), et renvoie <code>0</code> si tous les
        caractères sont les mêmes.</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span> Person_equals<span class="op">(</span>Person<span class="op">*</span> p<span class="op">,</span> Person<span class="op">*</span> other<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="op">(</span>strcmp<span class="op">(</span>p<span class="op">-&gt;</span>first_name<span class="op">,</span> other<span class="op">-&gt;</span>first_name<span class="op">)==</span><span class="dv">0</span> <span class="op">&amp;&amp;</span> </span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>            strcmp<span class="op">(</span>p<span class="op">-&gt;</span>last_name<span class="op">,</span> other<span class="op">-&gt;</span>last_name<span class="op">)==</span><span class="dv">0</span><span class="op">);</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Testons dans le <code>main</code>:</p>
        <div class="sourceCode" id="cb4"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>Person<span class="op">*</span> p <span class="op">=</span> Person_make<span class="op">(</span><span class="st">&quot;Adrien&quot;</span><span class="op">,</span> <span class="st">&quot;Foucart&quot;</span><span class="op">);</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>Person<span class="op">*</span> other <span class="op">=</span> Person_make<span class="op">(</span><span class="st">&quot;Adrien&quot;</span><span class="op">,</span> <span class="st">&quot;Foucart&quot;</span><span class="op">);</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>Person_print<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>Person_print<span class="op">(</span>other<span class="op">);</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> <span class="op">(</span>Person_equals<span class="op">(</span>p<span class="op">,</span> other<span class="op">)){</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;Persons are equal.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a><span class="cf">else</span> <span class="op">{</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;Persons are not equal.</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>Person_destroy<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>Person_destroy<span class="op">(</span>other<span class="op">);</span></span></code></pre></div>
        <p>Je reçois bien <code>Persons are equal</code>. Et si je
        change une lettre à mon nom, j’ai bien <code>unequal</code>.
        Tout va bien! Revenons à notre table.</p>
        <h2 id="tableau-redimensionnable">Tableau redimensionnable</h2>
        <p>Une manière classique de réaliser une table est le principe
        du “tableau redimensionnable”. Il implique de garder en
        parallèle une “taille physique” (espace mémoire occupé par la
        table) et une “taille logique” (nombre d’éléments dans la
        table). Lorsque la taille logique rattrape la taille physique,
        on agrandit la table (généralement en la doublant). On traduit
        ça dans notre <code>struct PersonsTable</code>:</p>
        <div class="sourceCode" id="cb5"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">// personsTable.c</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> PersonsTable <span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    Person<span class="op">**</span> table<span class="op">;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> capacity<span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> size<span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span></code></pre></div>
        <p>Le constructeur devra donc donner une capacité initiale à la
        table, et une taille logique (<code>size</code>) de 0. Notons le
        “double pointeur” pour <code>table</code>. Schématiquement,
        j’ai:</p>
        <pre><code>[table] --&gt; Addr: 0x1234 (exemple)
[0x1234] --&gt; [Person a]
[0x1235] --&gt; [Person b]
[0x1236] --&gt; [Person c]</code></pre>
        <p><code>table</code> pointe vers l’adresse du premier élément
        du tableau, qui se trouve quelque part dans la mémoire du
        programme. Ce premier élément est lui-même un pointeur vers une
        <code>Person</code>. L’élément suivant dans la mémoire sera le
        pointeur vers la personne suivante, etc. Au niveau du
        constructeur, j’aurai alors:</p>
        <div class="sourceCode" id="cb7"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>PersonsTable<span class="op">*</span> PersonsTable_make<span class="op">(){</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>    PersonsTable<span class="op">*</span> pt <span class="op">=</span> malloc<span class="op">(</span><span class="kw">sizeof</span><span class="op">(</span>PersonsTable<span class="op">));</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>    pt<span class="op">-&gt;</span>size <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>    pt<span class="op">-&gt;</span>capacity <span class="op">=</span> <span class="dv">16</span><span class="op">;</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>    pt<span class="op">-&gt;</span>table <span class="op">=</span> malloc<span class="op">(</span><span class="kw">sizeof</span><span class="op">(</span>Person<span class="op">*)*</span>pt<span class="op">-&gt;</span>capacity<span class="op">);</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> pt<span class="op">;</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>J’ai deux <a
        href="https://en.cppreference.com/w/c/memory/malloc"><code>malloc</code></a>
        dans le constructeur, j’aurai donc besoin de deux <a
        href="https://en.cppreference.com/w/c/memory/free.html"><code>free</code></a>
        dans le destructeur:</p>
        <div class="sourceCode" id="cb8"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> PersonsTable_destroy<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">)</span> <span class="op">{</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>    free<span class="op">(</span>t<span class="op">-&gt;</span>table<span class="op">);</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>    free<span class="op">(</span>t<span class="op">);</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Notons un choix fait ici: je ne libère pas les
        <code>Person</code>. Elles n’ont pas été construites par la
        classe table, donc elles peuvent exister ailleurs dans le
        programme. Ce n’est pas la responsabilité de
        <code>PersonsTable</code> de s’en préoccuper.</p>
        <p>Quand on ajoute une personne, on doit d’abord vérifier si on
        ne doit pas allonger la table (avec <a
        href="https://en.cppreference.com/w/c/memory/realloc.html"><code>realloc</code></a>).
        Et il ne faut pas oublier d’incrémenter la taille.</p>
        <div class="sourceCode" id="cb9"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> PersonsTable_add_person<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> Person<span class="op">*</span> p<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>t<span class="op">-&gt;</span>size <span class="op">==</span> t<span class="op">-&gt;</span>capacity<span class="op">){</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>        t<span class="op">-&gt;</span>capacity <span class="op">*=</span> <span class="dv">2</span><span class="op">;</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>        t<span class="op">-&gt;</span>table <span class="op">=</span> realloc<span class="op">(</span>t<span class="op">-&gt;</span>table<span class="op">,</span> t<span class="op">-&gt;</span>capacity<span class="op">);</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a>    t<span class="op">-&gt;</span>table<span class="op">[</span>t<span class="op">-&gt;</span>size<span class="op">++]</span> <span class="op">=</span> p<span class="op">;</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Pour retirer une personne, il faut s’assurer que l’index est
        valide. On retire une personne en décalant les éléments suivants
        vers la gauche. On renvoie un booléen pour indiquer si
        l’opération a réussit. Et on n’oublie pas de décrémenter la
        taille.</p>
        <div class="sourceCode" id="cb10"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span> PersonsTable_remove<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> <span class="dt">int</span> idx<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>idx <span class="op">&lt;</span> <span class="dv">0</span> <span class="op">||</span> idx <span class="op">&gt;=</span> t<span class="op">-&gt;</span>size<span class="op">)</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> idx<span class="op">;</span> i <span class="op">&lt;</span> t<span class="op">-&gt;</span>size <span class="op">-</span> <span class="dv">1</span><span class="op">;</span> i<span class="op">++)</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a>        t<span class="op">-&gt;</span>table<span class="op">[</span>i<span class="op">]</span> <span class="op">=</span> t<span class="op">-&gt;</span>table<span class="op">[</span>i<span class="op">+</span><span class="dv">1</span><span class="op">];</span></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>    t<span class="op">-&gt;</span>size<span class="op">--;</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Récupérer un élément est assez simple. On renvoie
        <code>NULL</code> si l’index n’est pas valide:</p>
        <div class="sourceCode" id="cb11"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a>Person<span class="op">*</span> PersonsTable_get<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> <span class="dt">int</span> idx<span class="op">)</span> <span class="op">{</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>idx <span class="op">&lt;</span> <span class="dv">0</span> <span class="op">||</span> idx <span class="op">&gt;=</span> t<span class="op">-&gt;</span>size<span class="op">)</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> NULL<span class="op">;</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> t<span class="op">-&gt;</span>table<span class="op">[</span>idx<span class="op">];</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Pour savoir si notre table contient un élément, on n’a pas
        trop le choix: il faut les parcourir tous. Je ne prétends pas
        ici à une grande efficacité!</p>
        <div class="sourceCode" id="cb12"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="dt">bool</span> PersonsTable_contains<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">,</span> Person<span class="op">*</span> p<span class="op">)</span> <span class="op">{</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> t<span class="op">-&gt;</span>size<span class="op">;</span> i<span class="op">++){</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>        <span class="cf">if</span> <span class="op">(</span>Person_equals<span class="op">(</span>p<span class="op">,</span> t<span class="op">-&gt;</span>table<span class="op">[</span>i<span class="op">]))</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>            <span class="cf">return</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Et finalement on veut pouvoir les afficher:</p>
        <div class="sourceCode" id="cb13"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> PersonsTable_print<span class="op">(</span>PersonsTable<span class="op">*</span> t<span class="op">)</span> <span class="op">{</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;[PersonsTable]:</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i<span class="op">=</span><span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> t<span class="op">-&gt;</span>size<span class="op">;</span> i<span class="op">++){</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a>        printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">\t</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a>        Person_print<span class="op">(</span>t<span class="op">-&gt;</span>table<span class="op">[</span>i<span class="op">]);</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Important ici: pour ces deux dernière méthodes, nous avons
        fait appel aux méthodes de <code>Person</code>. Des objets qui
        s’envoient des messages, comme il faut!</p>
        <p>Reste à tester que ça fonctionne.</p>
        <div class="sourceCode" id="cb14"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(){</span></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;Start program</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Create test persons</span></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a>    Person<span class="op">*</span> p <span class="op">=</span> Person_make<span class="op">(</span><span class="st">&quot;Adrien&quot;</span><span class="op">,</span> <span class="st">&quot;Foucart&quot;</span><span class="op">);</span></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a>    Person<span class="op">*</span> other <span class="op">=</span> Person_make<span class="op">(</span><span class="st">&quot;Leonardo&quot;</span><span class="op">,</span> <span class="st">&quot;Da Vinci&quot;</span><span class="op">);</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Create and fill table</span></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a>    PersonsTable<span class="op">*</span> pt <span class="op">=</span> PersonsTable_make<span class="op">();</span></span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a>    PersonsTable_add_person<span class="op">(</span>pt<span class="op">,</span> p<span class="op">);</span></span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a>    PersonsTable_add_person<span class="op">(</span>pt<span class="op">,</span> other<span class="op">);</span></span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-14"><a href="#cb14-14" aria-hidden="true" tabindex="-1"></a>    PersonsTable_print<span class="op">(</span>pt<span class="op">);</span></span>
<span id="cb14-15"><a href="#cb14-15" aria-hidden="true" tabindex="-1"></a>    </span>
<span id="cb14-16"><a href="#cb14-16" aria-hidden="true" tabindex="-1"></a>    <span class="co">// check contains method</span></span>
<span id="cb14-17"><a href="#cb14-17" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>PersonsTable_contains<span class="op">(</span>pt<span class="op">,</span> p<span class="op">)){</span></span>
<span id="cb14-18"><a href="#cb14-18" aria-hidden="true" tabindex="-1"></a>        printf<span class="op">(</span><span class="st">&quot;Table contains person: &quot;</span><span class="op">);</span></span>
<span id="cb14-19"><a href="#cb14-19" aria-hidden="true" tabindex="-1"></a>        Person_print<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb14-20"><a href="#cb14-20" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb14-21"><a href="#cb14-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-22"><a href="#cb14-22" aria-hidden="true" tabindex="-1"></a>    <span class="co">// check get method</span></span>
<span id="cb14-23"><a href="#cb14-23" aria-hidden="true" tabindex="-1"></a>    Person<span class="op">*</span> found <span class="op">=</span> PersonsTable_get<span class="op">(</span>pt<span class="op">,</span> <span class="dv">1</span><span class="op">);</span></span>
<span id="cb14-24"><a href="#cb14-24" aria-hidden="true" tabindex="-1"></a>    Person_print<span class="op">(</span>found<span class="op">);</span></span>
<span id="cb14-25"><a href="#cb14-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-26"><a href="#cb14-26" aria-hidden="true" tabindex="-1"></a>    <span class="co">// check remove</span></span>
<span id="cb14-27"><a href="#cb14-27" aria-hidden="true" tabindex="-1"></a>    PersonsTable_remove<span class="op">(</span>pt<span class="op">,</span> <span class="dv">0</span><span class="op">);</span></span>
<span id="cb14-28"><a href="#cb14-28" aria-hidden="true" tabindex="-1"></a>    PersonsTable_print<span class="op">(</span>pt<span class="op">);</span></span>
<span id="cb14-29"><a href="#cb14-29" aria-hidden="true" tabindex="-1"></a>    </span>
<span id="cb14-30"><a href="#cb14-30" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(!</span>PersonsTable_contains<span class="op">(</span>pt<span class="op">,</span> p<span class="op">)){</span></span>
<span id="cb14-31"><a href="#cb14-31" aria-hidden="true" tabindex="-1"></a>        printf<span class="op">(</span><span class="st">&quot;Table does not contain person: &quot;</span><span class="op">);</span></span>
<span id="cb14-32"><a href="#cb14-32" aria-hidden="true" tabindex="-1"></a>        Person_print<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb14-33"><a href="#cb14-33" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb14-34"><a href="#cb14-34" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-35"><a href="#cb14-35" aria-hidden="true" tabindex="-1"></a>    PersonsTable_destroy<span class="op">(</span>pt<span class="op">);</span></span>
<span id="cb14-36"><a href="#cb14-36" aria-hidden="true" tabindex="-1"></a>    Person_destroy<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb14-37"><a href="#cb14-37" aria-hidden="true" tabindex="-1"></a>    Person_destroy<span class="op">(</span>other<span class="op">);</span></span>
<span id="cb14-38"><a href="#cb14-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-39"><a href="#cb14-39" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;End program</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb14-40"><a href="#cb14-40" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb14-41"><a href="#cb14-41" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Ce qui me donne:</p>
        <pre><code>[PersonsTable]:
        [Person]: Adrien Foucart
        [Person]: Leonardo Da Vinci
Table contains person: [Person]: Adrien Foucart
[Person]: Leonardo Da Vinci
[PersonsTable]:
        [Person]: Leonardo Da Vinci
Table does not contain person: [Person]: Adrien Foucart
End program</code></pre>
        <h2 id="un-peu-de-recul">Un peu de recul</h2>
        <p>Qu’avons-nous jusqu’à présent?</p>
        <ul>
        <li>Des objets qui offrent des <em>interfaces publiques</em> et
        disposent d’<em>attributs privés</em>.</li>
        <li>Des objets qui sont responsables des opérations qui les
        concernent, et peuvent déléguer du travail en envoyant des
        messages.</li>
        </ul>
        <p>C’est déjà pas mal, pour une poignée de lignes de code!</p>
        <p>Mais j’ai l’impression qu’on peut aller un peu plus loin. Une
        prochaine fois.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2026.04.03.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Thu, 02 Apr 2026 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2026.04.01 C Orienté Objet | 1</title>
    <link>https://adfoucart.be/clavier/2026.04.01.html</link>
    <description>
        <h2><span class='emoji'>🟨</span>2026.04.01<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2026.04.01.html">C
Orienté Objet | 1</a></span></h2>
        <p>Le C n’est généralement pas un langage associé à la
        programmation orientée objet. Quand j’ai appris le C++, on me
        l’a initialement un peu présenté comme “du C mais avec des
        objets”, et c’est comme ça que j’ai généralement regardé ces
        langages (que j’utilise très peu) jusqu’à présent.</p>
        <p>Mais en “jouant” à résoudre les vieux puzzles <em>Advent of
        Code</em> en C (voir <a
        href="https://notes.adfoucart.be/aocode15">mes notes à ce
        sujet!</a>), je me suis rendu compte que je commençais à
        utiliser naturellement des principes “orienté objet” dans mes
        résolutions. On a parfois je pense (en tout cas moi!) un peu
        tendance à réduire “orienté objet” à “on peut déclarer des
        classes”. Mais on peut avoir des classes et des objets sans
        <em>faire</em> de la programmation orientée objet (beaucoup de
        projets Python en sont un bon exemple!), et on peut faire de la
        programmation orientée objet sans classes. Car au-delà de la
        syntaxe, c’est surtout dans la façon d’organiser et de penser
        son code que les concepts orientés objets apparaissent.</p>
        <p>Et après tout, beaucoup de langages “orientés objets” sont
        <em>écris</em> en C, donc c’est nécessairement possible d’en
        faire en revenant à la source!</p>
        <p>Essayons.</p>
        <h2 id="une-classe-person">Une classe <code>Person</code></h2>
        <p>Commençons par un classique: une classe <code>Person</code>,
        qui contiendrait un nom et un prénom. Nous n’avons pas de
        “classes” en <code>C</code>, mais on peut déclarer des
        structures:</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> Person <span class="op">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="dt">char</span><span class="op">*</span> first_name<span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">char</span><span class="op">*</span> last_name<span class="op">;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span></code></pre></div>
        <p>Ici, on a donc bien une <code>Person</code> qui possède un
        <code>first_name</code> et un <code>last_name</code>. Si je veux
        l’utiliser dans mon code, je pourrais faire quelque chose comme
        ceci:</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> Person<span class="op">*</span> p <span class="op">=</span> malloc<span class="op">(</span><span class="kw">sizeof</span><span class="op">(</span><span class="kw">struct</span> Person<span class="op">));</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>p<span class="op">-&gt;</span>first_name <span class="op">=</span> <span class="st">&quot;Adrien&quot;</span><span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>p<span class="op">-&gt;</span>last_name <span class="op">=</span> <span class="st">&quot;Foucart&quot;</span><span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>printf<span class="op">(</span><span class="st">&quot;</span><span class="sc">%s</span><span class="st"> </span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> p<span class="op">-&gt;</span>first_name<span class="op">,</span> p<span class="op">-&gt;</span>last_name<span class="op">);</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>free<span class="op">(</span>p<span class="op">);</span></span></code></pre></div>
        <p>Avec <code>malloc</code>, j’alloue dans ma mémoire l’espace
        pour mes deux pointeurs, que je fais pointer vers des chaînes de
        caractère.</p>
        <p>Dans une logique “orientée objet”, cependant, je ne veux pas
        que ces détails d’implémentation de ma classe (comme le nom des
        attributs) soient connus. Je voudrais idéalement ne manipuler
        <em>que</em> des méthodes “publiques”.</p>
        <p>On va donc commencer par créer deux fichiers:
        <code>person.c</code> et <code>person.h</code>. Dans
        <code>person.h</code>, nous allons mettre nos déclarations
        publiques:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">// person.h</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">typedef</span> <span class="kw">struct</span> Person Person<span class="op">;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>Person<span class="op">*</span> Person_make<span class="op">(</span><span class="dt">char</span><span class="op">*</span> first_name<span class="op">,</span> <span class="dt">char</span><span class="op">*</span> last_name<span class="op">);</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> Person_destroy<span class="op">(</span>Person<span class="op">*</span> p<span class="op">);</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> Person_print<span class="op">(</span>Person<span class="op">*</span> p<span class="op">);</span></span></code></pre></div>
        <p>Je prévois ici trois méthodes: un constructeur, un
        destructeur, et une méthode pour afficher le contenu de l’objet.
        Avec <code>typedef struct Person Person</code>, on permet de ne
        pas retaper <code>struct Person</code> à chaque fois en lui
        donnant un “alias” <code>Person</code>.</p>
        <p>L’implémentation, maintenant, va se trouver dans
        <code>person.c</code>, et sera invisible pour les utilisateurs
        de <code>person.h</code>:</p>
        <div class="sourceCode" id="cb4"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdlib.h&gt;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;string.h&gt;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&quot;person.h&quot;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> Person <span class="op">{</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">char</span><span class="op">*</span> first_name<span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>    <span class="dt">char</span><span class="op">*</span> last_name<span class="op">;</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>Person<span class="op">*</span> Person_make<span class="op">(</span><span class="dt">char</span><span class="op">*</span> first_name<span class="op">,</span> <span class="dt">char</span><span class="op">*</span> last_name<span class="op">){</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>    Person<span class="op">*</span> p <span class="op">=</span> malloc<span class="op">(</span><span class="kw">sizeof</span><span class="op">(</span>Person<span class="op">));</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>    p<span class="op">-&gt;</span>first_name <span class="op">=</span> malloc<span class="op">(</span>strlen<span class="op">(</span>first_name<span class="op">)+</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>    p<span class="op">-&gt;</span>last_name <span class="op">=</span> malloc<span class="op">(</span>strlen<span class="op">(</span>last_name<span class="op">)+</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>    strcpy<span class="op">(</span>p<span class="op">-&gt;</span>first_name<span class="op">,</span> first_name<span class="op">);</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>    strcpy<span class="op">(</span>p<span class="op">-&gt;</span>last_name<span class="op">,</span> last_name<span class="op">);</span></span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> p<span class="op">;</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> Person_destroy<span class="op">(</span>Person<span class="op">*</span> p<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>    free<span class="op">(</span>p<span class="op">-&gt;</span>first_name<span class="op">);</span></span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>    free<span class="op">(</span>p<span class="op">-&gt;</span>last_name<span class="op">);</span></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>    free<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span> Person_print<span class="op">(</span>Person<span class="op">*</span> p<span class="op">)</span> <span class="op">{</span></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;[Person]: </span><span class="sc">%s</span><span class="st"> </span><span class="sc">%s\n</span><span class="st">&quot;</span><span class="op">,</span> p<span class="op">-&gt;</span>first_name<span class="op">,</span> p<span class="op">-&gt;</span>last_name<span class="op">);</span></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Dans le constructeur, on alloue de l’espace pour les chaînes
        de caractères des noms et prénoms, que l’on copie. On ne veut
        pas juste copier les pointeurs, sinon celui qui appelle
        <code>Person_make</code> pourrait ensuite changer la valeur de
        <code>p-&gt;first_name</code>, ou faire un <code>free</code>
        dessus. Dans le destructeur, on fait le ménage.</p>
        <p>Pour utiliser tout ceci, écrivons un <code>main.c</code>:</p>
        <div class="sourceCode" id="cb5"><pre
        class="sourceCode c"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdio.h&gt;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;stdlib.h&gt;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;string.h&gt;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&quot;person.h&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(){</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;Start program</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>    Person<span class="op">*</span> p <span class="op">=</span> Person_make<span class="op">(</span><span class="st">&quot;Adrien&quot;</span><span class="op">,</span> <span class="st">&quot;Foucart&quot;</span><span class="op">);</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>    Person_print<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>    Person_destroy<span class="op">(</span>p<span class="op">);</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    printf<span class="op">(</span><span class="st">&quot;End program</span><span class="sc">\n</span><span class="st">&quot;</span><span class="op">);</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Ce <code>main</code> n’a plus aucune visibilité sur ce que
        contient <code>Person</code>. Si j’essaie d’ajouter
        <code>printf("%s\n", p-&gt;first_name);</code>, j’ai une erreur
        de compilation, car <code>person.h</code> ne contient pas de
        détail sur les “attributs” de Person. Je ne peux les utiliser
        qu’à travers ses méthodes.</p>
        <p>Autrement dit: <code>Person</code> se comporte maintenant
        comme un “objet” qui, par exemple en Java, s’écrirait<a
        href="#fn1" class="footnote-ref" id="fnref1"
        role="doc-noteref"><sup>1</sup></a>:</p>
        <div class="sourceCode" id="cb6"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> Person <span class="op">{</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="bu">String</span> first_name<span class="op">;</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="bu">String</span> last_name<span class="op">;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="fu">Person</span><span class="op">(</span><span class="bu">String</span> first_name<span class="op">,</span> <span class="bu">String</span> last_name<span class="op">){</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>        <span class="kw">this</span><span class="op">.</span><span class="fu">first_name</span> <span class="op">=</span> first_name<span class="op">;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>        <span class="kw">this</span><span class="op">.</span><span class="fu">last_name</span> <span class="op">=</span> last_name<span class="op">;</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">void</span> <span class="fu">print</span><span class="op">(){</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a>        <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;[Person]: &quot;</span> <span class="op">+</span> first_name <span class="op">+</span> <span class="st">&quot; &quot;</span> <span class="op">+</span> last_name<span class="op">);</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On ne va pas encore très loin dans le concept, mais je dirais
        que ça compte, comme premier pas. Pour la suite… on verra dans
        les épisodes suivants!</p>
        <section id="footnotes"
        class="footnotes footnotes-end-of-document" role="doc-endnotes">
        <hr />
        <ol>
        <li id="fn1"><p>Techniquement, on ne fait pas ici la “copie” du
        contenu des <code>String</code> que l’on fait en <code>C</code>.
        Le <code>first_name</code> dans <code>Person</code> pointera
        vers la même adresse que le <code>String</code> dans le scope
        qui a appelé le constructeur. Mais comme en Java les
        <code>String</code> sont des constantes, ça ne risque pas de
        causer un soucis. Si quelque chose essaye de modifier le
        <code>String</code> par après en dehors de la classe, ça y
        créera un nouveau <code>String</code> avec une nouvelle adresse,
        et l’ancienne adresse (retenue par <code>Person</code>)
        contiendra toujours la même information.<a href="#fnref1"
        class="footnote-back" role="doc-backlink">↩︎</a></p></li>
        </ol>
        </section>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2026.04.01.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Tue, 31 Mar 2026 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2026.03.30 auto-complétion</title>
    <link>https://adfoucart.be/clavier/2026.03.30.html</link>
    <description>
        <h2><span class='emoji'>⏩</span>2026.03.30<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2026.03.30.html">auto-complétion</a></span></h2>
        <p>Durant les séances d’exercice, je me retrouve souvent à
        réfléchir au rôle que joue l’auto-complétion, et plus
        généralement les outils de génération de code, dans la pratique
        de la programmation. Car c’est, je pense, l’un des nombreux
        domaines où les dommages causés par l’IA générative se fait
        fortement ressentir.</p>
        <p>L’auto-complétion “traditionnelle” fonctionne est simple et
        largement déterministe: étant donné les éléments présents dans
        le scope et les premières lettres entrées par l’utilisateur,
        déterminer quels sont les possibilités de complétion et (seule
        partie probabiliste) les trier selon leur pertinence. Les outils
        de génération, eux-aussi, sont (étaient) simple: étant donné un
        <em>template</em> paramétrable, et une série de paramètres,
        générer un ou des blocs de code.</p>
        <p>Simple, mais puissant. Il y a plus de dix ans maintenant, je
        l’utilisais pour générer des dizaines de classes à la volée en
        Java sur base d’un schéma de base de données. Tout le
        <em>boilerplate</em> répétitif de la création de classes
        “modèles” et “contrôleurs” était fait, on pouvait se concentrer
        sur la logique de plus haut niveau du programme.</p>
        <p>Et l’auto-complétion classique, en plus d’être un gain de
        temps, est aussi un formidable outil d’apprentissage et de
        découverte. Quelles sont les méthodes disponibles depuis cette
        classe? Tape un <code>.</code>, appuie sur <code>TAB</code> (ou
        <code>Ctrl-Espace</code> selon les IDEs, en général), et fait
        défiler tout l’univers des possibilités. Cette auto-complétion
        fonctionne au rythme du développeur et encourage la réflexion et
        la remise en question. Il m’est régulièrement arrivé de
        m’arrêter, intrigué par une proposition de méthode que je ne
        connaissais pas, de lire rapidement la documentation, et de me
        rendre compte d’une possibilité que je n’avais pas envisagée
        pour remplir les besoins de mon programme.</p>
        <p>Le code généré, quand à lui, ne nécessitait pas vraiment de
        relecture. Une fois qu’on avait compris le principe de comment
        configurer le template, on pouvait être certain que le résultat
        serait toujours exact, et on garde une compréhension aussi
        complète que possible de ce qui se trouve dans le code du
        projet.</p>
        <p>Les IAs génératives nous privent de tous ces bénéfices, et ne
        visent plus qu’à l’objectif de productivité à court terme:
        produire plus de lignes de codes, maintenant, tout de suite.</p>
        <p>L’étudiant.e écrit <code>if</code>, et l’IA propose
        immédiatement un bloc <code>if-else</code> complet qui <em>fait
        quelque chose</em>, quelque chose qui à première vue <em>semble
        toujours logique</em> par rapport à ce qui leur est demandé.
        Parfois, on peut résoudre un exercice complet à coup de
        <code>TAB</code>-<code>TAB</code>-<code>TAB</code> jusqu’à ce
        que tous les tests passent.</p>
        <p>On ne sait pas ce qu’il y a dans le code. On ne sait pas
        pourquoi ça marche. Quand finalement ça ne marche plus, on n’a
        pas la moindre idée de par où commencer.</p>
        <p>Vécu récemment: un.e étudiant.e m’appelle, me montre son
        code.</p>
        <p>– Monsieur, je ne comprends pas ce qu’il se passe.<br />
        – Qu’est-ce qui ne marche pas?<br />
        – Ca marche, mais je ne comprends pas pourquoi il faut écrire
        ça.<br />
        – Pourquoi tu l’as écrit? Qu’est-ce que tu cherchais à
        faire?<br />
        – …</p>
        <p>Je peux alors expliquer le pourquoi, mais la qualité de
        l’apprentissage n’est jamais la même. On apprend en faisant.
        Avec l’IA générative, l’étudiant.e ne fait pas, il relit ce que
        l’IA a fait et pense le comprendre.</p>
        <p>Heureusement, iels sont nombreux.ses à s’en rendre compte.
        Beaucoup m’appellent pour me demander de l’aide pour couper les
        aides IA. Ils voient bien qu’ils n’apprennent pas. Mais ces
        outils reviennent, se réactivent, se réinstallent à chaque mise
        à jour. Ce sont des mauvaises herbes dont ils peinent à se
        débarrasser. Et la tentation est toujours présente, quand la
        charge de travail devient un peu trop élevée, lorsqu’on se sent
        un peu trop largué, lorsqu’on est coincé alors que
        l’enseignant.e est occupé.e avec d’autres et qu’on a envie
        d’avancer.</p>
        <p>L’utilisation des IAs génératives brident l’apprentissage.
        C’est dramatique pour les étudiant.es, c’est problématique même
        pour les développeur.euses aguerri.es. J’ai beaucoup appris
        durant cette première année en tant qu’enseignant, à donner des
        cours dans des matières qui ne sont pas nécessairement les
        miennes. Je suis un meilleur développeur aujourd’hui que je ne
        l’étais il y a un an. Et je le dois, je crois, au fait que je me
        suis forcé à toujours <em>faire</em> les choses moi-même, à
        faire tous (ou presque) les exercices qu’on demande aux
        étudiant.es de faire, à me donner des petits projets pour aller
        plus loin. Et toujours j’ai cherché à avancer à petits pas, en
        comprenant à chaque étape ce que je faisais et pourquoi je le
        faisais.</p>
        <p>Serais-je allé plus vite avec Claude à mes côtés? Peut-être.
        Mais j’en serais sorti diminué. Au fur et à mesure des années,
        je suis persuadé que celleux qui se reposent sur Claude et ses
        amis seront de moins en moins compétents, de moins en moins
        capable de réfléchir, de moins en moins capable de <em>penser
        une solution</em>. Je crois qu’aujourd’hui déjà, et certainement
        demain après l’éclatement de la bulle spéculative des IAs
        génératives, être capable de développer sans <em>agent</em> à
        mes côtés est une compétence qui n’a pas de prix.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2026.03.30.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Sun, 29 Mar 2026 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2026.02.07 java test runner</title>
    <link>https://adfoucart.be/clavier/2026.02.07.html</link>
    <description>
        <h2><span class='emoji'>🛑</span>2026.02.07<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2026.02.07.html">java
test runner</a></span></h2>
        <p>Les tests unitaires en Java semblent assez désagréable à
        mettre en place. Le framework le plus utilisé semble être <a
        href="https://junit.org/">JUnit</a>, et à première vue ça me
        semble un peu “usine à gaz”. Il y a aussi visiblement eu de gros
        changements entre les versions, et les tutoriels que j’ai trouvé
        sont pour la plupart obsolètes.</p>
        <p>Mes besoins en tests unitaires sont assez limités. Je ne
        compte pas faire des tonnes de réflexion, de mocking ou autres
        joyeusetés. Je voudrais juste pouvoir facilement créer des
        classes de test, et avoir un point d’entrée d’où je peux tous
        les lancer automatiquement. Ca ne devrait pas nécessiter de
        faire tout un setup Maven ou Gradle et d’importer des
        <code>jar</code> dans tous les sens.</p>
        <p>On va voir: faisons un petit “test runner” Java en utilisant
        uniquement la librairie standard.</p>
        <h2 id="première-étape-les-tests-à-tester">Première étape: les
        tests à tester</h2>
        <p>Dans mon dossier <code>src</code>, où j’aurais dans un projet
        les “vraies” classes développées, mettons un dossier
        <code>Test</code> et créons une classe <code>TestTest</code> qui
        servira à tester le test runner. Rien de confus jusqu’à
        présent.</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">package</span><span class="im"> Test</span><span class="op">;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestTest <span class="op">{</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">testSuccess</span><span class="op">(){</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>        <span class="cf">assert</span> <span class="kw">true</span> <span class="op">:</span> <span class="st">&quot;if this fails, we are in trouble.&quot;</span><span class="op">;</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">testFailed</span><span class="op">(){</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>        <span class="cf">assert</span> <span class="kw">false</span> <span class="op">:</span> <span class="st">&quot;test has failed successfully!&quot;</span><span class="op">;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On met deux tests dedans: le premier devrait réussir, le
        second échouer.</p>
        <h2 id="deuxième-étape-lancer-les-tests">Deuxième étape: lancer
        les tests</h2>
        <p>Ajoutons maintenant une classe <code>TestRunner</code>, avec
        une méthode <code>main</code>. C’est ici que toute la partie
        intéressante doit se passer. Commençons simplement et laissons
        de côté la partie “découverte des classes et méthodes de test”
        pour juste lancer les deux tests et vérifier que tout se passe
        comme prévu.</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">package</span><span class="im"> Test</span><span class="op">;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>        TestTest<span class="op">.</span><span class="fu">testSuccess</span><span class="op">();</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>        TestTest<span class="op">.</span><span class="fu">testFailed</span><span class="op">();</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On lance… et rien ne se passe. Petit moment de recherche: par
        défaut, les assertions en Java <a
        href="https://www.w3schools.com/java/ref_keyword_assert.asp">sont
        désactivées</a>. Pour qu’elles aient de l’effet, il faut aller
        modifier le <a
        href="https://docs.oracle.com/en/java/javase/23/docs/api/java.base/java/lang/ClassLoader.html#setDefaultAssertionStatus(boolean)"><code>DefaultAssertionStatus</code></a>
        du <code>ClassLoader</code>:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">ClassLoader</span> loader <span class="op">=</span> <span class="bu">ClassLoader</span><span class="op">.</span><span class="fu">getSystemClassLoader</span><span class="op">();</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>        loader<span class="op">.</span><span class="fu">setDefaultAssertionStatus</span><span class="op">(</span><span class="kw">true</span><span class="op">);</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>        TestTest<span class="op">.</span><span class="fu">testSuccess</span><span class="op">();</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>        TestTest<span class="op">.</span><span class="fu">testFailed</span><span class="op">();</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On relance:</p>
        <pre><code>Exception in thread &quot;main&quot; java.lang.AssertionError: test has failed successfully!</code></pre>
        <p>Parfait.</p>
        <h2 id="on-continue-trouver-les-méthodes-dans-une-classe">On
        continue: trouver les méthodes dans une classe</h2>
        <p>La classe <code>Class</code> permet de récupérer les méthodes
        qui existent dans une classe donnée. Imaginons donc qu’on a une
        liste de “classes de test”. On peut parcourir ces classes et
        récupérer leurs méthodes:</p>
        <div class="sourceCode" id="cb5"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="dt">static</span> <span class="bu">String</span><span class="op">[]</span> testClasses <span class="op">=</span> <span class="op">{</span><span class="st">&quot;TestTest&quot;</span><span class="op">};</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>        <span class="co">// ...</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>        <span class="cf">for</span> <span class="op">(</span><span class="bu">String</span> c<span class="op">:</span> testClasses<span class="op">){</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>            <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>                <span class="bu">Class</span> testClass <span class="op">=</span> <span class="bu">Class</span><span class="op">.</span><span class="fu">forName</span><span class="op">(</span>c<span class="op">);</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>                <span class="bu">Method</span><span class="op">[]</span> methods <span class="op">=</span> testClass<span class="op">.</span><span class="fu">getDeclaredMethods</span><span class="op">();</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>                <span class="cf">for</span> <span class="op">(</span><span class="bu">Method</span> m <span class="op">:</span> methods<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>                    <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span>m<span class="op">.</span><span class="fu">getName</span><span class="op">());</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>                <span class="op">}</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">ClassNotFoundException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>                <span class="bu">System</span><span class="op">.</span><span class="fu">err</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;Class not found: &quot;</span> <span class="op">+</span> c<span class="op">);</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>                <span class="cf">return</span><span class="op">;</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span></code></pre></div>
        <p>Ce qui me donne…</p>
        <pre><code>Class not found: TestTest</code></pre>
        <p>Effectivement, le “nom” de la classe doit inclure le nom du
        package:</p>
        <div class="sourceCode" id="cb7"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="dt">static</span> <span class="bu">String</span><span class="op">[]</span> testClasses <span class="op">=</span> <span class="op">{</span><span class="st">&quot;Test.TestTest&quot;</span><span class="op">};</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span></code></pre></div>
        <p>On a maintenant:</p>
        <pre><code>testSuccess
testFailed</code></pre>
        <p>Parfait.</p>
        <h2
        id="invoquer-les-méthodes-et-récupérer-les-exceptions">Invoquer
        les méthodes et récupérer les exceptions</h2>
        <p>Reste à invoquer la méthode, ce qu’on fait avec
        <code>evoke</code>:</p>
        <div class="sourceCode" id="cb9"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>        <span class="co">// ...</span></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>            <span class="cf">for</span> <span class="op">(</span><span class="bu">Method</span> m <span class="op">:</span> methods<span class="op">)</span> <span class="op">{</span></span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a>                m<span class="op">.</span><span class="fu">invoke</span><span class="op">(</span><span class="kw">null</span><span class="op">);</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a>        <span class="co">// ...</span></span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span></code></pre></div>
        <p>Mon IDE me signale que je dois traiter des possibles
        <code>IllegalAccessException</code> et
        <code>InvocationTargetException</code>. La première peut arriver
        si la méthode que je cherche à invoquer est privée. Ceci nous
        donne donc gratuitement un filtre sur notre méthode de
        découverte: si la méthode est privée, on ne va pas l’invoquer.
        Dans nos classes Test, on pourra donc utiliser ça pour écrire
        des méthodes “internes” dont on pourrait avoir besoin.</p>
        <p><code>InvocationTargetException</code> arrive si une
        exception a lieu lors de l’exécution de la méthode. Par exemple,
        une <code>AssertionError</code> si un <code>assert</code> est
        <code>false</code>. On a donc notre moyen de détecter si le test
        a échoué.</p>
        <p>Voyons où on en est:</p>
        <div class="sourceCode" id="cb10"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="dt">static</span> <span class="bu">String</span><span class="op">[]</span> testClasses <span class="op">=</span> <span class="op">{</span><span class="st">&quot;Test.TestTest&quot;</span><span class="op">};</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a>        <span class="bu">ClassLoader</span> loader <span class="op">=</span> <span class="bu">ClassLoader</span><span class="op">.</span><span class="fu">getSystemClassLoader</span><span class="op">();</span></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a>        loader<span class="op">.</span><span class="fu">setDefaultAssertionStatus</span><span class="op">(</span><span class="kw">true</span><span class="op">);</span></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>        <span class="cf">for</span> <span class="op">(</span><span class="bu">String</span> c<span class="op">:</span> testClasses<span class="op">){</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a>            <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a>                <span class="bu">Class</span> testClass <span class="op">=</span> <span class="bu">Class</span><span class="op">.</span><span class="fu">forName</span><span class="op">(</span>c<span class="op">);</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a>                <span class="bu">Method</span><span class="op">[]</span> methods <span class="op">=</span> testClass<span class="op">.</span><span class="fu">getDeclaredMethods</span><span class="op">();</span></span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>                <span class="cf">for</span> <span class="op">(</span><span class="bu">Method</span> m <span class="op">:</span> methods<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a>                    <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a>                        m<span class="op">.</span><span class="fu">invoke</span><span class="op">(</span><span class="kw">null</span><span class="op">);</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a>                    <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">IllegalAccessException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a>                        <span class="cf">continue</span><span class="op">;</span></span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a>                    <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">InvocationTargetException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a>                        <span class="bu">System</span><span class="op">.</span><span class="fu">err</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;❌ Test &quot;</span> <span class="op">+</span> m<span class="op">.</span><span class="fu">getName</span><span class="op">()</span> <span class="op">+</span> <span class="st">&quot; failed (&quot;</span> <span class="op">+</span> e<span class="op">.</span><span class="fu">getMessage</span><span class="op">()</span> <span class="op">+</span> <span class="st">&quot;)&quot;</span><span class="op">);</span></span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a>                        <span class="cf">continue</span><span class="op">;</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a>                    <span class="op">}</span></span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a>                    <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;✅ Test &quot;</span> <span class="op">+</span> m<span class="op">.</span><span class="fu">getName</span><span class="op">()</span> <span class="op">+</span> <span class="st">&quot; passed&quot;</span><span class="op">);</span></span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a>                <span class="op">}</span></span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">ClassNotFoundException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a>                <span class="bu">System</span><span class="op">.</span><span class="fu">err</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;Class not found: &quot;</span> <span class="op">+</span> c<span class="op">);</span></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a>                <span class="cf">return</span><span class="op">;</span></span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Testons:</p>
        <pre><code>✅ Test testSuccess passed
❌ Test testFailed failed (null)</code></pre>
        <p>Presque bon: le message “test has failed successfully!”
        n’apparaît pas. Pourquoi? <code>InvocationTargetException</code>
        ne contient pas ce message, c’est — me dit la documentation — un
        “wrapper” autour de l’exception dans la méthode invoquée. Je
        dois donc récupérer d’abord la-dite exception, qui sera
        normalement le <code>AssertionError</code>.</p>
        <div class="sourceCode" id="cb12"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="bu">System</span><span class="op">.</span><span class="fu">err</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;❌ Test &quot;</span> <span class="op">+</span> m<span class="op">.</span><span class="fu">getName</span><span class="op">()</span> <span class="op">+</span> <span class="st">&quot; failed (&quot;</span> <span class="op">+</span> e<span class="op">.</span><span class="fu">getCause</span><span class="op">().</span><span class="fu">getMessage</span><span class="op">()</span> <span class="op">+</span> <span class="st">&quot;)&quot;</span><span class="op">);</span></span></code></pre></div>
        <pre><code>✅ Test testSuccess passed
❌ Test testFailed failed (test has failed successfully!)</code></pre>
        <p>Cette fois, c’est bon!</p>
        <h2 id="découverte-automatique-des-classes">Découverte
        automatique des classes</h2>
        <p>Java ne fournit pas de moyen simple de récupérer toutes les
        classes présentes dans un package, mais on peut s’en sortir
        grâce à notre ami le <code>ClassLoader</code>, celui-là même qui
        nous permet de voir les <code>assert</code>.</p>
        <p>Un certain Victor Tatai <a
        href="https://dzone.com/articles/get-all-classes-within-package">a
        partagé en 2007</a> un bout de code permettant de faire tout ça.
        Créons une sous-classe privée <code>Discovery</code> pour
        encapsuler toute cette logique.</p>
        <div class="sourceCode" id="cb14"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="kw">class</span> Discovery <span class="op">{</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a>        <span class="kw">private</span> <span class="dt">static</span> <span class="bu">Class</span><span class="op">[]</span> <span class="fu">getClasses</span><span class="op">(</span><span class="bu">ClassLoader</span> classLoader<span class="op">,</span> <span class="bu">String</span> packageName<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a>            <span class="co">// ...</span></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span></code></pre></div>
        <p>On pourra ainsi appeler
        <code>Discovery.getClasses(loader, "Test")</code> pour récupérer
        la liste des classes de test.</p>
        <p>La première étape est de récupérer toutes les “ressources” du
        package:</p>
        <div class="sourceCode" id="cb15"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="co">// dans getClasses(...)</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> classLoader <span class="op">!=</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a><span class="bu">List</span><span class="op">&lt;</span><span class="bu">Class</span><span class="op">&gt;</span> classes <span class="op">=</span> <span class="kw">new</span> <span class="bu">ArrayList</span><span class="op">&lt;&gt;();</span></span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a><span class="cf">try</span> <span class="op">{</span></span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a>    resources <span class="op">=</span> classLoader<span class="op">.</span><span class="fu">getResources</span><span class="op">(</span>packageName<span class="op">);</span></span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">IOException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> classes<span class="op">.</span><span class="fu">toArray</span><span class="op">(</span><span class="kw">new</span> <span class="bu">Class</span><span class="op">[</span><span class="dv">0</span><span class="op">]);</span></span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On va recevoir une liste d’URLs qui aura un seul élément dans
        notre cas: le chemin vers le dossier du package. Si on n’arrive
        pas à charger les ressources, on renvoie une liste vide. On peut
        parcourir les URLs et récupérer le dossier correspondant dans un
        objet de type <code>File</code>.</p>
        <div class="sourceCode" id="cb16"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="co">// ...</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="cf">while</span> <span class="op">(</span>resources<span class="op">.</span><span class="fu">hasMoreElements</span><span class="op">())</span> <span class="op">{</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a>    <span class="bu">URL</span> resource <span class="op">=</span> resources<span class="op">.</span><span class="fu">nextElement</span><span class="op">();</span></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a>    <span class="bu">File</span> directory <span class="op">=</span> <span class="kw">new</span> <span class="bu">File</span><span class="op">(</span>resource<span class="op">.</span><span class="fu">getPath</span><span class="op">());</span></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Ensuite: récupérer la liste des fichiers dans le
        répertoire.</p>
        <div class="sourceCode" id="cb17"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="co">// ...</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="bu">File</span><span class="op">[]</span> files <span class="op">=</span> directory<span class="op">.</span><span class="fu">listFiles</span><span class="op">();</span></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="cf">assert</span> files <span class="op">!=</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> <span class="op">(</span><span class="bu">File</span> file <span class="op">:</span> files<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>Les fichiers qu’on cherche seront les fichiers
        <code>.class</code>, qui contiennent le code Java compilé. Pour
        ceux-là, on doit récupérer leur nom (sans le
        <code>.class</code>), et on peut ensuite charger les classes
        correspondantes avec <code>Class.forName(...)</code>, en
        n’oubliant pas de rajouter le nom du package devant:</p>
        <div class="sourceCode" id="cb18"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> <span class="op">(</span><span class="bu">File</span> file <span class="op">:</span> files<span class="op">)</span> <span class="op">{</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">endsWith</span><span class="op">(</span><span class="st">&quot;.class&quot;</span><span class="op">))</span> <span class="op">{</span></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">String</span> className <span class="op">=</span> packageName <span class="op">+</span> <span class="st">&quot;.&quot;</span> <span class="op">+</span> file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">substring</span><span class="op">(</span><span class="dv">0</span><span class="op">,</span> file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">length</span><span class="op">()-</span><span class="dv">6</span><span class="op">);</span></span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a>        classes<span class="op">.</span><span class="fu">add</span><span class="op">(</span><span class="bu">Class</span><span class="op">.</span><span class="fu">forName</span><span class="op">(</span>className<span class="op">));</span></span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb18-6"><a href="#cb18-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>C’est à peu près bon, mais il faut maintenant traiter le cas
        où on a une arborescence un peu plus compliquée avec des
        sous-dossiers dans notre répertoire de test. Dans ce cas-là,
        <code>file</code> peut être aussi un dossier, et il faut alors
        chercher les fichiers dans ce sous-dossier. Pour pouvoir
        réaliser ça, on va devoir faire un peu de récursion et extraire
        une méthode <code>findClassesInDirectory</code> de notre méthode
        <code>getClasses</code>. On arrive au final à tout ceci pour la
        classe <code>Discovery</code>:</p>
        <div class="sourceCode" id="cb19"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">private</span> <span class="kw">class</span> Discovery <span class="op">{</span></span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a>        <span class="kw">private</span> <span class="dt">static</span> <span class="bu">Class</span><span class="op">[]</span> <span class="fu">getClasses</span><span class="op">(</span><span class="bu">ClassLoader</span> classLoader<span class="op">,</span> <span class="bu">String</span> packageName<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a>            <span class="cf">assert</span> classLoader <span class="op">!=</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a>            <span class="bu">List</span><span class="op">&lt;</span><span class="bu">Class</span><span class="op">&gt;</span> classes <span class="op">=</span> <span class="kw">new</span> <span class="bu">ArrayList</span><span class="op">&lt;&gt;();</span></span>
<span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a>            <span class="bu">Enumeration</span><span class="op">&lt;</span><span class="bu">URL</span><span class="op">&gt;</span> resources<span class="op">;</span></span>
<span id="cb19-8"><a href="#cb19-8" aria-hidden="true" tabindex="-1"></a>            <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb19-9"><a href="#cb19-9" aria-hidden="true" tabindex="-1"></a>                resources <span class="op">=</span> classLoader<span class="op">.</span><span class="fu">getResources</span><span class="op">(</span>packageName<span class="op">);</span></span>
<span id="cb19-10"><a href="#cb19-10" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">IOException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-11"><a href="#cb19-11" aria-hidden="true" tabindex="-1"></a>                <span class="cf">return</span> classes<span class="op">.</span><span class="fu">toArray</span><span class="op">(</span><span class="kw">new</span> <span class="bu">Class</span><span class="op">[</span><span class="dv">0</span><span class="op">]);</span></span>
<span id="cb19-12"><a href="#cb19-12" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb19-13"><a href="#cb19-13" aria-hidden="true" tabindex="-1"></a>            <span class="cf">while</span> <span class="op">(</span>resources<span class="op">.</span><span class="fu">hasMoreElements</span><span class="op">())</span> <span class="op">{</span></span>
<span id="cb19-14"><a href="#cb19-14" aria-hidden="true" tabindex="-1"></a>                <span class="bu">URL</span> resource <span class="op">=</span> resources<span class="op">.</span><span class="fu">nextElement</span><span class="op">();</span></span>
<span id="cb19-15"><a href="#cb19-15" aria-hidden="true" tabindex="-1"></a>                <span class="bu">File</span> directory <span class="op">=</span> <span class="kw">new</span> <span class="bu">File</span><span class="op">(</span>resource<span class="op">.</span><span class="fu">getPath</span><span class="op">());</span></span>
<span id="cb19-16"><a href="#cb19-16" aria-hidden="true" tabindex="-1"></a>                classes<span class="op">.</span><span class="fu">addAll</span><span class="op">(</span><span class="fu">findClassesInDirectory</span><span class="op">(</span>directory<span class="op">,</span> packageName<span class="op">));</span></span>
<span id="cb19-17"><a href="#cb19-17" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb19-18"><a href="#cb19-18" aria-hidden="true" tabindex="-1"></a>            <span class="cf">return</span> classes<span class="op">.</span><span class="fu">toArray</span><span class="op">(</span><span class="kw">new</span> <span class="bu">Class</span><span class="op">[</span><span class="dv">0</span><span class="op">]);</span></span>
<span id="cb19-19"><a href="#cb19-19" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb19-20"><a href="#cb19-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb19-21"><a href="#cb19-21" aria-hidden="true" tabindex="-1"></a>        <span class="kw">private</span> <span class="dt">static</span> <span class="bu">List</span><span class="op">&lt;</span><span class="bu">Class</span><span class="op">&gt;</span> <span class="fu">findClassesInDirectory</span><span class="op">(</span><span class="bu">File</span> directory<span class="op">,</span> <span class="bu">String</span> packageName<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-22"><a href="#cb19-22" aria-hidden="true" tabindex="-1"></a>            <span class="bu">List</span><span class="op">&lt;</span><span class="bu">Class</span><span class="op">&gt;</span> classes <span class="op">=</span> <span class="kw">new</span> <span class="bu">ArrayList</span><span class="op">&lt;</span><span class="bu">Class</span><span class="op">&gt;();</span></span>
<span id="cb19-23"><a href="#cb19-23" aria-hidden="true" tabindex="-1"></a>            <span class="cf">if</span> <span class="op">(!</span>directory<span class="op">.</span><span class="fu">exists</span><span class="op">())</span> <span class="op">{</span></span>
<span id="cb19-24"><a href="#cb19-24" aria-hidden="true" tabindex="-1"></a>                <span class="cf">return</span> classes<span class="op">;</span></span>
<span id="cb19-25"><a href="#cb19-25" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb19-26"><a href="#cb19-26" aria-hidden="true" tabindex="-1"></a>            <span class="bu">File</span><span class="op">[]</span> files <span class="op">=</span> directory<span class="op">.</span><span class="fu">listFiles</span><span class="op">();</span></span>
<span id="cb19-27"><a href="#cb19-27" aria-hidden="true" tabindex="-1"></a>            <span class="cf">assert</span> files <span class="op">!=</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb19-28"><a href="#cb19-28" aria-hidden="true" tabindex="-1"></a>            <span class="cf">for</span> <span class="op">(</span><span class="bu">File</span> file <span class="op">:</span> files<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-29"><a href="#cb19-29" aria-hidden="true" tabindex="-1"></a>                <span class="cf">if</span> <span class="op">(</span>file<span class="op">.</span><span class="fu">isDirectory</span><span class="op">()</span> <span class="op">&amp;&amp;</span> <span class="op">!</span>file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">contains</span><span class="op">(</span><span class="st">&quot;.&quot;</span><span class="op">))</span> <span class="op">{</span></span>
<span id="cb19-30"><a href="#cb19-30" aria-hidden="true" tabindex="-1"></a>                    classes<span class="op">.</span><span class="fu">addAll</span><span class="op">(</span><span class="fu">findClassesInDirectory</span><span class="op">(</span>file<span class="op">,</span> packageName <span class="op">+</span> <span class="st">&quot;.&quot;</span> <span class="op">+</span> file<span class="op">.</span><span class="fu">getName</span><span class="op">()));</span></span>
<span id="cb19-31"><a href="#cb19-31" aria-hidden="true" tabindex="-1"></a>                <span class="op">}</span> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">endsWith</span><span class="op">(</span><span class="st">&quot;.class&quot;</span><span class="op">))</span> <span class="op">{</span></span>
<span id="cb19-32"><a href="#cb19-32" aria-hidden="true" tabindex="-1"></a>                    <span class="bu">String</span> className <span class="op">=</span> packageName <span class="op">+</span> <span class="st">&quot;.&quot;</span> <span class="op">+</span> file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">substring</span><span class="op">(</span><span class="dv">0</span><span class="op">,</span> file<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">length</span><span class="op">()-</span><span class="dv">6</span><span class="op">);</span></span>
<span id="cb19-33"><a href="#cb19-33" aria-hidden="true" tabindex="-1"></a>                    <span class="cf">try</span> <span class="op">{</span></span>
<span id="cb19-34"><a href="#cb19-34" aria-hidden="true" tabindex="-1"></a>                        classes<span class="op">.</span><span class="fu">add</span><span class="op">(</span><span class="bu">Class</span><span class="op">.</span><span class="fu">forName</span><span class="op">(</span>className<span class="op">));</span></span>
<span id="cb19-35"><a href="#cb19-35" aria-hidden="true" tabindex="-1"></a>                    <span class="op">}</span> <span class="cf">catch</span> <span class="op">(</span><span class="bu">ClassNotFoundException</span> e<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-36"><a href="#cb19-36" aria-hidden="true" tabindex="-1"></a>                        <span class="cf">continue</span><span class="op">;</span></span>
<span id="cb19-37"><a href="#cb19-37" aria-hidden="true" tabindex="-1"></a>                    <span class="op">}</span></span>
<span id="cb19-38"><a href="#cb19-38" aria-hidden="true" tabindex="-1"></a>                <span class="op">}</span></span>
<span id="cb19-39"><a href="#cb19-39" aria-hidden="true" tabindex="-1"></a>            <span class="op">}</span></span>
<span id="cb19-40"><a href="#cb19-40" aria-hidden="true" tabindex="-1"></a>            <span class="cf">return</span> classes<span class="op">;</span></span>
<span id="cb19-41"><a href="#cb19-41" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb19-42"><a href="#cb19-42" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb19-43"><a href="#cb19-43" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <h2 id="utilisation-de-la-découverte-automatique">Utilisation de
        la découverte automatique</h2>
        <p>On peut maintenant retirer notre attribut
        <code>testClasses</code> et utiliser la découverte automatique
        pour récupérer toutes les classes de test. Voyons ce que ça
        donne:</p>
        <div class="sourceCode" id="cb20"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">ClassLoader</span> loader <span class="op">=</span> <span class="bu">ClassLoader</span><span class="op">.</span><span class="fu">getSystemClassLoader</span><span class="op">();</span></span>
<span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a>        loader<span class="op">.</span><span class="fu">setDefaultAssertionStatus</span><span class="op">(</span><span class="kw">true</span><span class="op">);</span></span>
<span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a>        <span class="bu">Class</span><span class="op">[]</span> classes <span class="op">=</span> Discovery<span class="op">.</span><span class="fu">getClasses</span><span class="op">(</span>loader<span class="op">,</span> <span class="st">&quot;Test&quot;</span><span class="op">);</span></span>
<span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a>        <span class="cf">for</span> <span class="op">(</span><span class="bu">Class</span> c <span class="op">:</span> classes<span class="op">)</span> <span class="op">{</span></span>
<span id="cb20-8"><a href="#cb20-8" aria-hidden="true" tabindex="-1"></a>            <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span>c<span class="op">.</span><span class="fu">getName</span><span class="op">());</span></span>
<span id="cb20-9"><a href="#cb20-9" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb20-10"><a href="#cb20-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb20-11"><a href="#cb20-11" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb20-12"><a href="#cb20-12" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On reçoit:</p>
        <pre><code>Test.TestRunner$Discovery
Test.TestRunner
Test.TestTest</code></pre>
        <p>Évidemment, le <code>TestRunner</code> et la sous-classe
        <code>Discovery</code> sont là aussi. Il va falloir les filtrer,
        puis on peut continuer comme avant.</p>
        <div class="sourceCode" id="cb22"><pre
        class="sourceCode java"><code class="sourceCode java"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> TestRunner <span class="op">{</span></span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span><span class="op">(</span><span class="bu">String</span><span class="op">[]</span> args<span class="op">){</span></span>
<span id="cb22-3"><a href="#cb22-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">ClassLoader</span> loader <span class="op">=</span> <span class="bu">ClassLoader</span><span class="op">.</span><span class="fu">getSystemClassLoader</span><span class="op">();</span></span>
<span id="cb22-4"><a href="#cb22-4" aria-hidden="true" tabindex="-1"></a>        loader<span class="op">.</span><span class="fu">setDefaultAssertionStatus</span><span class="op">(</span><span class="kw">true</span><span class="op">);</span></span>
<span id="cb22-5"><a href="#cb22-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb22-6"><a href="#cb22-6" aria-hidden="true" tabindex="-1"></a>        <span class="bu">Class</span><span class="op">[]</span> classes <span class="op">=</span> Discovery<span class="op">.</span><span class="fu">getClasses</span><span class="op">(</span>loader<span class="op">,</span> <span class="st">&quot;Test&quot;</span><span class="op">);</span></span>
<span id="cb22-7"><a href="#cb22-7" aria-hidden="true" tabindex="-1"></a>        <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;Found &quot;</span> <span class="op">+</span> <span class="op">(</span>classes<span class="op">.</span><span class="fu">length</span><span class="op">-</span><span class="dv">2</span><span class="op">)</span> <span class="op">+</span> <span class="st">&quot; test classes.&quot;</span><span class="op">);</span></span>
<span id="cb22-8"><a href="#cb22-8" aria-hidden="true" tabindex="-1"></a>        <span class="cf">for</span> <span class="op">(</span><span class="bu">Class</span> c <span class="op">:</span> classes<span class="op">)</span> <span class="op">{</span></span>
<span id="cb22-9"><a href="#cb22-9" aria-hidden="true" tabindex="-1"></a>            <span class="cf">if</span> <span class="op">(</span>c<span class="op">.</span><span class="fu">getName</span><span class="op">().</span><span class="fu">startsWith</span><span class="op">(</span>TestRunner<span class="op">.</span><span class="fu">class</span><span class="op">.</span><span class="fu">getName</span><span class="op">()))</span></span>
<span id="cb22-10"><a href="#cb22-10" aria-hidden="true" tabindex="-1"></a>                <span class="cf">continue</span><span class="op">;</span></span>
<span id="cb22-11"><a href="#cb22-11" aria-hidden="true" tabindex="-1"></a>            <span class="bu">System</span><span class="op">.</span><span class="fu">out</span><span class="op">.</span><span class="fu">println</span><span class="op">(</span><span class="st">&quot;Searching for tests in &quot;</span> <span class="op">+</span> c<span class="op">.</span><span class="fu">getName</span><span class="op">());</span></span>
<span id="cb22-12"><a href="#cb22-12" aria-hidden="true" tabindex="-1"></a>            <span class="bu">Method</span><span class="op">[]</span> methods <span class="op">=</span> c<span class="op">.</span><span class="fu">getDeclaredMethods</span><span class="op">();</span></span>
<span id="cb22-13"><a href="#cb22-13" aria-hidden="true" tabindex="-1"></a>            <span class="co">// ...</span></span>
<span id="cb22-14"><a href="#cb22-14" aria-hidden="true" tabindex="-1"></a>        <span class="op">}</span></span>
<span id="cb22-15"><a href="#cb22-15" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb22-16"><a href="#cb22-16" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ...</span></span>
<span id="cb22-17"><a href="#cb22-17" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
        <p>On relance:</p>
        <pre><code>Found 1 test classes.
Searching for tests in Test.TestTest
✅ Test testSuccess passed
❌ Test testFailed failed (test has failed successfully!)</code></pre>
        <p>Et c’est bon! J’ai maintenant un dossier Test que je peux
        “dropper” dans d’autres projet pour déployer des tests unitaires
        simples sans me casser la tête.</p>
        <p>Code complet sur Codeberg: <a
        href="https://codeberg.org/adfoucart/JavaTestRunner/">https://codeberg.org/adfoucart/JavaTestRunner/</a></p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2026.02.07.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Fri, 06 Feb 2026 23:00:00 -0000</pubDate>
</item>
<item>
    <title>2026.01.26 correction assistée par ordinateur</title>
    <link>https://adfoucart.be/clavier/2026.01.26.html</link>
    <description>
        <h2><span class='emoji'>🖋</span>2026.01.26<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2026.01.26.html">correction
assistée par ordinateur</a></span></h2>
        <p>Pourquoi passer dix heures à faire des corrections lorsqu’on
        peut passer cinq heures à faire un système qui automatise
        partiellement les corrections, <em>et puis</em> passer quand
        même dix heures à faire des corrections?</p>
        <p>Bon, il y a effectivement quelques révisions à prévoir pour
        réutiliser “mon” système, mais même si le résultat n’a pas été
        tout à fait à la hauteur de mes attentes cette session, je pense
        que les concepts de base sont raisonnablement sains. Mais
        prenons les choses étape par étape.</p>
        <h2 id="le-besoin">1. Le besoin</h2>
        <p>Une partie de mes corrections était sur papier. Pas
        d’automatisation par là, juste une solide grille de correction
        permettant de ne pas trop agoniser sur chaque demi-point à
        donner.</p>
        <p>Mais deux parties d’examen étaient susceptibles de recevoir
        un peu d’aide technologique: une question de maths en Excel, et
        un examen de machine learning avec des fonctions Python à
        compléter.</p>
        <p>Ce que je voulais éviter, c’est de devoir:</p>
        <ol type="1">
        <li>Ouvrir (et refermer) 300 fichiers Excel pour aller regarder
        les formules écrites par les étudiant.es une à une.<br />
        </li>
        <li>Ouvrir, exécuter et refermer 200 fichiers Python, et
        possiblement les modifier pour tester des fonctions
        individuelles.</li>
        </ol>
        <p>Il faut sans doute le préciser au vu de la situation
        actuelle, mais l’objectif n’est évidemment pas de balancer tout
        ça dans ChatGPT, Copilot ou autre et de lui “demander” de me
        coter tout ça pendant que je bois un café. Je veux me faciliter
        la vie <em>et</em> rester le plus correct possible dans mes
        cotes.</p>
        <p>Non, ce que je veux, c’est pouvoir me faciliter la vie.
        Tester automatiquement ce qui est testable automatiquement, et
        me mettre en avant les informations utiles pour corriger chaque
        étudiant.e sans devoir aller manuellement ouvrir tous les
        fichiers soumis.</p>
        <p>Idéalement, j’aimerais avoir quelque chose qui va me détecter
        facilement quand quelque chose est juste, pour que je puisse
        concentrer mon attention sur les cas où il y a des erreurs pour
        voir si ce sont des erreurs très problématiques ou non. Ou pas
        des erreurs du tout: tout système automatique aura ses faux
        positifs.</p>
        <h2 id="le-plan">2. Le plan</h2>
        <p>Ce que j’avais en tête était, en principe, raisonnablement
        simple:</p>
        <ul>
        <li>Récupérer, pour chaque étudiant.e, le(s) fichier(s)
        remplis.</li>
        <li>Pour Excel:
        <ul>
        <li>Récupérer le contenu des cellules à modifier.</li>
        <li>Comparer ce contenu avec les formules attendues.</li>
        <li>Créer un fichier “rapport” par étudiant.e avec le contenu
        des cellules et une note indiquant si c’est juste ou pas.</li>
        </ul></li>
        <li>Pour Python:
        <ul>
        <li>Copier les fichiers dans un environnement d’évaluation avec
        des fichiers <code>pytest</code> permettant de tester les
        différentes fonctions implémentées.</li>
        <li>Lancer <code>pytest</code> et récupérer le résultat.</li>
        <li>Créer un fichier “rapport” par étudiant avec le résultat des
        tests et une copie du code.</li>
        </ul></li>
        </ul>
        <p>Ensuite, dans les deux cas, créer aussi un fichier “index”
        avec la liste des étudiant.es et un lien vers leur rapport. Tout
        ça idéalement en HTML pour pouvoir facilement naviguer d’un
        rapport à l’autre.</p>
        <p>Python reste mon langage de prédilection, c’est donc de que
        j’utiliserai pour essayer de faire tout ça.</p>
        <h2 id="premier-écueil-la-récupération-des-fichiers">3. Premier
        écueil: la récupération des fichiers</h2>
        <p>Il y a plusieurs configurations possibles pour faire passer
        des examens sur machine à la Haute-École. Dans celle utilisée
        pour le cours de mathématiques, les étudiant.es reçoivent des
        identifiants temporaire pour le temps de l’examen, et doivent
        mettre leurs fichiers à la fin sur un disque réseau. À la fin de
        l’examen, on peut récupérer le contenu de ces disques,
        identifiés par le login temporaire. Les feuilles de login sont
        distribuées avec les énoncés lors de l’examen, et il n’y a donc
        pas de correspondance connue <em>a priori</em> entre les comptes
        et les étudiant.es.</p>
        <p>Par conséquent, les consignes indiquaient clairement que les
        étudiant.es devaient renommer le fichier Excel fourni
        (<code>Probabilites.xlsx</code>) avec le format
        <code>NOM_PRENOM.xlsx</code>. Ils devaient aussi dans le même
        examen faire un projet Java qu’ils devaient également appeler
        <code>NOM_Prenom</code> et mettre à la racine du disque réseau.
        Et on leur demandait également de mettre leur nom et prénom sur
        la feuille de login qu’on récupérait à la fin de l’examen.</p>
        <p>On aurait pu simplement décider que le non respect des
        consignes amènerait un 0, et ne pas corriger si le fichier Excel
        n’était pas correctement renommer, mais on est gentils. On va
        donc essayer de faire un minimum d’effort pour trouver toutes
        les correspondances entre les étudiant.es et leur compte.</p>
        <p>Pour commencer, je parcours tous les dossiers et, dans
        chaque, dossier, j’utilise la méthode <code>glob</code> de <a
        href="https://docs.python.org/3/library/pathlib.html"><code>pathlib</code></a>
        pour rechercher tous les fichiers <code>.xlsx</code> qui se
        trouvent dans le dossier ou ses sous-dossiers:
        <code>xlsx_files = list(f.glob("**/*.xlsx"))</code>. S’il y en a
        un qui ne s’appelle pas <code>Probabilites.xlsx</code>, je
        récupère le nom du fichier et considère que c’est le nom de mon
        étudiant.e. C’est le cas de base, pour celleux qui ont respecté
        les consignes.</p>
        <p>Sinon, je cherche s’il y a un dossier à la racine qui
        correspondrait au projet Java, et je note l’association. Et
        sinon, je note simplement le numéro de session, et espère qu’on
        retrouve une feuille de login avec un nom dessus. Sur un peu
        moins de 300 étudiant.es qui ont passé l’examen, je n’en ai
        qu’un seul au final qui a rempli quelque chose <em>et</em> qui
        n’a laissé aucun moyen de l’identifier.</p>
        <p>La seconde configuration, utilisée pour l’examen en Python,
        rend les choses un peu plus faciles: les étudiant.es uploadent
        leur projet sur une plateforme <a
        href="https://moodle.org/">Moodle</a>, où iels sont connecté.es
        avec leur propre compte. J’ai donc alors un fichier ZIP par
        étudiant.e avec son nom dessus.</p>
        <h2 id="tester-excel-cest-galère">4. Tester Excel, c’est
        galère</h2>
        <p>Il y a plusieurs librairies qui permettent de s’attaquer à un
        fichier Excel en Python. Une des façons les plus simples est
        d’utiliser <a
        href="https://pandas.pydata.org/"><code>pandas</code></a> et sa
        fonction <a
        href="https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html"><code>read_excel</code></a>.
        On peut y faire:</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> pandas <span class="im">as</span> pd</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>file_content <span class="op">=</span> pd.read_excel(<span class="st">&quot;/path/to/excel/file.xlsx&quot;</span>, header<span class="op">=</span><span class="va">None</span>)</span></code></pre></div>
        <p>Pour récupérer le contenu du fichier dans une DataFrame.
        Problème: on récupère uniquement les <em>valeurs</em> des
        cellules. Mais je ne veux pas juste les valeurs: je veux
        récupérer les formules. En effet, dans l’exercice proposé, une
        bonne partie des valeurs à trouver leur sont déjà données dans
        le fichier, et iels devaient justement trouver la formule
        permettant de les retrouver!</p>
        <p>Pandas utilise <a
        href="https://openpyxl.readthedocs.io/en/stable/"><code>openpyxl</code></a>
        en arrière-fond. Avec <code>openpyxl</code>, on peut ouvrir un
        fichier en récupérant soit les valeurs, soit les formules, et
        ensuite adresser directement les cellules:</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="im">from</span> openpyxl <span class="im">import</span> load_workbook</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>wb <span class="op">=</span> load_workbook(<span class="st">&quot;/path/to/excel/file.xlsx&quot;</span>, data_only<span class="op">=</span><span class="va">False</span>)</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>sheet <span class="op">=</span> wb[<span class="st">&quot;Sheet1&quot;</span>]</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>g4 <span class="op">=</span> sheet[<span class="st">&quot;G4&quot;</span>].value <span class="co"># formule dans la cellule G4</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>wb_val <span class="op">=</span> load_workbook(<span class="st">&quot;/path/to/excel/file.xlsx&quot;</span>, data_only<span class="op">=</span><span class="va">True</span>)</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>sheet_val <span class="op">=</span> wb_val[<span class="st">&quot;Sheet1&quot;</span>]</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>g4_val <span class="op">=</span> sheet_val[<span class="st">&quot;G4&quot;</span>].value <span class="co"># valeur dans la cellule G4</span></span></code></pre></div>
        <p>Et j’ai — malheureusement après avoir choisi de ne pas
        utiliser cette libraire — fini par trouver un moyen d’accéder
        aussi aux graphes présents sur la feuille, et à récupérer
        quelles données ont été utilisées pour réaliser ce graphe, et de
        quel type de graphe il s’agit. Le problème, c’est que c’est
        assez mal documenté, et qu’on doit accéder à des attributs
        “privés” (au sens pythonesque de “marqué par un <code>_</code>
        comme ne faisant pas partie de l’API”) pour pouvoir jouer
        avec:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>all_charts <span class="op">=</span> sheet._charts</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> chart <span class="kw">in</span> all_charts:</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>    <span class="bu">print</span>(chart.tagname) <span class="co"># donnera le type de graphe</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> ser <span class="kw">in</span> chart.ser: <span class="co"># parcourir les ranges</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>(ser.val.numRef.f) <span class="co"># formule du range</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>([pt.v <span class="cf">for</span> pt <span class="kw">in</span> ser.val.numRef.numCache.pt]) <span class="co"># valeurs utilisées dans le graphe</span></span></code></pre></div>
        <p>C’est un peu dégueu, et probablement pas très stable, mais ça
        peut marcher. Malheureusement, je n’ai pas trouvé ça tout de
        suite, et je suis tombé sur des recommandations pour <a
        href="https://www.xlwings.org/"><code>xlwings</code></a>, que
        j’ai finalement utilisé. <code>xlwings</code> permet de
        récupérer le contenu d’un fichier Excel avec ses formules et ses
        valeurs, ainsi que d’extraire les graphes et de les sauvegarder
        en fichiers images. Par contre, s’il y a un moyen de retrouver
        les données utilisées pour créer le graphe, je ne l’ai pas
        trouvé. Et <code>xlwings</code> est leeeeeent. Je pense que la
        prochaine fois, je reviendrai sur <code>openpyxl</code>.
        <code>xlwings</code> propose une API similaire:</p>
        <div class="sourceCode" id="cb4"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> xlwings <span class="im">as</span> xw</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>wb <span class="op">=</span> xw.Book(<span class="st">&quot;/path/to/excel/file.xlsx&quot;</span>)</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>sheet <span class="op">=</span> wb.sheets[<span class="st">&#39;Sheet1&#39;</span>]</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>g4 <span class="op">=</span> sheet[<span class="st">&#39;G4&#39;</span>].formula</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>g4_val <span class="op">=</span> sheet[<span class="st">&#39;G4&#39;</span>].value</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co"># extraction des graphes</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>charts <span class="op">=</span> sheet.charts</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> chart <span class="kw">in</span> charts:</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>    chart.to_png(<span class="st">&quot;/path/to/chart.png&quot;</span>)</span></code></pre></div>
        <p>Avec <code>xlwings</code>, j’ai donc pu faire à peu près ce
        que j’avais en tête:</p>
        <ul>
        <li>Récupérer les formules des cellules qui m’intéressent.</li>
        <li>Récupérer le graphe s’il y en a un, l’extraire et le
        sauvegarder en PNG.</li>
        <li>Comparer les formules à des “formules attendues”.</li>
        <li>Générer un rapport avec tout ça.</li>
        </ul>
        <p>Le rapport obtenu ressemble à ça (anonymisé ici):</p>
        <figure>
        <img src="./img/excel_report.png" width="300" alt="agrandir" />
        <figcaption aria-hidden="true"><a
        href="./img/excel_report.png">agrandir</a></figcaption>
        </figure>
        <p>Le problème principal qui m’a tout de même fait rouvrir une
        bonne partie des fichiers Excels, c’est que le nombre de
        différentes manières équivalentes d’écrire les formules était
        juste trop grand. Entre les parenthèses, les différentes
        manières possibles d’arriver au résultat, ou le choix (tout à
        fait raisonnable!) fait par certain.es d’utiliser des cellules
        “intermédiaires” pour découper les formules en éléments plus
        simples… on ne s’en sort pas trop, et énormément d’étudiant.es
        avaient une réponse correcte à laquelle je n’avais pas
        pensé.</p>
        <h3 id="notes-pour-le-futur">Notes pour le futur</h3>
        <ul>
        <li>Utiliser <code>openpyxl</code> directement.</li>
        <li>Mettre dans le rapport les formules et les valeurs des
        cellules attendus, mais aussi de tout autre cellule non-nulle
        modifiée par l’étudiant.e.</li>
        <li>Tester d’abord sur les valeurs en <em>excluant</em>
        certaines formules “non-autorisées” pour y arriver, plutôt que
        de tester sur les formules. Par exemple ici, certain.es ont
        tenté le coup de “recopier” les valeurs qui étaient déjà données
        (avec par exemple <code>=C4</code> pour récupérer dans la
        cellule où ils devaient mettre la formule la valeur déjà fournie
        correspondante): la valeur serait correcte, mais la formule
        pas.</li>
        <li>Récupérer les ranges et valeurs du graphe et les mettre dans
        le rapport, quitte à recréer le graphe en matplotlib sur base de
        ces valeurs plutôt que d’essayer de l’extraire depuis Excel si
        je veux l’avoir quand même affiché.</li>
        </ul>
        <h2 id="tester-python-cest-plus-facile">5. Tester Python, c’est
        plus facile?</h2>
        <p>J’avais mis toutes les chances de mon côté avec l’énoncé de
        mon examen de Machine Learning en Python: les étudiant.es
        avaient des fonctions à compléter dont iels n’étaient pas
        supposé.es changer le nom ou la signature, et avec des
        spécifications très claires sur les entrées et sorties
        attendues. Le scénario idéal pour pouvoir déployer des tests
        unitaires. J’avais donc préparé une batterie de tests avec
        <code>pytest</code>. Mon plan était donc: pour chaque
        étudiant.e, récupérer les deux fichiers de code qu’ils devaient
        modifier, les mettre dans un environnement de test avec mes
        fichiers <code>pytest</code> prêts à l’emploi, lancer les tests,
        récupérer les résultats, et mettre ces résultats avec le code
        dans un fichier “rapport” par étudiant.e.</p>
        <p>La correction devait ainsi être raisonnablement rapide: si
        les tests d’une fonction sont passés, c’est juste; sinon, on
        jette un coup d’oeil au code pour voir si l’étudiant.e a quand
        même compris quelque chose.</p>
        <p>Premier écueil: les étudiant.es ont été un peu trop
        enthousiastes sur l’auto-complétion, et ont souvent inclus des
        libraires imprévues dans leurs imports. Je ne spécifiais nulle
        part dans l’énoncé qu’il fallait limiter les <code>import</code>
        aux librairies nécessaires, donc je n’ai pas voulu pénaliser ça…
        mais je n’avais pas moi-même installé ces librairies dans mon
        environnement de test, donc les tests unitaires foiraient
        complètement. Heureusement, ces imports excessifs n’étaient pas
        utilisés ensuite dans le code, et pouvaient donc être
        automatiquement enlevés avec <a
        href="https://pypi.org/project/autoflake/"><code>autoflake</code></a>.
        J’ai fait les évaluations depuis mon PC-de-la-maison qui a
        encore Windows dessus, donc j’ai fait un bon vieux script
        <code>.bat</code> pour parcourir les dossiers, lancer
        <code>autoflake</code>, lancer les tests et mettre les résultats
        dans un fichier:</p>
        <div class="sourceCode" id="cb5"><pre
        class="sourceCode bat"><code class="sourceCode dosbat"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">REM Run autoflake and pytest in all directories</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> <span class="at">/f</span> <span class="va">%%i</span> <span class="kw">in</span> <span class="kw">(</span>&#39;dir <span class="at">/b/ad</span>&#39;<span class="kw">)</span> <span class="kw">do</span> <span class="kw">(</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="bu">echo</span> Autoflake in &quot;<span class="sc">%%</span><span class="va">i</span>&quot;</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    autoflake -<span class="at">-in-place</span> -<span class="at">-remove-all-unused-imports</span> <span class="st">&quot;</span><span class="sc">%%</span><span class="va">i</span><span class="st">/src/conseiller_fraude.py&quot;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    autoflake -<span class="at">-in-place</span> -<span class="at">-remove-all-unused-imports</span> <span class="st">&quot;</span><span class="sc">%%</span><span class="va">i</span><span class="st">/src/detection_fraude.py&quot;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>    <span class="bu">echo</span> pytest in &quot;<span class="sc">%%</span><span class="va">i</span>&quot;</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>    pytest <span class="st">&quot;</span><span class="sc">%%</span><span class="va">i</span><span class="st">/tests/test_conseiller_fraude.py&quot;</span> -<span class="at">-no-header</span> <span class="at">-tb</span>=no <span class="kw">&gt;&gt;</span> <span class="st">&quot;</span><span class="sc">%%</span><span class="va">i</span><span class="st">.txt&quot;</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>    pytest <span class="st">&quot;</span><span class="sc">%%</span><span class="va">i</span><span class="st">/tests/test_detection_fraude.py&quot;</span> -<span class="at">-no-header</span> <span class="at">-tb</span>=no <span class="kw">&gt;&gt;</span> <span class="st">&quot;</span><span class="sc">%%</span><span class="va">i</span><span class="st">.txt&quot;</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="kw">)</span></span></code></pre></div>
        <p>Le <code>pytest</code> aurait pu être fait en une fois, mais
        le faire séparément sur les deux fichiers permet de tout de même
        avoir les résultats si un des deux fichiers plante complètement,
        par exemple pour cause d’import foireux.</p>
        <p>Les rapport obtenus ressemblent à ceci:</p>
        <figure>
        <img src="./img/python_report.png" width="300" alt="agrandir" />
        <figcaption aria-hidden="true"><a
        href="./img/python_report.png">agrandir</a></figcaption>
        </figure>
        <p>Le problème principal que j’ai eu avec ces tests n’était ici
        pas technique, mais conceptuel: les tests unitaires que j’avais
        prévu ne capturaient pas bien les critères sur lesquels je
        voulais les évaluer. Du coup, à part si tous les tests d’une
        méthode passaient (ce qui n’arrivait malheureusement pas très
        souvent à part pour les méthodes les plus simples), je devais
        tout de même souvent repasser sur le code ligne par ligne avec
        ma grille de critères pour voir ce qui passait ou non. C’était
        un peu bête de ma part: j’ai écrit les tests unitaires comme si
        je développais le programme, au lieu de les écrire avec la
        grille d’évaluation en tête. Probablement parce que je n’avais
        pas encore écrit la grille d’évaluation, ce qui n’aide pas.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2026.01.26.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Sun, 25 Jan 2026 23:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.11.22 idiomatique</title>
    <link>https://adfoucart.be/clavier/2025.11.22.html</link>
    <description>
        <h2><span class='emoji'>🌍</span>2025.11.22<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.11.22.html">idiomatique</a></span></h2>
        <p>Mon langage de prédilection en programmation est depuis
        longtemps maintenant le Python. Dans le cursus dans lequel
        j’enseigne, cependant, les langages principalement utilisés sont
        le Java et le Typescript. J’ai fait pas mal de Java. Le
        Typescript est une “surcouche” du Javascript, et j’ai déjà fait
        du Javascript. Donc “ça devrait aller”, en tout cas pour m’en
        sortir dans la <strong>syntaxe</strong>. Et, évidemment, les
        concepts de programmation en général sont transférables d’un
        langage à l’autre.</p>
        <p>Mais ce qui va me manquer, et sur lequel je vais devoir un
        peu travailler, c’est ce qui fait souvent dire à des gens qui ne
        maîtrisent pas un langage particulier que ce langage est nul: la
        connaissance des <strong>idiomes</strong> du langage.</p>
        <p>J’entends souvent des gens qui se plaignent du Python, et à
        chaque fois quand je vois ce qui les bloque, c’est qu’ils n’ont
        “pas compris” comment utiliser Python efficacement. Je râle de
        mon côté sur le Java ou le Javascript chaque fois que je me
        retrouve à en faire, et c’est sans doute pour la même
        raison.</p>
        <p>Les <a
        href="https://fr.wikipedia.org/wiki/Idiome_de_programmation"><strong>idiomes</strong></a>
        ne sont pas seulement liés à la syntaxe ou aux règles formelles
        du langage, mais bien au “bon usage”, les réflexes à avoir qui
        font que les choses fonctionnent sans friction.</p>
        <p>L’exemple typique, en Python, est celui des <a
        href="https://docs.python.org/fr/3/tutorial/datastructures.html#list-comprehensions">listes
        en compréhension</a>. Si on cherche à appliquer une certaine
        fonction sur tous les éléments d’une liste et à stocker les
        résultats dans une nouvelle liste, on pourrait faire:</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>nouvelle_liste <span class="op">=</span> []</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> i <span class="kw">in</span> <span class="bu">range</span>(<span class="bu">len</span>(ma_liste)):</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    element <span class="op">=</span> ma_liste[i]</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    resultat <span class="op">=</span> ma_fonction(element)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    nouvelle_liste.append(resultat)</span></code></pre></div>
        <p>Ce serait tout à fait “correct” au niveau de la syntaxe du
        langage, mais ce n’est pas <em>idiomatique</em>. La liste en
        compréhension offre, en Python, une manière plus lisible (pour
        celleux qui sont ont l’accent local!) d’exprimer la même
        opération:</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>nouvelle_liste <span class="op">=</span> [ma_fonction(element) <span class="cf">for</span> element <span class="kw">in</span> ma_liste]</span></code></pre></div>
        <p>Et comme dans les langages parlés par les humains, plusieurs
        idiomes peuvent coexister. Dans certaines régions du pays
        Python, on utiliserait plutôt:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>nouvelle_liste <span class="op">=</span> <span class="bu">list</span>(<span class="bu">map</span>(ma_fonction, ma_liste))</span></code></pre></div>
        <p>Avec toujours le même résultat.</p>
        <p>Comprendre non seulement la syntaxe et les règles, mais aussi
        les idiomes du Typescript, c’est ce que je vais chercher à faire
        dans les prochains mois. Je documenterai cette aventure en terre
        moins connue ici: <a
        href="https://notes.adfoucart.be/typescript">Typescript: une
        aventure</a>.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.11.22.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Fri, 21 Nov 2025 23:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.11.04 simulation probabilités</title>
    <link>https://adfoucart.be/clavier/2025.11.04.html</link>
    <description>
        <h2><span class='emoji'>🪙</span>2025.11.04<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.11.04.html">simulation
probabilités</a></span></h2>
        <p>[<a
        href="https://notes.adfoucart.be/vinci/saint_petersbourg.html">Lien
        vers le simulateur</a>]</p>
        <p>Dans le cours de mathématiques, on utilise le <a
        href="https://fr.wikipedia.org/wiki/Paradoxe_de_Saint-P%C3%A9tersbourg">Paradoxe
        de Saint-Peterbourg</a> comme exemple de calcul de fonction de
        masse et d’espérance d’une variable aléatoire.</p>
        <p>Le paradoxe tourne autour d’un jeu qui se déroule ainsi:</p>
        <ul>
        <li>le joueur mise une certaine somme.</li>
        <li>il fait ensuite une série de jets de pile ou face.</li>
        <li>le jeu s’arrête au premier “pile”.</li>
        <li>le joueur gagne alors <span
        class="math inline">2<sup><em>n</em></sup></span> euros, où
        <span class="math inline"><em>n</em></span> est le nombre de
        “faces” obtenues avant d’avoir un “pile”.</li>
        </ul>
        <p>Donc si, par exemple, j’ai la série: Face-Face-Pile, le jeu
        s’arrête après trois lancers et je reçois <span
        class="math inline">2<sup>2</sup> = 4</span> euros.</p>
        <p>Le <strong>paradoxe</strong> réside dans le fait que,
        théoriquement, l’<strong>espérance</strong> des gains obtenus
        est <strong>infinie</strong>. Pourtant, peu de joueurs seraient
        prêts à miser une grosse somme, vu que les chances de gagner
        plus qu’une poignée d’euros sont extrêmement faibles.</p>
        <p>Notre cours de mathématique ne s’intéresse pas trop au cas
        infini. On ajoute donc une règle supplémentaire: si au bout de
        10 lancers on n’a obtenu <strong>que des faces</strong>, le
        joueur gagne <span
        class="math inline">2<sup>10</sup> = 1024</span> euros et la
        partie s’arrête aussi. Les gains ne sont plus infinis, mais le
        “paradoxe” reste présent. L’espérance du gain dans cette
        configuration est de <span class="math inline">6</span> euros,
        mais les chances pour un joueur de gagner de l’argent en misant
        <span class="math inline">6</span> euros sont faibles (<span
        class="math inline">12.5%</span>, pour être précis).</p>
        <p>On peut calculer toutes ces valeurs, mais dès qu’on fait des
        probabilités, j’aime bien <strong>simuler</strong> les choses,
        pour obtenir une bonne intuition de ce qui se passe. En plus,
        comme on enseigne à des futurs développeurs, approcher une
        solution via une simulation est une compétence qui semble
        intéressante à mettre en avant.</p>
        <p>Le <a
        href="https://notes.adfoucart.be/vinci/saint_petersbourg.html">simulateur</a>
        permet de voir comment l’espérance change avec le nombre maximal
        de lancers autorisé, et comment la précision de notre estimation
        de l’espérance s’améliore avec le nombre de parties
        simulées.</p>
        <p>Pour que tout ça soit accessible sur une page web, on va
        faire un peu de Javascript. Commençons donc par la simulation
        d’une partie:</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">jeuSaintPetersbourg</span>(max_n) {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> n <span class="op">=</span> <span class="dv">1</span><span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> (<span class="bu">Math</span><span class="op">.</span><span class="fu">random</span>() <span class="op">&lt;</span> <span class="fl">0.5</span>) {</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>        <span class="cf">if</span> (n <span class="op">===</span> max_n) <span class="cf">return</span> <span class="dv">2</span><span class="op">**</span>n<span class="op">;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>        n<span class="op">++;</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">2</span><span class="op">**</span>(n<span class="op">-</span><span class="dv">1</span>)<span class="op">;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
        <p>On utilise pour “pile ou face” un test
        <code>Math.random() &lt; 0.5</code>, et on considère ici que
        <code>true</code> représente face, et <code>false</code>
        représente pile. La fonction renvoie le gain: si on atteint le
        maximum de lancers, on renvoie <span
        class="math inline">2<sup><em>n</em></sup></span>, sinon on
        s’arrête dès qu’on tombe sur pile et on renvoie <span
        class="math inline">2<sup><em>n</em> − 1</sup></span>.</p>
        <p>Pour estimer une statistique via une simulation, il faut
        pouvoir relancer l’expérience beaucoup de fois. On va donc faire
        ça, en collectant la distribution des résultats:</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">lancerSimulation</span>(){</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ... récupération d&#39;inputs</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> resultats <span class="op">=</span> {}<span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> (<span class="kw">let</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;=</span> maxLancers<span class="op">;</span> i<span class="op">++</span> ){</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>       resultats[<span class="dv">2</span><span class="op">**</span>i] <span class="op">=</span> <span class="dv">0</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> total <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> (<span class="kw">let</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> numJeux<span class="op">;</span> i<span class="op">++</span>) {</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>        <span class="kw">const</span> gains <span class="op">=</span> <span class="fu">jeuSaintPetersbourg</span>(maxLancers)<span class="op">;</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>        resultats[gains]<span class="op">++;</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>        total <span class="op">+=</span> gains<span class="op">;</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>    <span class="kw">const</span> moyenne <span class="op">=</span> (total <span class="op">/</span> numJeux)<span class="op">.</span><span class="fu">toFixed</span>(<span class="dv">2</span>)<span class="op">;</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ... affichage des résultats</span></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
        <p><code>maxLancers</code> et <code>numJeux</code> sont
        récupérés dans des <code>&lt;input&gt;</code>. On initialise le
        tableau des résultats avec tous les gains possibles, puis on
        lance <code>numJeux</code> parties, et on collecte les gains
        obtenus. On calcule notre estimation de l’espérance en faisant
        la moyenne des gains. L’affichage se fait via un diagramme en
        barres fait dans un <a
        href="https://www.w3schools.com/html/html5_canvas.asp">&lt;canvas&gt;</a>.</p>
        <p>Il est clair que miser beaucoup sur une partie n’est pas très
        avantageux. Mais si on peut miser un peu sur beaucoup de
        parties, par contre, la dynamique change. C’est la deuxième
        partie de la simulation: que ce passe-t-il si on peut
        rejouer?</p>
        <p>On va maintenant calculer le total des gains si on joue une
        série de parties, chacune avec une mise fixe:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">totalSerie</span>(numJeux<span class="op">,</span> prix) {</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> total <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span>(<span class="kw">let</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> numJeux<span class="op">;</span> i<span class="op">++</span>){</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>        res <span class="op">=</span> <span class="fu">jeuSaintPetersbourg</span>(<span class="dv">10</span>)<span class="op">;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>        total <span class="op">+=</span> res <span class="op">-</span> prix<span class="op">;</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> total<span class="op">;</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
        <p>Et on va cette fois-ci dans notre simulation regarder une
        variable aléatoire binaire: est-ce qu’on gagne de l’argent à la
        fin? (bon, en vrai je l’ai faite ternaire parce que j’ai séparé
        le cas où on récupérait exactement notre mise).</p>
        <div class="sourceCode" id="cb4"><pre
        class="sourceCode javascript"><code class="sourceCode javascript"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">lancerSerieJeux</span>(){</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ... récupération d&#39;inputs</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> partiesGagnees <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> partiesPerdues <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span> partiesEgales <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span>( <span class="kw">let</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> numSimulations<span class="op">;</span> i<span class="op">++</span> ){</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>        gains <span class="op">=</span> <span class="fu">totalSerie</span>(numJeux<span class="op">,</span> prix)<span class="op">;</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>        <span class="cf">if</span>(gains <span class="op">&gt;</span> <span class="dv">0</span>){</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>            partiesGagnees <span class="op">+=</span> <span class="dv">1</span><span class="op">;</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>        }</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>        <span class="cf">else</span> <span class="cf">if</span>( gains <span class="op">&lt;</span> <span class="dv">0</span>){</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>            partiesPerdues <span class="op">+=</span> <span class="dv">1</span><span class="op">;</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>        }</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>        <span class="cf">else</span>{</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>            partiesEgales <span class="op">+=</span> <span class="dv">1</span><span class="op">;</span></span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>        }</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>    <span class="co">// ... affichage des résultats</span></span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
        <p>En jouant avec le simulateur, on peut ainsi facilement voir
        que si on mise une fois 1000 euros, on va vraisemblablement les
        perdre (0.1% de victoires). Si par contre je joue 200 parties à
        5 euros, j’ai plus d’une chance sur deux de sortir vainqueur de
        l’échange. Enfin, sauf si on compte le temps perdu à jeter la
        pièce.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.11.04.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Mon, 03 Nov 2025 23:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.10.17 tests postgres</title>
    <link>https://adfoucart.be/clavier/2025.10.17.html</link>
    <description>
        <h2><span class='emoji'>🗄️</span>2025.10.17<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.10.17.html">tests
postgres</a></span></h2>
        <p>Est-ce que ma requête est correcte?</p>
        <p>Je ne suis pas un grand spécialiste des bases de données
        PostgreSQL. Des trois cours que je donne ce quadrimestre, c’est
        certainement celui dans lequel je suis le moins à l’aise. Je
        sais comprendre et expliquer tous les concepts théoriques, mais
        pouvoir rapidement vérifier si une requête fonctionnne
        <strong>exactement</strong> comme prévu, si elle répond
        <strong>exactement</strong> aux critères de l’énoncé, ce n’est
        pas toujours évident.</p>
        <p>Je me suis demandé: est-ce qu’il y a moyen de faire
        l’équivalent en SQL des “tests unitaires” qu’on peut retrouver
        dans les différents languages de programmation auxquels je suis
        plus habitués? Cela me permettrait de mettre en place des
        procédures relativement simples pour vérifier qu’une solution
        (la mienne ou celle d’un.e étudiant.e) est correcte… ou de
        pointer dans la direction de l’erreur dans le cas contraire.</p>
        <p>Je suis tombé sur un article prometteur: “<a
        href="https://vb-consulting.github.io/blog/unit-testing-postgresql/">Unit
        Testing and TDD With PostgreSQL is Easy</a>”, par un certain
        Vedran Bilopavlović. Nous n’avons pas nécessairement la même
        notion de <em>easy</em>, mais je dois reconnaître que la
        solution est, en tout cas, <em>raisonnable</em>. L’idée est
        d’utiliser une procédure SQL qui va:</p>
        <ul>
        <li>Insérer des données de test.</li>
        <li>Exécuter la requête à tester.</li>
        <li>Utiliser des <code>ASSERT</code> pour vérifier si le
        résultat est celui attendu.</li>
        </ul>
        <p>La bonne nouvelle, c’est que ça marche assez bien. La
        mauvaise, c’est que c’est clairement quand même un peu lourd à
        écrire. Mais c’est sans doute une question d’habitude plus
        qu’autre chose. En tout cas je suis prêt à tenter un peu.
        Prenons un exemple avec quelque chose qui piège pas mal
        d’étudiant.e.s débutant.e.s: la différence entre un
        <code>LEFT JOIN</code> et un <code>INNER JOIN</code>.</p>
        <p>Imaginons un schéma avec trois tables: <code>pizza</code>,
        <code>garniture</code> et <code>garniture_pizza</code>. Une
        <strong>pizza</strong> aura un identifiant, un nom et un prix.
        Une <strong>garniture</strong> aura un identifiant et un nom. Et
        on fera le lien entre les deux pour pouvoir associer des pizzas
        à leurs garniture(s):</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="cf">BEGIN</span>;</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">TABLE</span> <span class="cf">IF</span> <span class="kw">NOT</span> <span class="kw">EXISTS</span> pizza (</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    id_pizza <span class="dt">int</span>,</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    nom <span class="dt">varchar</span>(<span class="dv">50</span>) <span class="kw">UNIQUE</span>,</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>    prix <span class="dt">float</span> <span class="kw">not</span> <span class="kw">null</span>,</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>    <span class="kw">PRIMARY</span> <span class="kw">KEY</span> (id_pizza)</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>);</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">TABLE</span> <span class="cf">IF</span> <span class="kw">NOT</span> <span class="kw">EXISTS</span> garniture (</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>    id_garniture <span class="dt">int</span> <span class="kw">PRIMARY</span> <span class="kw">KEY</span>,</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>    nom <span class="dt">varchar</span>(<span class="dv">50</span>) <span class="kw">UNIQUE</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>);</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">TABLE</span> <span class="cf">IF</span> <span class="kw">NOT</span> <span class="kw">EXISTS</span> garniture_pizza(</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    id_pizza <span class="dt">int</span>,</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>    id_garniture <span class="dt">int</span>,</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>    <span class="kw">PRIMARY</span> <span class="kw">KEY</span>(id_pizza, id_garniture)</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>);</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="kw">COMMIT</span>;</span></code></pre></div>
        <p>Pour respecter les principes du “test driven development”, on
        va commencer par écrire les tests avant d’écrire la requête.
        Imaginons qu’on cherche à récupérer le nombre de pizzas dans
        laquelle chaque garniture est utilisée. Quels sont les
        différents cas à tester?</p>
        <ul>
        <li>Une garniture g est utilisée sur n&gt;=1 pizzas -&gt; on
        doit voir aparraître le tuple (g, n) dans la réponse.</li>
        <li>Une garniture g n’est utilisée sur aucune pizza -&gt; on
        doit voir apparaître le tuple (g, 0) dans la réponse.</li>
        <li>Une pizza p n’a aucun ingrédient -&gt; elle ne doit pas
        affecter le résultat de la requête.</li>
        </ul>
        <p>J’ai donc besoin d’insérer dans mes données, au minimum:</p>
        <ul>
        <li>Trois pizzas (1, 2, 3)</li>
        <li>Deux garnitures (1, 2)</li>
        <li>Liens (pizza, garniture): ((1, 1), (2, 1))</li>
        </ul>
        <p>On pourrait ne mettre que deux pizzas, mais le nombre de
        pizzas serait alors égal au nombre de garniture, ce qui peut
        rendre des tests sur le “nombre de résultats obtenus” moins
        évidents.</p>
        <p>Mettons ça dans une procédure, en commençant par supprimer
        toutes les données (pour s’assurer qu’on n’a bien uniquement nos
        données de test), et en terminant par un <code>rollback</code>
        (pour remettre la base de données dans son état initial). Il va
        de soi que je ne mettrais pas cette procédure dans une base de
        données en production, on va éviter de supprimer toutes les
        données par erreur!</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">OR</span> <span class="kw">REPLACE</span> <span class="kw">PROCEDURE</span> test_nb_pizzas_par_garniture() <span class="kw">AS</span> $$</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">BEGIN</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- supprimer toutes les données</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>        <span class="kw">TRUNCATE</span> pizza, garniture, garniture_pizza;</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- données minimales pour le test</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>        <span class="kw">INSERT</span> <span class="kw">INTO</span> pizza <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="st">&#39;pizza 1&#39;</span>, <span class="dv">0</span>),</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>                                 (<span class="dv">2</span>, <span class="st">&#39;pizza 2&#39;</span>, <span class="dv">0</span>),</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>                                 (<span class="dv">3</span>, <span class="st">&#39;pizza 3&#39;</span>, <span class="dv">0</span>);</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>        <span class="kw">INSERT</span> <span class="kw">INTO</span> garniture <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="st">&#39;garniture 1&#39;</span>),</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>                                     (<span class="dv">2</span>, <span class="st">&#39;garniture_2&#39;</span>);</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>        <span class="kw">INSERT</span> <span class="kw">INTO</span> garniture_pizza <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="dv">1</span>),</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>                                           (<span class="dv">2</span>, <span class="dv">1</span>);</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>        <span class="kw">ROLLBACK</span>;</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>    <span class="cf">END</span>;</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>$$ LANGUAGE plpgsql;</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a><span class="kw">call</span> test_nb_pizzas_par_garniture();</span></code></pre></div>
        <p>Si on exécute ceci dans une base PostgreSQL, on va: -
        supprimer les données existantes. - ajouter les données de test.
        - annuler tout et remettre la base dans son état
        pré-exécution.</p>
        <p>On doit maintenant ajouter la requête et les tests. Pour
        commencer, on imagine que notre requête se trouve dans une
        fonction <code>nb_pizzas_par_garniture</code>, qu’on définira
        plus tard. On peut alors récupérer les résultats de cette
        requête dans notre procédure de test avec:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> TEMP <span class="kw">TABLE</span> result <span class="kw">ON</span> <span class="kw">COMMIT</span> <span class="kw">DROP</span> <span class="kw">AS</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="kw">SELECT</span> <span class="op">*</span> <span class="kw">FROM</span> nb_pizzas_par_garniture();</span></code></pre></div>
        <p>On stocke ainsi le résultat dans une table temporaire, qui
        sera éliminée à la fin de la transaction. On peut maintenant
        passer aux tests. La syntaxe des <code>ASSERT</code> est:
        <code>ASSERT [expr][, msg]</code>, où si l’expression est fausse
        on affiche une erreur et, optionnellement, le message. On doit
        tester:</p>
        <ul>
        <li>Que l’on trouve 2 garnitures</li>
        <li>Que la garniture 1 apparaît sur 2 pizzas</li>
        <li>Que la garniture 2 apparaît sur 0 pizzas</li>
        </ul>
        <p>Traduisons cela en SQL:</p>
        <div class="sourceCode" id="cb4"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- 2 garnitures trouvées</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>ASSERT (<span class="kw">SELECT</span> <span class="fu">COUNT</span>(<span class="op">*</span>) <span class="kw">FROM</span> result) <span class="op">=</span> <span class="dv">2</span>,</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;2 garnitures attendues, reçu &#39;</span> <span class="op">||</span> (<span class="kw">SELECT</span> <span class="fu">COUNT</span>(<span class="op">*</span>) <span class="kw">FROM</span> result):<span class="ch">:text</span>;</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- garniture 1 -&gt; 2 pizzas</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>ASSERT (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">1</span>) <span class="op">=</span> <span class="dv">2</span>,</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;2 pizzas attendues pour la garniture 1, reçu &#39;</span> <span class="op">||</span> (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">1</span>):<span class="ch">:text</span>;</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- garniture 2 -&gt; 0 pizzas</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>ASSERT (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">2</span>) <span class="op">=</span> <span class="dv">0</span>,</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;0 pizzas attendues pour la garniture 2, reçu &#39;</span> <span class="op">||</span> (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">2</span>):<span class="ch">:text</span>;</span></code></pre></div>
        <p>La syntaxe n’est pas la plus lisible, mais ça passe.
        <code>||</code> permet de concaténer des chaînes de caractères,
        et <code>::text</code> de <em>caster</em> les entiers de
        <code>nb_pizzas</code> en chaînes de caractères. Commençons par
        écrire une requête bidon pour vérifier que notre test fonctionne
        (sans oublier de la mettre dans une fonction):</p>
        <div class="sourceCode" id="cb5"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">OR</span> <span class="kw">REPLACE</span> <span class="kw">FUNCTION</span> nb_pizzas_par_garniture()</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>RETURNS <span class="kw">TABLE</span> (</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    id_garniture <span class="dt">int</span>,</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    nb_pizzas <span class="dt">int</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>) language sql <span class="kw">as</span> $$</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- LA REQUÊTE EST ICI:</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="kw">SELECT</span> <span class="dv">0</span>, <span class="dv">0</span>;</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="co">-- FIN DE LA REQUÊTE</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>$$;</span></code></pre></div>
        <p>On peut maintenant maintenant appeler la procédure de test:
        <code>call test_nb_pizzas_par_garniture();</code>, qui va bien
        planter dès le premier test en renvoyant le message:
        <code>ERROR: 2 garnitures attendues, reçu 1</code>.</p>
        <p>Imaginons maintenant un.e étudiant.e qui utilise un
        <code>INNER JOIN</code> pour répondre à la question:</p>
        <div class="sourceCode" id="cb6"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Mauvais JOIN</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">SELECT</span> g.id_garniture, <span class="fu">COUNT</span>(gp.id_pizza)</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="kw">FROM</span> garniture g</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="kw">INNER</span> <span class="kw">JOIN</span> garniture_pizza gp <span class="kw">ON</span> gp.id_garniture <span class="op">=</span> g.id_garniture</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="kw">GROUP</span> <span class="kw">BY</span> g.id_garniture</span></code></pre></div>
        <p>Il aura bien une erreur:
        <code>ERROR: 2 garnitures attendues, reçu 1</code>, car le
        <code>INNER JOIN</code> aura éliminé la garniture 2. S’iel fait
        un <code>LEFT JOIN</code> en laissant le reste identique, tous
        le tests passeront. S’il se trompe dans le <code>COUNT</code> et
        écrit:</p>
        <div class="sourceCode" id="cb7"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Mauvais COUNT</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="kw">SELECT</span> g.id_garniture, <span class="fu">COUNT</span>(g.id_garniture)</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="kw">FROM</span> garniture g</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="kw">LEFT</span> <span class="kw">JOIN</span> garniture_pizza gp <span class="kw">ON</span> gp.id_garniture <span class="op">=</span> g.id_garniture</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="kw">GROUP</span> <span class="kw">BY</span> g.id_garniture</span></code></pre></div>
        <p>Iel aura:
        <code>ERROR: 0 pizzas attendues pour la garniture 2, reçu 1</code>.</p>
        <p>Le code complet avec la requête correcte:</p>
        <div class="sourceCode" id="cb8"><pre
        class="sourceCode sql"><code class="sourceCode sql"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">OR</span> <span class="kw">REPLACE</span> <span class="kw">FUNCTION</span> nb_pizzas_par_garniture()</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>RETURNS <span class="kw">TABLE</span> (</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>    id_garniture <span class="dt">int</span>,</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>    nb_pizzas <span class="dt">int</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>) language sql <span class="kw">as</span> $$</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- LA REQUÊTE EST ICI:</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="kw">SELECT</span> g.id_garniture, <span class="fu">COUNT</span>(gp.id_garniture)</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="kw">FROM</span> garniture g</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="kw">LEFT</span> <span class="kw">JOIN</span> garniture_pizza gp <span class="kw">ON</span> gp.id_garniture <span class="op">=</span> g.id_garniture</span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="kw">GROUP</span> <span class="kw">BY</span> g.id_garniture</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a><span class="co">-- FIN DE LA REQUÊTE</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a>$$;</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a><span class="kw">CREATE</span> <span class="kw">OR</span> <span class="kw">REPLACE</span> <span class="kw">PROCEDURE</span> test_nb_pizzas_par_garniture() <span class="kw">AS</span> $$</span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a>    <span class="cf">BEGIN</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- supprimer toutes les données</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a>        <span class="kw">TRUNCATE</span> pizza, garniture, garniture_pizza;</span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- données minimales pour le test</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a>        <span class="kw">INSERT</span> <span class="kw">INTO</span> pizza <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="st">&#39;pizza 1&#39;</span>, <span class="dv">0</span>),</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a>                                 (<span class="dv">2</span>, <span class="st">&#39;pizza 2&#39;</span>, <span class="dv">0</span>),</span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a>                                 (<span class="dv">3</span>, <span class="st">&#39;pizza 3&#39;</span>, <span class="dv">0</span>);</span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a>        <span class="kw">INSERT</span> <span class="kw">INTO</span> garniture <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="st">&#39;garniture 1&#39;</span>),</span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a>                                     (<span class="dv">2</span>, <span class="st">&#39;garniture_2&#39;</span>);</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a>        <span class="kw">INSERT</span> <span class="kw">INTO</span> garniture_pizza <span class="kw">VALUES</span> (<span class="dv">1</span>, <span class="dv">1</span>),</span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a>                                           (<span class="dv">2</span>, <span class="dv">1</span>);</span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a>        <span class="kw">CREATE</span> TEMP <span class="kw">TABLE</span> result <span class="kw">ON</span> <span class="kw">COMMIT</span> <span class="kw">DROP</span> <span class="kw">AS</span></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a>        <span class="kw">SELECT</span> <span class="op">*</span> <span class="kw">FROM</span> nb_pizzas_par_garniture();</span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- 2 garnitures trouvées</span></span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true" tabindex="-1"></a>        ASSERT (<span class="kw">SELECT</span> <span class="fu">COUNT</span>(<span class="op">*</span>) <span class="kw">FROM</span> result) <span class="op">=</span> <span class="dv">2</span>,</span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;2 garnitures attendues, reçu &#39;</span> <span class="op">||</span> (<span class="kw">SELECT</span> <span class="fu">COUNT</span>(<span class="op">*</span>) <span class="kw">FROM</span> result):<span class="ch">:text</span>;</span>
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- garniture 1 -&gt; 2 pizzas</span></span>
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true" tabindex="-1"></a>        ASSERT (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">1</span>) <span class="op">=</span> <span class="dv">2</span>,</span>
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;2 pizzas attendues pour la garniture 1, reçu &#39;</span> <span class="op">||</span> (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">1</span>):<span class="ch">:text</span>;</span>
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true" tabindex="-1"></a>        <span class="co">-- garniture 2 -&gt; 0 pizzas</span></span>
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true" tabindex="-1"></a>        ASSERT (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">2</span>) <span class="op">=</span> <span class="dv">0</span>,</span>
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;0 pizzas attendues pour la garniture 2, reçu &#39;</span> <span class="op">||</span> (<span class="kw">SELECT</span> nb_pizzas <span class="kw">FROM</span> result <span class="kw">WHERE</span> id_garniture <span class="op">=</span> <span class="dv">2</span>):<span class="ch">:text</span>;</span>
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-42"><a href="#cb8-42" aria-hidden="true" tabindex="-1"></a>        <span class="kw">ROLLBACK</span>;</span>
<span id="cb8-43"><a href="#cb8-43" aria-hidden="true" tabindex="-1"></a>    <span class="cf">END</span>;</span>
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true" tabindex="-1"></a>$$ LANGUAGE plpgsql;</span>
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true" tabindex="-1"></a><span class="kw">call</span> test_nb_pizzas_par_garniture();</span></code></pre></div>
        <p>Est-ce que c’est beaucoup de boulot juste pour tester une
        requête? Oui, mais je peux voir des cas où cela vaudrait la
        peine. Par exemple dans un examen ou une interro, pouvoir
        valider rapidement que les résultats attendus ont été obtenus
        peut éviter des erreurs de correction.</p>
        <p>Évidemment, ces tests ne détecteront pas les codes
        sous-optimaux. Par exemple, je pourrais ici ajouter une jointure
        inutile à <code>pizza</code> et compter sur
        <code>p.id_pizza</code>, et les tests passeront.</p>
        <p>Mais je trouve que l’idée reste intéressante.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.10.17.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Thu, 16 Oct 2025 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.10.07 les maths, c'est chouette?</title>
    <link>https://adfoucart.be/clavier/2025.10.07.html</link>
    <description>
        <h2><span class='emoji'>➕</span>2025.10.07<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.10.07.html">les
maths, c’est chouette?</a></span></h2>
        <p>Donner les exercices de mathématiques n’était pas vraiment la
        tâche qui m’emballait le plus dans mes assignations du
        quadrimestre. Nos étudiant.e.s n’ont pas un profil “maths
        fortes” à la base, et ne viennent certainement pas dans une
        école d’informatique dans le but de faire des maths. Tout
        l’enjeux, c’est donc de leur faire comprendre <em>pourquoi</em>,
        au juste, on fait faire des maths à des futurs
        informaticien.ne.s.</p>
        <p>Parce que les maths qu’on leur fait voir sont, heureusement,
        adaptées à leurs besoins. On ne fait pas d’analyse complexe: on
        fait de la logique, de la théorie des ensembles, des suites. Des
        concepts qui ont un lien direct et raisonnablement facile à
        tracer avec les programmes qu’ils vont devoir développer.</p>
        <p>Et cet exercice-là, trouver les liens, voir les étudiants
        passer d’un désintérêt fataliste à, si pas de l’intérêt, au
        moins une certaine prise de conscience que ce qu’on est en train
        de voir, ce n’est pas juste pour les embêter… je trouve ça
        chouette.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.10.07.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Mon, 06 Oct 2025 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.10.03 le temps file</title>
    <link>https://adfoucart.be/clavier/2025.10.03.html</link>
    <description>
        <h2><span class='emoji'>⏱️</span>2025.10.03<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.10.03.html">le
temps file</a></span></h2>
        <p>Je m’attendais à ce que le rythme soit soutenu, mais s’y
        attendre et le vivre, ce sont deux choses bien différentes.
        Quand on regarde l’horaire, on se dit “ça va”. Après tout, il y
        a “seulement” vingt heures par semaine face aux étudiant.e.s
        dans un temps plein. Certain.e.s expert.e.s semblent juger que,
        pour les enseignant.e.s du secondaire en tout cas, c’est trop
        peu [<a
        href="https://www.rtbf.be/article/reduire-le-deficit-de-la-fwb-est-une-necessite-urgente-et-absolue-appuie-un-rapport-d-experts-11606293">rtbf.be</a>].
        Je ne vois personnellement pas comment on pourrait faire plus en
        gardant un minimum de qualité.</p>
        <p>Cette semaine est passée en un clin d’oeil. Pourtant, j’ai un
        rôle facile. Je suis arrivé tard, tous les cours ou presque sont
        déjà préparés. Je n’ai qu’à être présent pour les étudiant.e.s,
        répondre à leurs questions, faire l’une ou l’autre correction au
        tableau. La matière n’est pas particulièrement compliquée. Mais
        c’est toujours la course. Donner cours, relire les notes, boire
        un café, donner cours, écrire des notes, manger, donner cours,
        rentrer à la maison.</p>
        <p>Je n’ai pas à me plaindre. Mais un prof qui ne se plaint pas
        un peu, est-ce que c’est vraiment un prof?</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.10.03.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Thu, 02 Oct 2025 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.09.28 rentrée tardive</title>
    <link>https://adfoucart.be/clavier/2025.09.28.html</link>
    <description>
        <h2><span class='emoji'>👨‍🏫</span>2025.09.28<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.09.28.html">rentrée
tardive</a></span></h2>
        <p>Il y a déjà beaucoup de dates de rentrée des classes en
        Belgique. Pour ma fille, c’était le 25 août. Pour ses ami·e·s en
        école néerlandophone, c’était le 1er septembre. Pour la plupart
        des étudiant·e·s et enseignant·e·s du supérieur, c’était le 15
        septembre. Cette année, je suis un petit peu en retard: pour
        moi, ce sera le 29 septembre. La transition avec mon <a
        href="https://research.adfoucart.be">ancien travail</a> aurait
        été un peu trop abrupte, mon engagement était un peu trop
        tardif, je ne voulais pas laisser un trop grand chantier
        derrière moi.</p>
        <p>Du coup, j’ai des choses à rattraper.</p>
        <p>Mes collègues connaissent déjà, un peu, la cohorte de jeunes
        qui s’amasse dans nos classes et nos auditoires. Ils sont déjà
        rentrés dans leur routine. Pour ce premier quadrimestre de ma
        nouvelle vie d’enseignant, je viens m’intercaler comme je peux
        dans des cours et des exercices préparés par d’autres. J’espère
        ne pas tomber comme un cheveu dans la soupe, mais plutôt comme
        du parmesan sur un plat de spaghettis: on aurait pu faire sans,
        mais ma présence est appréciée!</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.09.28.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Sat, 27 Sep 2025 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.09.24 merci Google</title>
    <link>https://adfoucart.be/clavier/2025.09.24.html</link>
    <description>
        <h2><span class='emoji'>🔎</span>2025.09.24<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.09.24.html">merci
Google</a></span></h2>
        <p>En développant riem [<a
        href="https://gitlab.ulb.be/lisa/ia/protherwal/riem-project">riem-project
        on gitlab.ulb.be</a>], le projet que je suis en train de
        terminer (enfin, clôturer plutôt) dans mes derniers jours de
        chercheur à l’ULB, j’utilise le “viewer” Napari [<a
        href="https://napari.org/">napari.org</a>] pour visualiser et
        annoter des images médicales. Napari est une excellente libraire
        Python, mais sa documentation est parfois un peu bancale. Je
        dois donc souvent faire appel à Google pour essayer de trouver
        comment certaines fonctionnalités fonctionnent.</p>
        <p>Je cherchais en particulier comment pouvoir intercepter
        l’événement déclenché lorsqu’on ajoute un point dans
        l’interface. Je vous passe les détails du pourquoi. J’ai donc
        ouvert Google, et j’ai lancé la requête
        <code>napari event when point added to layer</code>.</p>
        <p>Google ne donne plus des “résultats de recherche” comme
        première option, c’est dépassé. Au lieu de ça, j’ai évidemment
        eu droit à la fameuse “AI Overview”. En général, j’ignore l’AI
        Overview autant que possible. Mais étant un peu pressé, j’ai
        tout de même voulu tester cette fois-ci la solution qu’il me
        proposait. Le code fourni n’était pas utilisable tel quel, mais
        j’avais les lignes qui m’intéressaient:</p>
        <p>Pour pouvoir intercepter l’événement:</p>
        <div class="sourceCode" id="cb1"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>points_layer.events.data.<span class="ex">connect</span>(on_point_added)</span></code></pre></div>
        <p>Et pour le traiter:</p>
        <div class="sourceCode" id="cb2"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> on_point_added(event):</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> event.<span class="bu">type</span> <span class="op">==</span> <span class="st">&#39;add&#39;</span>:</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>(<span class="st">&quot;Point added!&quot;</span>)</span></code></pre></div>
        <p>J’ai ajouté cela à mon programme et, sans grande surprise, je
        reçus en retour une erreur: <code>type</code> n’est pas un
        attribut de <code>event</code>. Zut. Mais où Google a-t-il
        cherché ce <code>event.type</code>? Heureusement, l’AI Overview
        fourni des sources. Dans ce cas-ci, la principale était une
        discussions sur Github [<a
        href="https://github.com/napari/napari/issues/2809">issue#2809
        on github.com/napari</a>], datant de 2021, et proposant de
        modifier le comportement du <em>data event</em> de Napari pour y
        ajouter un type, qui pourrait être “set”, “move”, “add” ou
        “remove”.</p>
        <p>Le problème, évidemment, c’est qu’entre le moment où cette
        proposition a été faite et le moment où la fonctionnalité a été
        rajoutée dans Napari, deux ans se sont écoulés, et l’idée a fait
        son chemin et s’est transformée [<a
        href="https://github.com/napari/napari/pull/5967">pull#5967 on
        github.com/napari</a>]. Dans la description du “pull request”,
        on voit que finalement l’événement n’a pas de type mais est
        associé à une <code>action</code>, qui peut être
        <code>add</code>, <code>remove</code> ou <code>change</code>. Le
        bon code serait donc:</p>
        <div class="sourceCode" id="cb3"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> on_point_added(event):</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> event.action <span class="op">==</span> <span class="st">&#39;add&#39;</span>:</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>(<span class="st">&quot;Point added!&quot;</span>)</span></code></pre></div>
        <p>Sauf que ça ne marche toujours pas. Heureusement, une fois
        qu’on sait quels fichiers sont impliqués dans cette
        fonctionnalité [<a
        href="https://github.com/napari/napari/pull/5967/files">pull#5967
        files on github.com/napari</a>], on peut aller voir à quoi ils
        ressemblent aujourd’hui. Et on peut ainsi retrouver que les
        actions ont été modifiées peu de temps après leur ajout [<a
        href="https://github.com/napari/napari/commit/bc3c1e3a3dacb2b94962f90e1e8541892503538b">commit#bc3c1e3a
        on github.com/napari</a>]: dorénavant, on trouve
        <code>adding</code>, <code>removing</code>,
        <code>changing</code>, <code>added</code>, <code>removed</code>
        et <code>changed</code>.</p>
        <p>On peut donc faire:</p>
        <div class="sourceCode" id="cb4"><pre
        class="sourceCode python"><code class="sourceCode python"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> on_point_added(event):</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> event.action <span class="op">==</span> <span class="st">&#39;adding&#39;</span>:</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>(<span class="st">&quot;Appelé avant ajout du point&quot;</span>)</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>    <span class="cf">elif</span> event.action <span class="op">==</span> <span class="st">&#39;added&#39;</span>:</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>(<span class="st">&quot;Appelé après ajout du point&quot;</span>)</span></code></pre></div>
        <p>Ce qui fonctionne enfin.</p>
        <p>L’AI Overview, une nouvelle fois, n’aura servit qu’à ralentir
        le processus, et à entraver l’apprentissage: quand bien même la
        réponse fournie aurait fonctionné, le fait de lire, même en
        diagonale, les pull requests et les codes des commits est
        extrêmement informatif sur le fonctionnement de la librairie. Si
        Google pouvait faire ce qu’il faisait tellement bien avant,
        c’est-à-dire me diriger tout de suite vers les bonnes sources
        d’information, il serait tellement plus utile !</p>
        <p>Mais nous vivons à l’ère du tout-à-l’IA, et on ne peut pas se
        permettre de bêtement donner des informations correctes ou
        utiles… Par curiosité, j’ai regardé ce que Copilot et ChatGPT me
        proposaient, et aucun des deux n’avaient de bonne solution non
        plus.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.09.24.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Tue, 23 Sep 2025 22:00:00 -0000</pubDate>
</item>
<item>
    <title>2025.09.22 des blogs</title>
    <link>https://adfoucart.be/clavier/2025.09.22.html</link>
    <description>
        <h2><span class='emoji'>💬</span>2025.09.22<br /><span class='article-title'><a href="https://adfoucart.be/clavier/2025.09.22.html">des
blogs</a></span></h2>
        <p>J’aime bien faire des blogs. Sans doute un truc d’enfant des
        années Skyblog (même si je ne pense pas que j’en avais un, mais
        ma mémoire me fait peut-être défaut). C’est important pour moi
        d’écrire sur ce que je fais et sur ce que je pense. Sur mon blog
        d’opinions souvent plus politiques [<a
        href="https://blog.adfoucart.be">2xRien</a>], il m’est bien
        souvent arrivé de démarrer un article avec une opinion que je
        voulais partager, puis de me rendre compte en l’écrivant, et en
        cherchant des sources pour vérifier mes a priori, que c’était
        une opinion que je n’étais en réalité pas capable de défendre.
        Ou qu’elle était banale et sans intérêt. Même ces articles
        jamais publiés m’aident à façonner et tester mes idées.</p>
        <p>Mon blog de recherche [<a
        href="https://research.adfoucart.be">Research blog</a>] était
        une extension naturelle: je voulais dissocier mes opinions
        <em>personnelles</em> de mes opinions <em>professionnelles</em>,
        dans lesquelles j’engageais mon expertise (et ma légitimité) de
        chercheur. Et puis, quand je communiquais sur ma recherche, je
        préférais le faire en Anglais.</p>
        <p>Mais à partir du 29 septembre, je ne serai plus chercheur. Il
        va donc être temps de clôturer le blog de recherche, ou en tout
        cas de le mettre de côté (je resterai en contact avec la
        recherche, mais sur mon temps personnel). Je commence une
        nouvelle carrière d’enseignant en informatique. L’occasion de
        repartir à zéro sur un nouveau blog, que voici sous nos yeux
        ébahis. J’y parlerai d’enseignement, d’informatique, de
        technologies, d’intelligence artificielle, de programmation, de
        Python, de Java, de tout ce qui peut piquer mon intérêt et que
        j’ai envie de partager avec ma casquette “enseignant” et qui ne
        rentre pas dans la “ligne éditoriale” de “2xRien”. Le choix de
        publier dans l’un ou l’autre sera arbitraire et jamais justifié.
        On va dire que c’est du “vibe blogging”, sauf que ce sera
        vraiment moi qui écrirai.</p>
        <p>Car si je ne garantis rien sur les contenus (ni sur les
        thèmes, ni sur la qualité!), je garantis que tout ce qui est
        écris ici (ou sur tout autre de mes blogs) l’est entièrement par
        moi. Écrire m’aide à penser: si l’IA écrit pour moi, elle
        pensera aussi pour moi, et vu l’alignement politique de ceux qui
        construisent ces IAs [<a
        href="https://www.whitehouse.gov/articles/2025/09/president-trump-tech-leaders-unite-american-ai-dominance/">whitehouse.gov</a>],
        je n’ai pas trop envie qu’elles aient trop d’influence sur mes
        pensées.</p>
        <p class='permalink'>[<a href="https://adfoucart.be/clavier/2025.09.22.html">lien permanent</a>]</p>
        <div id="contact">Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur <a rel="me" href="https://social.sciences.re/@AFoucart">Mastodon</a> ou par mail (adrien@adfoucart.be)</div>
    </description>
    <author>Adrien Foucart &lt;adrien@adfoucart.be&gt;</author> 
    <pubDate>Sun, 21 Sep 2025 22:00:00 -0000</pubDate>
</item>

</channel>
</rss>