<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>débogage - Tag - Jordan Chapuy - Blog d'un développeur passionné ❤️</title><link>https://jordanchapuy.com/tags/d%C3%A9bogage/</link><description>débogage - Tag - Jordan Chapuy - Blog d'un développeur passionné ❤️</description><generator>Hugo -- gohugo.io</generator><language>fr</language><lastBuildDate>Fri, 29 Dec 2023 15:44:00 +0100</lastBuildDate><atom:link href="https://jordanchapuy.com/tags/d%C3%A9bogage/" rel="self" type="application/rss+xml"/><item><title>Mon terminal et zsh sont long à démarrer (blame nvm)</title><link>https://jordanchapuy.com/posts/2023/12/mon-terminal-et-zsh-sont-long-a-demarrer-blame-nvm/</link><pubDate>Fri, 29 Dec 2023 15:44:00 +0100</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2023/12/mon-terminal-et-zsh-sont-long-a-demarrer-blame-nvm/</guid><description><![CDATA[<p>Le terminal, c&rsquo;est une sorte de couteau suisse pour les développeurs. Il permet de faire énormément de choses - presque tout d&rsquo;ailleurs - et on s&rsquo;en sert très souvent. Alors quand le temps de lancement du terminal s&rsquo;allonge, je ne vous cache pas que c&rsquo;est très embêtant.</p>
<p>C&rsquo;était mon cas avec iTerm2 + oh-my-zsh, et on va voir comment trouver les problèmes à la source.</p>
<h1 id="identifier-les-causes">Identifier les causes</h1>
<p>Je n&rsquo;ai pas forcément fait attention au moment exact où mon terminal a commencé à être capricieux pour se lancer. J&rsquo;ai l&rsquo;impression que ça a commencé la semaine marquée par l&rsquo;arrivée d&rsquo;un nouveau super pouvoir du MDM sur nos machines pro. Il analysait toutes les commandes exécutées pour vérifier les bons privilèges à appliquer.</p>
<p>Une belle coïncidence, c&rsquo;était le suspect parfait. Je l&rsquo;ai un peu blâmé sans trop chercher plus loin. Et puis un jour, il est arrivé ce fameux imprévu qui permet au héros de se lancer dans l&rsquo;aventure.</p>
<p>J&rsquo;étais en pair avec un collègue pour l&rsquo;aider sur une erreur autour de cocoapods et iOS. Je lui fais installer un <a href="https://jordanchapuy.com/posts/2023/05/des-version-manager-pour-faire-cohabiter-plusieurs-versions-dun-langage-sdk-et-framework/" rel="">version manager</a> pour Ruby, on modifie son <code>.zshrc</code>, et on veut supprimer le <code>Podfile.lock</code> pour réinstaller proprement les packages. Mais dans la rapidité de l&rsquo;action, il tape <code>rm .zshrc</code>. Aïe. Coup dur.</p>
<p>À ce moment-là, mon collègue est un peu dégoûté - je l&rsquo;aurais été aussi. Avec <code>rm</code>, ça ne part pas à la corbeille, on ne récupère pas facilement ce que l&rsquo;on a supprimé. Ça l&rsquo;embête parce qu&rsquo;il avait effectué plusieurs configurations dedans, et qu&rsquo;il avait passé une heure avec la DSI pour optimiser le temps de chargement. Hop hop hop, attends. Quoi ?!</p>
<p>Lui aussi avait son terminal qui était long au lancement, et de mémoire, ça venait en partie de l&rsquo;initialisation de <code>nvm</code>. Me voilà avec une piste intéressante. Je l&rsquo;ai gardée dans un coin de tête, jusqu&rsquo;à hier soir où j&rsquo;ai fouillé les internets avec une idée de direction plus précise.</p>
<h2 id="mesurer-le-temps">Mesurer le temps</h2>
<p>À l&rsquo;aide de deux instructions, on peut obtenir un rapport avec les différentes étapes exécutées au lancement de <code>zsh</code> et le temps associé.</p>
<p>Il suffit de modifier le fichier <code>.zshrc</code> pour y ajouter <code>zmodload zsh/zprof</code> au tout début, à la première ligne. On quitte le terminal, on le relance et on tape la commande <code>zprof</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">num  calls                <span class="nb">time</span>                       self            name
</span></span><span class="line"><span class="cl">-----------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl"> 1<span class="o">)</span>    <span class="m">2</span>        3047,39  1523,69   43,08%   2046,60  1023,30   28,93%  nvm
</span></span><span class="line"><span class="cl"> 2<span class="o">)</span>    <span class="m">1</span>        4998,69  4998,69   70,66%   1951,31  1951,31   27,58%  nvm_auto
</span></span><span class="line"><span class="cl"> 3<span class="o">)</span>    <span class="m">1</span>         821,18   821,18   11,61%    665,29   665,29    9,40%  nvm_ensure_version_installed
</span></span><span class="line"><span class="cl"> 4<span class="o">)</span>    <span class="m">1</span>         563,64   563,64    7,97%    563,64   563,64    7,97%  is_update_available
</span></span><span class="line"><span class="cl"> 5<span class="o">)</span>    <span class="m">1</span>         743,74   743,74   10,51%    180,11   180,11    2,55%  /.oh-my-zsh/tools/check_for_upgrade.sh
</span></span><span class="line"><span class="cl"> 6<span class="o">)</span>    <span class="m">1</span>         176,44   176,44    2,49%    174,59   174,59    2,47%  nvm_die_on_prefix
</span></span><span class="line"><span class="cl"> 7<span class="o">)</span>    <span class="m">1</span>         155,88   155,88    2,20%    155,88   155,88    2,20%  nvm_is_version_installed
</span></span><span class="line"><span class="cl"> 8<span class="o">)</span>    <span class="m">1</span>          93,04    93,04    1,32%     51,05    51,05    0,72%  compinit
</span></span></code></pre></div><p>Dans ce tableau, on retrouve le nom des étapes, le temps, le pourcentage du temps par rapport à l&rsquo;ensemble, et c&rsquo;est même classé dans l&rsquo;ordre du plus long au plus rapide.</p>
<p>Sur ma machine, je vois clairement que <code>nvm</code> est dans le haut du panier. Plusieurs noms d&rsquo;étapes semblent liées à <code>nvm</code> et elles prennent énormément de temps. Je vais donc optimiser le lancement de <code>nvm</code>, le reste me semble anecdotique. Je le ferais ou pas un jour. Sûrement jamais. :)</p>
<h1 id="améliorer-linitialisation-de-nvm">Améliorer l&rsquo;initialisation de nvm</h1>
<p>J&rsquo;ai trouvé plusieurs moyens pour pallier la lenteur de <code>nvm</code>.</p>
<h2 id="supprimer-nvm-du-zshrc">Supprimer nvm du .zshrc</h2>
<p>Quand on installe <code>nvm</code>, il y a des instructions à ajouter dans notre configuration <code>.zshrc</code>. Leurs rôles sont de charger <code>nvm</code>, de s&rsquo;assurer qu&rsquo;il est à jour, de préparer l&rsquo;auto-complétion, etc. C&rsquo;est ce qui ralentit le lancement de notre terminal. Ces instructions ressemblent à ça :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">NVM_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/.nvm&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -s <span class="s2">&#34;</span><span class="nv">$NVM_DIR</span><span class="s2">/nvm.sh&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="se">\.</span> <span class="s2">&#34;</span><span class="nv">$NVM_DIR</span><span class="s2">/nvm.sh&#34;</span>  <span class="c1"># This loads nvm</span>
</span></span><span class="line"><span class="cl">    <span class="o">[</span> -s <span class="s2">&#34;</span><span class="nv">$NVM_DIR</span><span class="s2">/bash_completion&#34;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="se">\.</span> <span class="s2">&#34;</span><span class="nv">$NVM_DIR</span><span class="s2">/bash_completion&#34;</span>  <span class="c1"># This loads nvm bash_completion</span>
</span></span></code></pre></div><p>Bon. Eh bien de manière pragmatique, on peut supprimer ces instructions de notre configuration <code>.zshrc</code> et le temps de lancement sera grandement amélioré. Si on n&rsquo;utilise plus <code>nvm</code>, <code>node</code> et compagnie, ou très peu souvent, on n&rsquo;a pas envie de pourrir l&rsquo;expérience nominale de notre terminal.</p>
<h2 id="lazy-loading-de-nvm">Lazy loading de nvm</h2>
<p>Une autre solution serait d&rsquo;initialiser <code>nvm</code> uniquement lorsque l&rsquo;on en a besoin. Quand on tapera une commande avec <code>nvm</code>, <code>node</code> ou <code>npm</code> par exemple. C&rsquo;est du lazy loading. Ce serait l&rsquo;idéal si on veut conserver <code>nvm</code> et que l&rsquo;on veut éviter de ralentir le lancement du terminal.</p>
<p>Il est possible de le faire soi-même en créant quelques fonctions bash comme l&rsquo;explique <a href="https://broken-by.me/lazy-load-nvm/" target="_blank" rel="noopener noreffer">cet article</a>. On peut aussi utiliser le système de plugin de <code>zsh</code>. Luke en a créé un pour nvm : <a href="https://github.com/lukechilds/zsh-nvm" target="_blank" rel="noopener noreffer">zsh-nvm</a>. J&rsquo;ai opté pour cette solution.</p>
<ul>
<li>La première étape est de supprimer les éventuelles instructions <code>nvm</code> de notre fichier <code>.zshrc</code> (cf. partie précédente).</li>
<li>On installe ensuite le plugin <a href="https://github.com/lukechilds/zsh-nvm?tab=readme-ov-file#installation" target="_blank" rel="noopener noreffer">zsh-nvm</a>.</li>
<li>On modifie le fichier <code>.zshrc</code> pour ajouter <code>zsh-nvm</code> dans la liste des plugins.
<ul>
<li>La ligne ressemble à <code>plugins=(.......)</code>.</li>
</ul>
</li>
<li>On indique au plugin que l&rsquo;on veut du lazy loading.
<ul>
<li>On ajoute <code>export NVM_LAZY_LOAD=true</code> juste avant la liste des plugins.</li>
</ul>
</li>
<li>On peut ajouter l&rsquo;auto-complétion si nécessaire.
<ul>
<li>En ajoutant également <code>export NVM_COMPLETION=true</code> avant la liste des plugins.</li>
</ul>
</li>
</ul>
<h2 id="changer-de-version-manager-pour-node">Changer de version manager pour node</h2>
<p>Vu que <code>nvm</code> est le fautif et qu&rsquo;il existe d&rsquo;autres version manager pour node, on peut se demander s&rsquo;il en existe qui sont plus rapides.</p>
<p>Ça semble être le cas pour <a href="https://github.com/Schniz/fnm" target="_blank" rel="noopener noreffer">fnm (Fast Node Manager)</a>. Son nom est revenu plusieurs fois pendant mes recherches et dans les commentaires de personnes qui proposaient des alternatives au lazy loading de <code>nvm</code>. Je ne l&rsquo;ai pas essayé.</p>
<p>Il y a également <a href="https://asdf-vm.com/" target="_blank" rel="noopener noreffer">asdf</a>, un outil unique qui propose de gérer les versions de multiples autres outils (node, Ruby, python, &hellip;). J&rsquo;en parlais dans mon article sur <a href="https://jordanchapuy.com/posts/2023/05/des-version-manager-pour-faire-cohabiter-plusieurs-versions-dun-langage-sdk-et-framework/" rel="">les version manager</a>. Il serait plus rapide que <code>nvm</code> d&rsquo;après ce que j&rsquo;ai lu. À tester. Je pense que je finirais par me passer de mes autres version manager pour tout regrouper avec <code>asdf</code>.</p>
<hr>
<p>Quelques références de contenu que j&rsquo;ai parcouru :</p>
<ul>
<li><a href="https://dev.to/thraizz/fix-slow-zsh-startup-due-to-nvm-408k" target="_blank" rel="noopener noreffer">https://dev.to/thraizz/fix-slow-zsh-startup-due-to-nvm-408k</a></li>
<li><a href="https://blog.mattclemente.com/2020/06/26/oh-my-zsh-slow-to-load/" target="_blank" rel="noopener noreffer">https://blog.mattclemente.com/2020/06/26/oh-my-zsh-slow-to-load/</a></li>
<li><a href="https://broken-by.me/lazy-load-nvm/" target="_blank" rel="noopener noreffer">https://broken-by.me/lazy-load-nvm/</a></li>
<li><a href="https://stackoverflow.com/questions/47009776/how-to-run-nvm-in-oh-my-zsh" target="_blank" rel="noopener noreffer">https://stackoverflow.com/questions/47009776/how-to-run-nvm-in-oh-my-zsh</a></li>
</ul>
]]></description></item><item><title>Enquête, expérimentations et résolution d'anomalies sur mobile</title><link>https://jordanchapuy.com/posts/2023/04/enquete-experimentations-et-resolution-danomalies-sur-mobile/</link><pubDate>Mon, 10 Apr 2023 18:50:00 +0200</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2023/04/enquete-experimentations-et-resolution-danomalies-sur-mobile/</guid><description><![CDATA[<p>On a rencontré une anomalie qui nous a donné du fil à retordre fin mars. J&rsquo;ai envie de raconter cette histoire parce que je trouve intéressant de partager certains éléments que j&rsquo;utilise pour m&rsquo;aider dans ces situations. Je vais essayer de ne pas donner trop de détails futiles.</p>
<p>Tout commence lorsque notre PO, vient nous voir pour nous montrer un problème avec la liste des offres mises en favoris. Pour une catégorie d&rsquo;offre en particulier, l&rsquo;icône favori n&rsquo;est pas dans le bon état.</p>
<p>Première action, j&rsquo;essaie de reproduire le problème. Avant de foncer dans le code, <strong>je cherche les scénarios utilisateurs qui provoquent le problème</strong> et je les note. Et ensuite, avec ça, je peux me prouver que l&rsquo;anomalie est résolue en ayant écrit des tests et en rejouant le scénario avec/sans correctif. C&rsquo;est un des premiers conseils que je voudrais partager dans cette histoire.</p>
<p>Grâce à un scénario précis, on peut également affiner l&rsquo;origine du problème. Par exemple, ici, ça m&rsquo;a permis de voir que le problème n&rsquo;était absolument pas lié à la catégorie de l&rsquo;offre. L&rsquo;origine se situait dans notre cache HTTP - il nous manquait une invalidation du cache suite à une action spécifique. On corrige le problème en pair-programming et, tout content de notre avancée, on informe notre PO qu&rsquo;une nouvelle version de l&rsquo;application est déployée en recette. On passe à autre chose. Fin de l&rsquo;histoire, tous les problèmes sont résolus.</p>
<p>Tous ? Non ! Un village peuplé d’irréductibles anomalies résiste encore et toujours aux développeurs. Le fil à retordre va devenir bien rigide.</p>
<p>À notre grande surprise, notre PO revient me voir : &ldquo;je fais comme avant et j&rsquo;ai le même problème, la correction n&rsquo;aide pas&rdquo;. Ok. Très étrange. On a pourtant bien constaté la résolution avec nos scénarios. La cause identifiée était parfaitement logique. La partie métier est très bien couverte par des tests. Je recommence le scénario sur mon téléphone, c&rsquo;est OK. Qu&rsquo;est-ce qui diffère entre nos téléphones ?</p>
<p>Frédéric a constamment le problème alors que de mon côté, je ne l&rsquo;ai jamais. En creusant et en essayant diverses choses (même modèle de téléphone, s&rsquo;échanger les comptes utilisateurs, &hellip;), on finit par trouver. Il télécharge l&rsquo;application depuis <em>Firebase Distribution</em> alors que moi je l&rsquo;exécute directement depuis mon IDE. On a la même base de code, mais la compilation diffère légèrement - lorsqu&rsquo;une application est déployée, elle est compilée en mode <em>release</em> (il y a des optimisations, des règles plus strictes, etc).</p>
<p>Super ! On sait reproduire à 100%. Super ! Compiler en mode release signifie aussi que je me coupe de tout un tas d&rsquo;outils de développeurs pour observer ce qu&rsquo;il se passe. Je ne peux pas debugger avec des breakpoints, je ne peux pas lire les logs, je ne peux pas observer les échanges réseaux, et, très embêtant, les build en mode release sont bien plus long à générer que l&rsquo;exécution en mode debug.</p>
<p>À ce moment-là, je me dis qu&rsquo;on a un bon problème mystique. Je n&rsquo;arrive pas à reproduire à 100% en mode debug, et on ne sait rien de la cause. C&rsquo;est à mon tour de changer de mode : je passe en mode &ldquo;enquête&rdquo;.</p>
<p>Je commence par ouvrir un document pour <strong>écrire ce que je sais du contexte et du scénario qui reproduit le problème.</strong> Ça me permet de rassembler toutes les informations, de vérifier si je passe à côté de quelque chose d&rsquo;évident en l&rsquo;explicitant, et de pouvoir mieux communiquer. Puis <strong>je lance une multitude d&rsquo;hypothèses</strong>. On a plusieurs appels HTTPs qui se déclenchent au lancement de l&rsquo;application, dont la liste des favoris. Est-ce que notre backend rejette certaines requêtes parce qu&rsquo;un même compte utilisateur fait trop d&rsquo;appels simultanés ? Est-ce le client HTTP utilisé dans l&rsquo;application ? Est-ce le cache HTTP ? Est-ce l&rsquo;OS du téléphone qui empêche de faire trop de requêtes dans un trop court laps de temps ? Etc. J&rsquo;essaie de ne fermer aucune porte.</p>
<p>Ensuite, je me lance dans des <strong>expérimentations rapides</strong>. Je cherche à valider ou invalider des hypothèses pour affiner progressivement l&rsquo;origine. Pour le moment l&rsquo;éventail des possibilités est trop larges. Je cherche aussi à apprendre de nouvelles informations avec les résultats de ces expérimentations.</p>
<p>Je commence par m&rsquo;orienter sur le backend. Je veux voir ce qu&rsquo;il se passe alors j&rsquo;ouvre <em>Elastic</em> (un outils d&rsquo;analyse de logs très puissant ajouté sur notre backend) et je filtre les logs sur l&rsquo;identifiant de mon compte utilisateur. Tiens, la requête des favoris n&rsquo;apparaît pas. Je vais voir un développeur backend pour creuser cette curiosité. On fait plusieurs vérifications, notre backend n&rsquo;a pas de mécanisme pour rejeter la requête, et ce n&rsquo;est pas l&rsquo;infrastructure non plus. <strong>J&rsquo;invalide donc l&rsquo;hypothèse</strong> du backend, et je repars avec un <strong>apprentissage</strong> intéressant : la requête n&rsquo;arrive pas au backend.</p>
<p>Je repasse sur l&rsquo;application mobile. Je veux travailler l&rsquo;hypothèse du cache. Au tout début, j&rsquo;ai effectué un correctif mais il reste un problème. Le mécanisme dans sa globalité a un peu de complexité, je cherche un réponse stricte et binaire : est-ce que ça vient du cache, OUI ou NON ? Je désactive le cache simplement, au plus haut niveau, sur toutes les routes - il n&rsquo;est plus du tout utilisé. Résultat, le problème est toujours là, l&rsquo;hypothèse est invalidée.</p>
<p>Il faut chercher ailleurs, mais je manque d&rsquo;indices. J&rsquo;ai besoin de rendre l&rsquo;application davantage <strong>observable</strong>. Pour rappel, en mode release, l&rsquo;application n&rsquo;écrit pas dans la console lorsque l&rsquo;on appelle la fonction notre logger. Néanmoins, la fonction est tout de même appelée. Eh bien, je peux modifier la fonction pour stocker les logs en mémoire, et ajouter un bouton sur la page d&rsquo;accueil qui me permet de me partager les logs (via un mail ou une note synchronisée dans le cloud). Je peux faire ça très rapidement, en quelques minutes.</p>
<p>J&rsquo;insiste à nouveau sur <strong>rapide</strong>. Tout le long de mon enquête, je veux une <strong>boucle de feedback très courte</strong>, je ne cherche pas à coder pendant 3h un truc <em>over engineered</em> ou trop <em>clean</em> ou trop <em>future-proof</em>, ce n&rsquo;est pas le but. Je ne conserverais aucun code à la fin. Lorsque j&rsquo;aurais quelque chose qui fonctionne et qui aura corrigé le problème, je <strong>recommencerais</strong> proprement de zéro en sachant où je dois aller.</p>
<p>Maintenant que j&rsquo;ai accès à des logs provenant de l&rsquo;application, je constate que la requête HTTP ne part pas. Ça confirme ce qu&rsquo;on a pu voir sur le backend, et le besoin d&rsquo;avoir plus d&rsquo;informations pour comprendre pourquoi la requête ne part pas. J&rsquo;ajoute alors davantage de logs, pour voir si chaque brique est bien appelée. Middleware, repository, action, etc. Tout le cheminement est bien fait, et ce n&rsquo;est pas surprenant vu que le use case est couvert par des tests.</p>
<p>Il y a quelque chose de plus fin à trouver. Je mets des logs juste avant et juste après l&rsquo;appel HTTP. Bingo. Une bizzarerie se met en évidence : le log après l&rsquo;appel HTTP n&rsquo;est jamais appelé, et il n&rsquo;y a pas non plus d&rsquo;exception soulevée. La fonction semble ne jamais rendre la main. Première hypothèse, on pense que c&rsquo;est dû à la co-existance de deux clients HTTPs (le temps d&rsquo;une migration douce). Alors on migre vulgairement ces quelques requêtes sur le même client. Invalidée. On commente les appels réseaux au démarrage, sauf ceux des favoris. Invalidée.</p>
<p>Je saute dans le temps pour arriver à la fin. On fini par identifier la source du problème et y apporter une correction. On comprend au passage qu&rsquo;il y avait deux problèmes distincts, le premier correctif était aussi nécessaire. Tout fonctionne parfaitement. Victoire, par toutatis !</p>
<hr>
<p>Au travers de cette histoire, j&rsquo;ai essayé de mettre en lumière certains aspects. L&rsquo;utilité d&rsquo;avoir des scénarios de reproduction précis, tout d&rsquo;abord, pour vérifier que l&rsquo;anomalie est corrigée, écrire des tests, éliminer/affiner de premières hypothèses. Puis le fonctionnement en expérimentations rapides. On essaie d&rsquo;avoir un tas d&rsquo;hypothèses, d&rsquo;en sélectionner une à la fois, de valider ou invalider rapidement avec des actions simples, d&rsquo;apprendre de nouvelles informations pour la suite de l&rsquo;enquête.</p>
<p>Il y a aussi une notion d&rsquo;observabilité. On a besoin de comprendre ce qu&rsquo;il se passe pendant qu&rsquo;un logiciel est utilisé. Côté backend, on est très bien équipé avec la suite ELK par exemple. Mais côté mobile, Crashlytics n&rsquo;est pas du tout suffisant. Les crashs ne sont qu&rsquo;un sous-ensemble des problèmes qui peuvent arriver, et l&rsquo;interface est loin d&rsquo;être aussi puissante qu&rsquo;ElasticSearch. On peut s&rsquo;améliorer dans ce domaine tout en respectant les contraintes liées à l&rsquo;écosystème mobile.</p>
<p>À de multiples reprises, j&rsquo;étais également en pair-programming, avec plusieurs collègues. Rien ne vaux plusieurs cerveaux, plusieurs idées, visions, façons de faire.</p>
]]></description></item></channel></rss>