<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>code legacy - Tag - Jordan Chapuy - Blog d'un développeur passionné ❤️</title><link>https://jordanchapuy.com/tags/code-legacy/</link><description>code legacy - Tag - Jordan Chapuy - Blog d'un développeur passionné ❤️</description><generator>Hugo -- gohugo.io</generator><language>fr</language><lastBuildDate>Mon, 10 Apr 2023 18:50:00 +0200</lastBuildDate><atom:link href="https://jordanchapuy.com/tags/code-legacy/" rel="self" type="application/rss+xml"/><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><item><title>La méthode Mikado : de grands changements avec de petites étapes</title><link>https://jordanchapuy.com/posts/2021/07/la-methode-mikado-de-grands-changements-avec-de-petites-etapes/</link><pubDate>Wed, 14 Jul 2021 13:38:00 +0200</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2021/07/la-methode-mikado-de-grands-changements-avec-de-petites-etapes/</guid><description><![CDATA[<p>Vous avez déjà assemblé des Lego ou monté un meuble Ikea ? Quand j&rsquo;étais petit, j&rsquo;adorais les Lego. Je laissais libre cours à mon imagination en créant des tas de lieux, de véhicules et d&rsquo;histoires. J&rsquo;ai aussi eu des boîtes de Lego avec quelque chose de précis à assembler (un château par exemple). Dans ces boîtes, il y a toujours un plan. On y voit le résultat final (le château) et toutes les étapes à suivre pour le construire. Des étapes simples et petites qui se suivent. Parfois, on assemble un morceau (une tour, un mur) et on le met de côté avant de revenir dessus plus tard pour l&rsquo;assembler avec toute la structure. À la fin, on a notre grand château avec toute sa complexité, ses détails, ses nombreuses briques.</p>
<p>La méthode Mikado, c&rsquo;est un peu la même chose. On suit un plan décomposé en plusieurs petits pré-requis pour atteindre un objectif plus grand bien précis. Sauf que ce plan, on ne l&rsquo;a pas au départ mais on le construit au fil de notre avancée. Et c&rsquo;est là toute la puissance de la méthode Mikado. On avance par petits pas, on expérimente, on visualise notre chemin, et on reste toujours dans un état fonctionnel jusqu&rsquo;à atteindre l&rsquo;objectif défini.</p>
<p>Je m&rsquo;en sers régulièrement lors de <a href="https://jordanchapuy.com/posts/2021/06/le-refactoring/" rel="">refactoring</a> et je trouve ça génial (n&rsquo;en déplaise aux adeptes des chantiers abandonnés après 4 mois sans visibilité). La méthode m&rsquo;apporte de la structure, je vois ce que j&rsquo;ai à faire, j&rsquo;avance par petites étapes. Rencontrer des difficultés ou des imprévus en chemin est moins problématique grâce à l&rsquo;approche très itérative et expérimentale de la méthode. Je découpe les problèmes, j&rsquo;essaie des solutions, je garde ce qui marche, j&rsquo;annule ce qui ne fonctionne pas. Le projet est toujours dans un état fonctionnel. Je peux m&rsquo;arrêter à tout moment et le livrer.</p>
<p>C&rsquo;est très puissant pour effectuer des changements progressivement et avec rigueur dans n&rsquo;importe quel domaine. Je parlais de refactoring, mais on peut effectuer tout type d&rsquo;évolutions dans notre code, ou également sortir de la sphère technique en l&rsquo;utilisant dans notre équipe pour modifier une organisation, ou dans notre quotidien.</p>
<h1 id="la-méthode-mikado">La méthode Mikado</h1>
<p>Avant de plonger profondément dans les détails, je vous propose de découvrir un exemple d&rsquo;utilisation de la méthode Mikado qui parle à tout le monde pour représenter son fonctionnement.</p>
<div class="details admonition question open">
        <div class="details-summary admonition-title">
            <i class="icon fas fa-question-circle fa-fw"></i>Une histoire de TV<i class="details-icon fas fa-angle-right fa-fw"></i>
        </div>
        <div class="details-content">
            <div class="admonition-content"><p>Notre équipe a une salle de pause qui augmente la productivité de 700 % depuis que nous l’avons ré-aménagé. Son secret ? Des fauteuils confortables et des Fatboy pour profiter pleinement d’une diffusion continue des épisodes de Kaamelott. L’efficacité est telle que la direction nous a fait livrer une nouvelle TV 4K avec une très grande diagonale.</p>
<p>Je veux donc remplacer le vieil écran PC posé sur le plan de travail par la nouvelle TV afin de regarder Kaamelott en 4K ! Il faut cependant limiter au maximum l’interruption de la diffusion des épisodes, pas question de perdre les bénéfices de notre salle de pause.</p>
<p>Par quoi pourrais-je commencer ? Je pourrais brancher le lecteur sur la nouvelle TV, ou mettre le Blu-ray dans le lecteur à la place du DVD. Allez, je vais d’abord mettre le BluRay. J’arrête la lecture, je retire le DVD, je le range dans la boite, je prends le Blu-ray… Le Blu-ray ?! Ah, mince, on ne l&rsquo;a pas encore en fait. Je n’y avais pas du tout pensé. Ce n’est pas grave, je remet tout en état avec la diffusion du DVD, comme avant. Que dois-je faire maintenant pour avoir les Blu-ray ? Il faut simplement l&rsquo;acheter. Ok, rien ne me bloque, j&rsquo;y vais.</p>
<p>[Plus tard&hellip;]</p>
<p>J&rsquo;ai maintenant les Blu-ray de toutes les saisons de Kaamelott. Un superbe coffret. Je peux reprendre l’étape précédente. Je remplace le DVD dans le lecteur par le Blu-ray, puis je lance la lecture. Tout fonctionne, c’est génial. Je dois m’arrêter là pour le moment par contre, ça m’a pris plus de temps que prévu et j’ai des impératifs. Je continuerai la semaine prochaine.</p>
<p>[Plus tard&hellip;]</p>
<p>J’ai un peu de temps pour avancer vers notre objectif ce matin. Où est-ce que j’en étais ? Ah oui, au branchement du lecteur sur la nouvelle TV. Facile. Je débranche l’ancien écran, je sors la TV et… Et rien du tout. C’est une TV que l’on doit poser sur le mur, elle n’a pas de pied. Il faut que je pose cette accroche au mur avant, et pour ça, il faut que je perce les trous dans le mur. Retour en arrière, je range tout et je rebranche l’ancien écran.</p>
<p>Je ne peux pas m’occuper de ces dernières étapes tout de suite, mais un de mes collègues s’est proposé de prendre le relai. Il a compris ce qu’il restait à faire, il s’en charge. Percer les trous, puis poser l’accroche, installer la TV, et enfin la brancher. Voilà. Nous pouvons maintenant profiter d&rsquo;une légende arthurienne encore plus grande.</p>
</div>
        </div>
    </div>
<h2 id="les-grands-principes">Les grands principes</h2>
<p>Ok. Maintenant que vous avez un exemple simple et concret d&rsquo;utilisation de la méthode Mikado, nous pouvons décortiquer les grands principes au travers de cette histoire.</p>
<h3 id="avoir-un-objectif">Avoir un objectif</h3>
<p>On commence avec un objectif précis. Dès le départ, on sait où l’on doit aller, on a un cap. On se concentre dessus, on ne fait pas autre chose en même temps. Dans le récit précédent, le besoin était de remplacer un vieil écran par une nouvelle TV afin de regarder Kaamelott en 4K. Le résultat attendu est parfaitement clair.</p>
<h3 id="faire-des-expérimentations">Faire des expérimentations</h3>
<p>Tout au long de notre chemin pour arriver à notre objectif, on va expérimenter. On va essayer des choses qui nous rapprochent de l’objectif. On avance par petites étapes. On cherche des solutions simples et naïves. Par exemple, on essaie de brancher directement la nouvelle TV et on voit ce qu’il se passe.</p>
<p>N’essayez pas de tout prévoir en avance, de faire un plan complet pour aller sur la lune. Demandez-vous ce que vous pouvez faire comme toute première étape pour avancer. Mettre le Blu-ray ? Ok, j’essaie.</p>
<h4 id="valider-une-expérimentation-réussie">Valider une expérimentation réussie</h4>
<p>Lorsqu’une expérimentation fonctionne, on la valide. On commit, on enregistre, on garde ces modifications. Elles nous permettent d’avancer. Et si on arrête là, tout fonctionne toujours. On n’a peut-être pas terminé, mais on a avancé dans le schmilbik. J’ai acheté le Blu-ray de Kaamelott, je me suis rapproché de mon objectif final.</p>
<h4 id="annuler-une-expérimentation-échouée">Annuler une expérimentation échouée</h4>
<p>Quand une expérimentation rate, on annule. On retire toutes les modifications de notre expérimentation et on revient dans l&rsquo;état précédant. On restore sur git, on remet tout en place. C’est une des clés de la méthode. Si vous ne le faites pas, vous n’appliquez pas la méthode Mikado. Pour être transparent, j’ai tenté de ne pas le faire, de prendre des raccourcis, d&rsquo;aller trop vite. Je me suis perdu et cela ne m’a pas aidé.</p>
<p>Revenir en arrière est très puissant. On n&rsquo;a pas expérimenté pour rien, on a appris des choses. On se pose des questions pour continuer. Qu’est-ce qui me bloque ? Que faut-il faire pour débloquer cette étape ? On va construire la suite de notre chemin grâce à cette expérimentation échouée et on pourra revenir sur cette étape plus tard, dans de meilleures conditions pour réussir.</p>
<p>Si on arrête là, tout fonctionne. Toujours. Parce qu&rsquo;on garde les expérimentations qui ont réussi et on retourne en arrière lorsqu&rsquo;elles échouent.</p>
<h3 id="dessiner-un-graphe-pour-visualiser">Dessiner un graphe pour visualiser</h3>
<p>Vous ne le voyez pas en lisant l&rsquo;histoire, mais un graphe a été créé et modifié au fil des expérimentations. C&rsquo;est un outil essentiel dans l&rsquo;utilisation de la méthode Mikado qui permet de visualiser l&rsquo;avancée. On voit ce qu&rsquo;on a fait, ce qu&rsquo;on a tenté, ce qu&rsquo;il reste à faire. On sait facilement à quel point on se situe. On pourra reprendre aisément plus tard ou se faire aider par d&rsquo;autres personnes. Oui car c&rsquo;est également un très bon support pour communiquer, pour expliquer ce que l&rsquo;on fait, pour avoir un coup de main.</p>
<figure><a href="images/mikado-kaamelott.jpeg"></a>
</figure>

<h2 id="la-création-du-graphe">La création du graphe</h2>
<p>Dessiner un graphe avec la méthode Mikado est très simple. On commence par écrire l&rsquo;objectif tout en bas dans un double cercle.</p>
<figure><a href="images/mikado-graph-1.jpeg"></a>
</figure>

<p>On ajoute ensuite un cercle pour chaque expérimentation que l&rsquo;on réalise, au-dessus du pré-requis précédant, et on les relie à l&rsquo;aide d&rsquo;une flèche. La flèche pointe vers le ou les pré-requis à effectuer au préalable - il faut d&rsquo;abord acheter le Blu-ray afin de le mettre dans le lecteur.</p>
<figure><a href="images/mikado-graph-2.jpeg"></a>
</figure>

<p>On repère très rapidement les possibles prochaines étapes : il s&rsquo;agit des pré-requis n&rsquo;ayant pas de dépendance. On peut alors choisir une expérimentation et essayer.</p>
<ul>
<li>Si elle échoue, on ajoutera une ou des nouvelles expérimentations en pré-requis à cette étape.</li>
<li>Si elle réussie, on cochera l&rsquo;expérimentation. Ce qui débloquera peut-être d&rsquo;autres étapes.</li>
</ul>
<figure><a href="images/mikado-graph-3.jpeg"></a>
</figure>

<h2 id="bref-la-démarche">Bref, la démarche</h2>
<p>Si vous avez bien compris les principes, la démarche ne devrait pas vous surprendre. C&rsquo;est tout simplement l&rsquo;application de ces principes coordonnés par le graphe, étape par étape. Dans le livre Mikado Method, il y a un diagramme qui représente très bien les différentes étapes à suivre. Je l&rsquo;ai recréé ci-dessous.</p>
<figure><a href="images/mikado-method-diagram.jpeg"></a>
</figure>

<p>On commence par dessiner l&rsquo;objectif sur notre graphe. On essaie ensuite naïvement de trouver une solution. S&rsquo;en suit une répétition de ces étapes selon le succès de l&rsquo;expérimentation :</p>
<ul>
<li>Dès que l&rsquo;on bloque ou que notre expérimentation échoue, on dessine les nouveaux pré-requis dans le graphe, on annule les modifications, et on choisit un autre pré-requis.</li>
<li>Lorsque l&rsquo;on réussi, on enregistre nos modifications, on commit, et on démarre avec une autre expérimentation du graphe.</li>
</ul>
<p>On réitère toutes ces étapes jusqu&rsquo;à atteindre l&rsquo;objectif. Simple et terriblement efficace.</p>
<h1 id="la-dernière-brique">La dernière brique</h1>
<p>On a vu une méthode qui permet de structurer des changements. Même lorsqu&rsquo;il y a un grand chantier devant nous, la méthode Mikado apporte de la structure en travaillant rigoureusement par petites étapes et expérimentations. Le graphe nous guide tout le long pour arriver à notre objectif, et sert également de support pour communiquer. On ne conserve que ce qui fonctionne et on annule ce qui échoue, ce qui permet d&rsquo;avoir un projet ou contexte toujours opérationnel.</p>
<p>Et à la fin, il reste toujours une brique Lego ou une pièce du meuble Ikea. Considérez-là comme une dernière chance d&rsquo;améliorer votre travail une fois que l&rsquo;ensemble est achevé :)</p>
]]></description></item><item><title>Le Refactoring</title><link>https://jordanchapuy.com/posts/2021/06/le-refactoring/</link><pubDate>Thu, 10 Jun 2021 08:42:00 +0200</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2021/06/le-refactoring/</guid><description><![CDATA[<p>La machine à café, c’est un peu un objet magique pour les développeurs. On peut y voir deux principales capacités. Elle provoque des résolutions de problèmes spontanées suite à des discussions proche de la machine. Et elle produit le café qui, une fois assimilé par le développeur, se transforme en d’innombrables lignes de code. Alors quand on possède un tel objet, on n’a pas envie de le casser. On voudra certainement l’améliorer, mais en le gardant fonctionnel. Tiens, ne commencerait-on pas à parler de refactoring ?</p>
<p>Avant d’entrer dans les détails, je vous propose de sortir un stylo et une feuille afin de vous poser la question suivante : <strong>qu’est-ce que le refactoring ?</strong> Faites couler l’encre, gravez votre définition dans les fibres du papier.</p>
<figure>
</figure>

<h2 id="définitions-et-fondamentaux">Définitions et fondamentaux</h2>
<p>Le refactoring demande de la <strong>discipline</strong> et de l’<strong>apprentissage</strong>. Il existe des méthodologies, des principes et de multiples techniques. <em>Ah bon, ce n’est pas juste tout refaire ?</em> Non. On voit trop souvent ces fameux « chantiers » qui durent des mois, effectués en silo, sans visibilité et qui ont un résultat peu satisfaisant. Ils auront souvent duré bien plus longtemps que prévu, ou comporteront des régressions, ou seront abandonnés avant la fin. J&rsquo;en ai vu et j&rsquo;en vois malheureusement encore. J&rsquo;en ai également fait, mais depuis, j&rsquo;ai appris et je continue d&rsquo;apprendre.</p>
<p>Aujourd’hui, j’ai envie de parler de ce qu’est le refactoring, de ses grands principes. Avant de se lancer dans ces grands chantiers, comprenons de quoi on parle. Je vais m’appuyer sur les définitions de Martin Fowler. Si vous ne le connaissez pas, c’est un des grands noms quand on parle de refactoring. Je vous recommande son site et ses livres.</p>
<blockquote>
<p>noun: a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.</p>
</blockquote>
<blockquote>
<p>verb: to restructure software by applying a series of refactorings without changing its observable behavior.</p>
</blockquote>
<p>Je vois plusieurs fondamentaux dans ces définitions. C’est bien plus que « tout ré-écrire » ou même que « ré-écrire pour améliorer » (déjà plus précis, mais encore trop simpliste).</p>
<p>Faire du refactoring, c’est <strong>conserver le même comportement</strong>. Ne pas casser l’existant. Si on change le comportement, ce n’est plus du refactoring. On sépare le refactoring des évolutions et corrections de bugs. Dans le cycle du TDD, il y a une étape dédiée pour le refactoring ; ce n’est pas pendant qu’on écrit le test ni pendant qu’on fait émerger une solution. Lorsque l’on fait du refactoring sur la machine à café, on veut qu’elle continue à faire du café lorsque l’on a terminé. On ne veut pas qu’elle fasse un thé, ni un tiramisu. Et on veut encore moins qu’elle soit en panne.</p>
<p>Faire du refactoring, c’est <strong>avancer par petites étapes</strong>. On enchaîne une série d’améliorations, on évite de vouloir tout changer d’un coup. Ce sont des petites étapes que l’on peut <strong>partager régulièrement</strong>. C’est à dire les rendre disponibles aux autres, les committer, les rapatrier sur la branche principale. Ainsi, on peut s’arrêter à tout moment. Si soudainement on doit faire quelque chose de plus prioritaire ou partir en vacances, <strong>le projet est toujours dans un bon état</strong> et nos petites améliorations seront en plus intégrées. Dans l’hypothèse où les développeurs tirent leurs super-pouvoirs du café, si pendant un mois la machine est en chantier, donc inutilisable, ça posera des problèmes.</p>
<p>Faire du refactoring dans le but d’<strong>améliorer la compréhension et de diminuer le coût du changement</strong>. On peut vouloir rendre le code plus lisible, avec une intention plus claire, un métier mieux projeté. L’amélioration continue est importante. On peut vouloir également rendre le changement plus facile, donc moins couteux. Ré-écrire uniquement pour le plaisir du développeur d’intégrer le dernier framework X-2000 est peut-être une moins bonne raison. Notre machine à café a besoin d’un refactoring, car elle ne prend en charge qu’un seul type de dosette très précis.</p>
<p>Je pense qu’il est important de prendre conscience de ces fondamentaux et d’être dans cet état d’esprit afin d’améliorer son approche du refactoring. Bien sûr, il reste du chemin ensuite, ce n’est que le début du voyage, mais c’est une étape essentielle.</p>
<h2 id="aller-plus-loin">Aller plus loin</h2>
<p>Pour continuer d’approfondir le refactoring, on peut regarder du côté de la détection de code smells, de l’apprentissage des techniques de refactoring, de méthodes pour structurer un refactoring, ou encore de l’art de tester du code legacy (casser des dépendances, introduire des tests, …). Oui, la liste est grande, ce n&rsquo;est pas si simple. Et oui, il ne faut pas oublier les tests pour s&rsquo;ajouter un filet de sécurité.</p>
<p>Quelques ressources autour du refactoring :</p>
<ul>
<li>Martin Fowler. Une figure incontournable avec son le site <a href="https://refactoring.com/" target="_blank" rel="noopener noreffer">refactoring.com</a> et son livre <em>Refactoring Improving the Design of Existing Code</em>.</li>
<li>Michael Feathers. Une seconde figure incontournable, plus axée sur le code legacy avec son livre <em>Working Effectively with Legacy Code</em>.</li>
<li>Le site <a href="https://refactoring.guru/" target="_blank" rel="noopener noreffer">refactoring.guru</a> listant techniques de refactoring et code smells.</li>
<li>Divers katas pour s&rsquo;exercer comme le Gilded Rose, Tennis Game, Trivia, etc.</li>
<li>Ici même, avec le temps, via le tag <a href="https://jordanchapuy.com/tags/refactoring/" rel="">#refactoring</a>.</li>
</ul>
<h2 id="seconde-édition">Seconde édition</h2>
<p>Vous avez toujours le bout de papier avec votre première définition ? Reprenez-le. Je vous propose d’écrire une nouvelle fois ce que représente le refactoring pour vous pour terminer. Au dos de la feuille ou sans relire les définitions a minima.</p>
<p>Amusez-vous à comparer les trois définitions maintenant. Et si le cœur vous en dit, partagez-moi vos écrits :)</p>
]]></description></item><item><title>Profiter de chaque occasion pour améliorer le code legacy</title><link>https://jordanchapuy.com/posts/2020/09/profiter-de-chaque-occasion-pour-ameliorer-le-code-legacy/</link><pubDate>Tue, 08 Sep 2020 09:00:00 +0200</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2020/09/profiter-de-chaque-occasion-pour-ameliorer-le-code-legacy/</guid><description><![CDATA[<p>Vivre avec une grande codebase legacy n’est pas agréable. Les changements sont longs, les bugs se cumulent, la compréhension se perd. Cependant, rien ne nous oblige à subir ces inconvénients éternellement. On peut agir, à tout moment, et dès maintenant. Il n’y a pas besoin de se lancer dans un grand chantier pour cela, ni de prévoir un plan pour aller sur la Lune. On peut faire du refactoring par petites étapes et le faire régulièrement. Profiter de chaque occasion (une évolution, une tâche, une correction) pour améliorer le code qui entoure les modifications. Ce concept, tout simple, permet d’améliorer la qualité du code dans le temps.</p>
<p>Cet été, j’ai eu un bug à corriger sur une partie de l’application que je n’avais pas encore touché. Après une brève balade dans le code, je tombe sur le <em>RoadMapBlock</em>. Le fameux <em>RoadMapBlock</em>. Ça ne vous parle certainement pas, mais en interne, ce nom possède une grande renommée. Ce nom-là, je l’ai entendu lors de mes premiers jours. Son évocation suscitait rires nerveux et désespoir. Il est même connu des non-tech, c&rsquo;est dire. Mais de quoi bon s’agit-il ?</p>
<p>Le <em>RoadMapBlock</em> est à la fois une brique centrale pour l’un des écrans les plus importants de l’application et l’un des gros points de douleurs du projet. Cette brique a été créée des années auparavant et a depuis été modifiée par des dizaines de développeurs. Pour donner un ordre d’idée, la classe possède une centaine de fonctions. La méthode qui contenait le bug faisait avoisinait le millier de lignes et possédait une grande complexité (une centaine de <em>ifs</em>, plusieurs niveaux d’imbrications, des boucles, &hellip;).</p>
<p>Une horreur à première vue, mais également une énorme opportunité d’amélioration.</p>
<hr>
<h1 id="faire-du-refactoring-progressivement">Faire du refactoring progressivement</h1>
<p>J’ai commencé ma session de travail par la localisation de l’erreur dans le code. Je sentais qu’il se passait quelque chose dans une partie de cette fonction, mais la zone était difficilement intelligible. Je me suis donc mis à faire du rapide refactoring pour gagner en compréhension. J’ai effectué plusieurs opérations de refactoring pour améliorer le code, me l’approprier, et y voir plus clair. C’est un moyen rapide de comprendre du code legacy que j’aime bien utiliser (j’en parle plus en détails dans <a href="https://jordanchapuy.com/posts/2020/08/comprendre-du-code-legacy-grace-au-refactoring" rel="">mon précédent article</a>). Peu de temps après, la cause du bug me sautait aux yeux et j’ai pu le corriger facilement. La correction tenait en une seule ligne. Mais était-ce fini ? Non !</p>
<p>J’étais là, au milieu de tout ce code legacy. J’avais pris du temps pour en comprendre une partie, pour l’analyser, pour charger tout ce contexte dans ma tête. J’avais même effectué des modifications ici. Alors autant en profiter pour améliorer le code, pendant que j’étais encore plongé dans ce contexte. Il ne faut pas sous-estimer ce moment. La compréhension du code legacy est souvent difficile et longue. Ce n’est pas non plus quelque chose qui motive, ou qui donne envie, pour la plupart des développeurs. Il faut sauter sur l’occasion pour l’améliorer. Il y a peu de chances qu’on se réveille un matin en se disant « tiens, je vais refaire cette partie pour le plaisir ». Si on ne le fait pas là, maintenant, on ne le fera probablement jamais. Ce code qui nous fait si mal restera une épine dans notre pied.</p>
<p>J’ai donc recommencé mon refactoring proprement. Mon gain de compréhension me permettait de mieux le faire. J’ai renommé des variables, des noms de méthodes. J’ai extrait des méthodes pour commencer à découper et à structurer. J’ai diminué les niveaux d’imbrications des <em>ifs</em>, <em>switch</em>, et <em>for</em>. Des actions simples qui aident énormément pour le futur. Oui, pour le futur. On lit beaucoup plus de code que l’on en écrit. Faire du refactoring maintenant permettra de mieux comprendre ce code toutes les autres fois.</p>
<p>C’est également une fondation pour un refactoring plus large. La succession des petites améliorations facilitent un refactoring plus conséquent. Un jour, il sera aisé de tout reprendre. Le refactoring d’une fonction complexe de mille lignes demande un gros effort. Mais cette même fonction, découpée en plusieurs petites fonctions simples, mieux nommée, mieux structurée, est bien plus facilement modifiable. Peut-être même que ce ne sera plus nécessaire, la structure du code sera peut-être suffisamment bonne pour être comprise et pour évoluer.</p>
<p>L’amélioration du code qui entoure nos modifications peut aller très vite. Je n’ai pas dû prendre plus d’une heure. Une heure sur la vie d’un projet, ce n’est rien. Pourtant, ça a une immense importance. C’est un gros gain. On agit maintenant, on passe à l’action. <strong>C&rsquo;est facile de se plaindre du code legacy, c&rsquo;est aussi facile de faire des améliorations par petits pas</strong>. Cependant, se plaindre ne fera pas avancer les choses.</p>
<h2 id="savoir-sarrêter">Savoir s’arrêter</h2>
<p>On est enfin lancé dans ce refactoring, on veut bien faire. On peut avoir envie de faire quelque chose de parfait. Ou de tout refaire. Mais l’important, c’est de commencer à améliorer, par petits bouts, et de le faire régulièrement. Il est plus facile d’être régulier en faisant des petits refactoring. Avec le temps, ce deviendra un automatisme. Je fais une modification ici, je vais en profiter pour améliorer là. La qualité globale du projet augmentera progressivement.</p>
<p>L’objectif n’est pas de faire quelque chose de parfait. Ce serait trop long. Dans certains cas, le legacy est tellement grand qu’il faut effectuer de nombreux changements. Malheureusement, certains changements sont longs et complexes. Casser des dépendances. Changer la structure du code. Et puis, qu’est-ce que le code parfait ? On peut toujours améliorer et faire différemment. Le design du code peut faire grincer des dents, mais ce n’est pas grave, c’est mieux qu&rsquo;avant.</p>
<p>L’objectif n’est pas de tout refaire. Ce serait risqué et également long. Si on veut tout réécrire d’un coup, le travail sera énorme et probablement risqué. On peut faire de grosses erreurs, rater le refactoring. On peut découvrir de nombreux problèmes en cours de route et prendre beaucoup plus de temps que prévu. Pire, on peut abandonner avant même d’avoir terminé. Tout le travail serait alors jeté. L’équipe ou le management pourrait accorder plus difficilement sa confiance pour d’autres sujets. Il est préférable de bien cibler la partie que l’on veut améliorer à cet instant.</p>
<p>Il faut donc savoir s’arrêter à un moment. Se concentrer sur des petites améliorations. C’est déjà mieux qu’avant. Si on y revient plus tard, on pourra à nouveau l’améliorer. Voire améliorer de manière plus macro. J&rsquo;ai espoir que mon refactoring d&rsquo;une petite zone du <em>RoadMapBlock</em> servira de première étape à l&rsquo;amélioration de l&rsquo;ensemble. La première pierre à l&rsquo;édifice. Ce travail continuera et inspirera les autres développeurs.</p>
<hr>
<h1 id="lancez-vous-maintenant">Lancez-vous, maintenant</h1>
<p>Toutes les améliorations sont intéressantes. Du simple renaming ou suppression de code mort à l’extraction de méthodes ou restructuration. Les améliorations peuvent être simples et effectuées rapidement. Il n’y a pas d’excuse à rester sans rien faire devant du code legacy. Il n’y a pas besoin de tout refaire, ni d’y passer des journées entières.</p>
<p>Faire de petites améliorations et être régulier. C’est une des clés pour sortir du legacy progressivement. Profitez d’une correction de bug ou d’une évolution pour améliorer le code environnant. Passez à l’action dès maintenant.</p>
<p>Et vous, que faites-vous pour améliorer du code legacy ? Quelles sont vos stratégies ?</p>
]]></description></item><item><title>Comprendre du code legacy grâce au refactoring</title><link>https://jordanchapuy.com/posts/2020/08/comprendre-du-code-legacy-grace-au-refactoring/</link><pubDate>Tue, 25 Aug 2020 09:00:00 +0200</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2020/08/comprendre-du-code-legacy-grace-au-refactoring/</guid><description><![CDATA[<p>Je travaille actuellement sur un vieux projet vieux comportant un legacy douloureux. Je suis souvent confronté à du code que je ne comprends pas et sur lequel je ne peux pas obtenir d&rsquo;aide : les autres développeurs n&rsquo;en savent pas plus que moi, et les Product Owner ont perdu la connaissance fonctionnelle.</p>
<p>C&rsquo;est un phénomène commun. Le code legacy s&rsquo;entoure d&rsquo;une perte de connaissance technique et métier. L&rsquo;équipe ne comprends plus ce qui se passe dans le code, et ne sait plus trop ce que ça doit faire fonctionnellement.</p>
<p>Dans ces moments-là, la seule réponse restante se trouve dans le code. Le code est la seule vérité. La vérite d&rsquo;un programme en production. Mais comprendre du code legacy est difficile. Alors comment le faire parler ? </p>
<p>Mes deux méthodes préférées sont le dessin et le <a href="https://jordanchapuy.com/posts/2021/06/le-refactoring/" target="_blank" rel="noopener noreffer">Refactoring</a>. Encore récemment, j&rsquo;ai utilisé le refactoring pour gagner en compréhension, et ça m&rsquo;a donné envie d&rsquo;écrire sur ce sujet.</p>
<hr>
<h1 id="gagner-de-la-compréhension">Gagner de la compréhension</h1>
<p>Si modifier du code pour mieux le comprendre vous surprend, regardons une définition (simple) d&rsquo;un refactoring : c&rsquo;est améliorer le code sans en changer le comportement. Rendre du code plus lisible, plus facile à comprendre, c&rsquo;est améliorer le code. C&rsquo;est exactement ce que l&rsquo;on cherche.</p>
<p>Lorsque je bloque sur un bout de code, je le modifie. J&rsquo;essaie de me l&rsquo;approprier, de gagner en compréhension. Si je casse quelque chose ce n&rsquo;est pas grave, je retourne en arrière avec Git ou tout autre VCS.</p>
<p>Qu&rsquo;est-ce que l&rsquo;on peut faire comme type de refactoring ? Je dirais tout ce qui nous aide. Pour illustrer mon utilisation des différentes techniques de refactoring, j&rsquo;ai écrit une petite fonction. Ce sera notre legacy.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">hit</span><span class="p">(</span><span class="n">p1</span><span class="p">:</span> <span class="n">Player</span><span class="p">,</span> <span class="n">p2</span><span class="p">:</span> <span class="n">Player</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">var</span> <span class="py">name</span> <span class="p">=</span> <span class="s2">&#34;King&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">p1</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">p2</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!(</span><span class="n">p1</span><span class="p">.</span><span class="n">firstName</span> <span class="o">==</span> <span class="s2">&#34;Arthur&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">p1</span><span class="p">.</span><span class="n">lastName</span> <span class="o">==</span> <span class="s2">&#34;Pendragon&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">p1</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;Excalibur&#34;</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">var</span> <span class="py">multiplier</span> <span class="p">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">p1</span><span class="p">.</span><span class="n">lord</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">multiplier</span> <span class="p">=</span> <span class="m">7</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// will multiply the power!!
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>                <span class="k">var</span> <span class="py">power</span> <span class="p">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">strength</span> <span class="p">*</span> <span class="n">multiplier</span>
</span></span><span class="line"><span class="cl">                <span class="n">p1</span><span class="p">.</span><span class="n">strength</span> <span class="o">+=</span> <span class="m">10</span>
</span></span><span class="line"><span class="cl">                <span class="n">power</span> <span class="p">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">strength</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">var</span> <span class="py">health</span> <span class="p">=</span> <span class="n">p1</span><span class="p">.</span><span class="n">strength</span> <span class="p">+</span> <span class="n">p1</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">damage</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">p2</span><span class="p">.</span><span class="n">shield</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">p2</span><span class="p">.</span><span class="n">shield</span><span class="p">.</span><span class="n">defense</span> <span class="p">&gt;</span> <span class="n">p1</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">damage</span> <span class="o">&amp;&amp;</span> <span class="n">p2</span><span class="p">.</span><span class="n">speed</span> <span class="p">&gt;</span> <span class="n">p1</span><span class="p">.</span><span class="n">speed</span> <span class="p">+</span> <span class="m">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">health</span> <span class="p">=</span> <span class="n">health</span> <span class="p">/</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">p1</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">worn</span> <span class="o">+=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">            <span class="n">p2</span><span class="p">.</span><span class="n">health</span> <span class="o">-=</span> <span class="n">health</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">p1</span><span class="p">.</span><span class="n">kill</span><span class="p">(</span><span class="n">p2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Je commence souvent par des actions simples comme renommer. Renommer une variable, une fonction, une classe. Je fais également du nettoyage : en supprimant du code non utilisé, j&rsquo;enlève le superflux.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">hit</span><span class="p">(</span><span class="n">attacker</span><span class="p">:</span> <span class="n">Player</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="n">Player</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">target</span> <span class="o">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!(</span><span class="n">attacker</span><span class="p">.</span><span class="n">firstName</span> <span class="o">==</span> <span class="s2">&#34;Arthur&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">lastName</span> <span class="o">==</span> <span class="s2">&#34;Pendragon&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;Excalibur&#34;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span><span class="p">.</span><span class="n">isLord</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">attacker</span><span class="p">.</span><span class="n">strength</span> <span class="o">+=</span> <span class="m">10</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">var</span> <span class="py">damage</span> <span class="p">=</span> <span class="n">attacker</span><span class="p">.</span><span class="n">strength</span> <span class="p">+</span> <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">damage</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">shield</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">target</span><span class="p">.</span><span class="n">shield</span><span class="p">.</span><span class="n">defense</span> <span class="p">&gt;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">damage</span> <span class="o">&amp;&amp;</span> <span class="n">target</span><span class="p">.</span><span class="n">speed</span> <span class="p">&gt;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">speed</span> <span class="p">+</span> <span class="m">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">damage</span> <span class="p">=</span> <span class="n">damage</span> <span class="p">/</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">worn</span> <span class="o">+=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">            <span class="n">target</span><span class="p">.</span><span class="n">health</span> <span class="o">-=</span> <span class="n">damage</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">attacker</span><span class="p">.</span><span class="n">kill</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>J&rsquo;aime bien inverser les <em>if</em>. J&rsquo;ai parfois du mal à lire les conditions négatives, notamment lorsque la condition est complexe. Je dois d&rsquo;abord comprendre la condition, la mapper mentalement, puis je vois la négation, j&rsquo;inverse cette logique complexe&hellip; pour finalement me perdre. Parfois même je loupe ce tout petit caractère, ce point d&rsquo;exclamation collé trop près de la variable. Alors j&rsquo;inverse. La condition gagne en clarté, je sais pourquoi on passe dans le corps du <em>if</em>, et au passage ça peut permettre de réduire les niveaux d&rsquo;imbrication.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">hit</span><span class="p">(</span><span class="n">attacker</span><span class="p">:</span> <span class="n">Player</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="n">Player</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span> <span class="o">==</span> <span class="k">null</span> <span class="o">||</span> <span class="n">target</span> <span class="o">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span><span class="p">.</span><span class="n">firstName</span> <span class="o">==</span> <span class="s2">&#34;Arthur&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">lastName</span> <span class="o">==</span> <span class="s2">&#34;Pendragon&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">name</span> <span class="o">==</span> <span class="s2">&#34;Excalibur&#34;</span><span class="p">)</span> <span class="p">{</span>    
</span></span><span class="line"><span class="cl">        <span class="n">attacker</span><span class="p">.</span><span class="n">kill</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span><span class="p">.</span><span class="n">isLord</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">attacker</span><span class="p">.</span><span class="n">strength</span> <span class="o">+=</span> <span class="m">10</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">var</span> <span class="py">damage</span> <span class="p">=</span> <span class="n">attacker</span><span class="p">.</span><span class="n">strength</span> <span class="p">+</span> <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">damage</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">shield</span> <span class="o">!=</span> <span class="k">null</span> <span class="o">&amp;&amp;</span> <span class="n">target</span><span class="p">.</span><span class="n">shield</span><span class="p">.</span><span class="n">defense</span> <span class="p">&gt;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">damage</span> <span class="o">&amp;&amp;</span> <span class="n">target</span><span class="p">.</span><span class="n">speed</span> <span class="p">&gt;</span> <span class="n">attacker</span><span class="p">.</span><span class="n">speed</span> <span class="p">+</span> <span class="m">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">damage</span> <span class="p">=</span> <span class="n">damage</span> <span class="p">/</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">attacker</span><span class="p">.</span><span class="n">weapon</span><span class="p">.</span><span class="n">worn</span> <span class="o">+=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">        <span class="n">target</span><span class="p">.</span><span class="n">health</span> <span class="o">-=</span> <span class="n">damage</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>Je cherche aussi à mieux structurer et à découper pour réduire la charge mentale nécessaire pour comprendre. Je parlais des <em>if</em> juste avant, j&rsquo;aime bien faire un <em>extract method</em> pour rendre la condition explicite. J&rsquo;utilise aussi le <em>extract method</em> dans le corps des grandes fonctions, ou dans les boucles. Le code est plus lisible, la fonction est plus courte, et j&rsquo;ai une indication sur son intention. Je gagne en compréhension.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">fun</span> <span class="nf">hit</span><span class="p">(</span><span class="n">attacker</span><span class="p">:</span> <span class="n">Player</span><span class="p">,</span> <span class="n">target</span><span class="p">:</span> <span class="n">Player</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span> <span class="o">==</span> <span class="k">null</span> <span class="o">||</span> <span class="n">target</span> <span class="o">==</span> <span class="k">null</span><span class="p">)</span> <span class="k">return</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">isArthurWithExcalibur</span><span class="p">(</span><span class="n">attacker</span><span class="p">))</span> <span class="p">{</span>    
</span></span><span class="line"><span class="cl">        <span class="n">attacker</span><span class="p">.</span><span class="n">kill</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">attacker</span><span class="p">.</span><span class="n">isLord</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">attacker</span><span class="p">.</span><span class="n">strength</span> <span class="o">+=</span> <span class="m">10</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">inflictDamage</span><span class="p">(</span><span class="n">attacker</span><span class="p">,</span> <span class="n">target</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="p">}</span>
</span></span></code></pre></div><p>Pour une restructuration encore plus poussée, il m&rsquo;arrive quelque fois de faire un <em>extract class</em> ou un <em>move method</em> afin d&rsquo;organiser davantage les fonctions et d&rsquo;améliorer la lisibilité. Par exemple, ici j&rsquo;aurais pu déplacer quelques méthodes ou créer des extensions sur la classe <em>Player</em>. Le design du code serait amélioré, mais ce ne serait pas forcément d&rsquo;une grande aide pour ma compréhension immédiate. Il faut savoir quand s&rsquo;arrêter.</p>
<p>Avec ce type de modifications, je suis passé d&rsquo;une zone très dense de 3000 lignes (difficile à mapper mentalement, avec beaucoup de choses superflux pour mon contexte) à quelques petites fonctions (bien mieux pour ma charge mental). J&rsquo;ai pu recentrer mon effort sur les morceaux qui m&rsquo;intéressaient le plus.</p>
<p>Bien sûr, il existe tout un tas d&rsquo;autres techniques de refactoring. Les sites <a href="https://refactoring.com" target="_blank" rel="noopener noreffer">refactoring.com</a> et <a href="https://refactoring.guru" target="_blank" rel="noopener noreffer">refactoring.guru</a> proposent un aperçu intéressant. Si vous voulez approfondir, je vous recommande de lire le livre <em>Improving the Design of Existing Code</em> de Martin Fowler ou <em>Working Effectively with Legacy Code</em> de Michael Feathers.</p>
<h2 id="prendre-des-libertés">Prendre des libertés</h2>
<p>Je ne conserve pas forcément mes changements. Parfois, mon objectif principal est le gain de compréhension. Le refactoring est un moyen pour y parvenir, ce n&rsquo;est pas la finalité. Il m&rsquo;arrive donc de prendre des libertés avec le code que j&rsquo;écris.</p>
<p>Je n&rsquo;essaie pas de faire le meilleur code possible, ni le meilleur design. Je m&rsquo;autorise par exemple à passer des paramètres en <em>inout</em> par simplicité. Créer des fonctions avec de nombreux paramètres ne me dérange pas non plus.</p>
<p>Je ne fais pas de tests unitaires garantissant que mes modifications ne changent pas le comportement. Encore une fois, mon but est de comprendre. Je ne compte pas garder ce code, donc le comportement ne sera pas altéré.</p>
<p>Ces prises de libertés me permettent d&rsquo;obtenir rapidement une meilleure compréhension globale d&rsquo;un morceau de code legacy. Le retour en arrière est facile avec Git. On efface tout ou on utilise une autre branche (permettant de faire des petits commits de compréhension).</p>
<h2 id="faire-attention">Faire attention</h2>
<p>Le refactoring est une discipline. Conserver le même comportement est primordiale. Même lorsque les modifications sont destinées à être effacées, il faut faire attention à ne pas faire une grosse erreur qui fausserait notre compréhension en allant trop vite.</p>
<p>Pour minimiser ce risque, j&rsquo;utilise au maximum des techniques de refactoring sûre, et je conserve les mêmes signatures pour avoir des vérifications au niveau de la compilation. Certains IDEs proposent également des actions de refactoring automatisées qui évitent des erreurs humaines.</p>
<p>Si vous prenez des libertés pour aller plus vite et que vous prévoyez d&rsquo;abandonner le code, abandonnez-le. Ne vous y attachez pas. Avec la compréhension que vous aurez acquises, votre second refactoring sera encore meilleur.</p>
<hr>
<h2 id="bref">Bref</h2>
<p>Je me sers du refactoring pour faire émerger de la compréhension. Pour rendre le code plus lisible, pour voir l&rsquo;intention. C&rsquo;est relativement rapide et extrêmement efficace. Je m&rsquo;approprie le code et c&rsquo;est un point de départ intéressant pour améliorer le code legacy.</p>
<p>Je fais également émerger des points de douleurs, ou des zones qui soulèvent de grandes questions. C&rsquo;est tout aussi important pour identifier d&rsquo;éventuels problèmes à venir et les partager au reste de l&rsquo;équipe.</p>
<p>La prochaine fois que vous tombez sur une zone difficile, essayez de faire un petit refactoring. C&rsquo;est à la portée de tous, sur tous les langages et plateformes. Le refactoring est une discipline connue, de même que les techniques utilisées. Vous avez juste à y penser, et à essayer.</p>
<p>Et vous, que faites-vous pour comprendre du code legacy ? Quelles sont vos techniques ?</p>
]]></description></item><item><title>Fixing thousands of SwiftLint violations over time</title><link>https://jordanchapuy.com/posts/2019/11/fixing-thousands-of-swiftlint-violations-over-time/</link><pubDate>Mon, 18 Nov 2019 09:00:00 +0200</pubDate><author>Auteur</author><guid>https://jordanchapuy.com/posts/2019/11/fixing-thousands-of-swiftlint-violations-over-time/</guid><description><![CDATA[<p>What would happen if SwiftLint were added to a project with a lot of existing code? You could have many violations, and much more when SwiftLint is deeply configured.</p>
<p>Having too many warnings can be discouraging, and there is also a bigger problem: <strong>too many warnings kill the warning</strong>. If you start to write code before resolving SwiftLint issues, you may miss some new important problems. So, let’s see how you can handle that.</p>
<figure>
</figure>

<hr>
<h1 id="quarantine-strategy-">Quarantine strategy ☣️</h1>
<p>Most of the violations can be fixed without problem, but there are many. Then there are more complex issues. Some violations will need refactoring.</p>
<p>You will need time to handle every warning, and you have to take time. Do not go fast, do it well. As you may not fix everything at once, you will have to do it progressively. But how? How can we take time and live with so many warnings?</p>
<p>Our strategy will be to isolate <em>infected</em> files and exclude them, then to fix violations and files over time. This strategy also plans to visualize progress and prevent regressions.</p>
<h2 id="autocorrect-is-our-magic-wand">Autocorrect is our magic wand</h2>
<p>Before you start the quarantine, you can run the <code>swiftlint autocorrect</code> command. Only a handful of rules can be automatically corrected, but it is pretty efficient and it can save you a lot of time and effort.</p>
<p>Autocorrecting is the easiest part. It is automatic, and it is mainly dealing with spaces and indentations. Be sure to check changes after running the command.</p>
<h2 id="exclude-infected-files">Exclude infected files</h2>
<p>More than a thousand of warnings on dozens of files. That’s what you can have. The goal of this step will be to fall to zero violation, as if there were nothing, but without fixing.</p>
<p>The idea of quarantine is to <strong>exclude all infected files from the analysis</strong>. An infected file is a file containing at least one violation. Only healthy files and new files will be linted.</p>
<p>In order to make the job easier, I wrote a script to list all infected files in a project. It will run SwiftLint, extract paths, and even format the output list.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">current_pwd</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>
</span></span><span class="line"><span class="cl"><span class="nv">escaped_pwd</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$current_pwd</span><span class="s2">&#34;</span> <span class="p">|</span> sed <span class="s1">&#39;s/\//\\\//g&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># run swiftlint, grep to filter only lines with .swift, sed to extract paths, sort to group paths, sed to remove first slash, sed to add</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Pods/SwiftLint/swiftlint <span class="p">|</span> grep .swift: <span class="p">|</span> sed <span class="s1">&#39;s/^\([^:]*\):.*/\1/&#39;</span> <span class="p">|</span> sort -u <span class="p">|</span> sed <span class="s2">&#34;s/</span><span class="nv">$escaped_pwd</span><span class="s2">//&#34;</span> <span class="p">|</span> sed <span class="s1">&#39;s/^\///&#39;</span> <span class="p">|</span> sed <span class="s1">&#39;s/\(.*\)/- \1/&#39;</span>
</span></span></code></pre></div><p>This script assumes that you did install SwiftLint in your project with cocoapods, and that you put the script in the root folder of your project. Feel free to change paths. The output of your terminal will look like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Text" data-lang="Text"><span class="line"><span class="cl">Loading configuration from &#39;.swiftlint.yml&#39;
</span></span><span class="line"><span class="cl">Linting Swift files at paths
</span></span><span class="line"><span class="cl">Linting &#39;ArticleListViewController.swift&#39; (1/464)
</span></span><span class="line"><span class="cl">Linting &#39;ArticleViewController.swift&#39; (2/464)
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">Linting &#39;Widget.swift&#39; (464/464)
</span></span><span class="line"><span class="cl">Done linting! Found 336 violations, 30 serious in 464 files.
</span></span><span class="line"><span class="cl">- MyApp/Article/ArticleListViewController.swift
</span></span><span class="line"><span class="cl">- MyApp/Article/ArticleCell.swift
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">- MyWidget/Widget.swift
</span></span></code></pre></div><p>The first part is printed by SwiftLint. It is telling you which files are analyzed. The second part is the list of infected files with the relative path. This list is formatted. You will just have to copy and paste the result in the configuration file <code>.swiftlint.yml</code> under `excluded keyword. You may have to indent it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">excluded</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="l">— Pods</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">MyApp/Article/ArticleListViewController.swift</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">MyApp/Article/ArticleCell.swift</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nn">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="l">MyWidget/Widget.swift</span><span class="w">
</span></span></span></code></pre></div><p>From now on, <strong>the number of warnings is zero and we are preventing problems on healthy files</strong>. A warning will appear as soon as a code will violate a rule on these files. And that’s because SwiftLint is still integrated, enabled and fully configured with the rules you choose.</p>
<p><strong>Only infected files at the quarantine creation are excluded</strong>. Their warnings are <em>hidden</em> and you will be able to handle them progressively. This step is performed only once, you may not have to exclude new files. We can imagine doing another quarantine if a change causes a lot of warnings (changing your rules or updating SwiftLint for example).</p>
<h2 id="treat-infected-files">Treat infected files</h2>
<p>Each time you have to edit an infected file, <strong>take the opportunity to correct few violations</strong>. Sometimes, you can also choose to pick a file and to treat all of its problems at once. Do not forget to remove a file from the excluded list when it becomes healthy.</p>
<p>Try to decide how you can take time to reduce warnings over time. Discuss about it with your team, with the other developers and also with your Product Owner or your manager. It can be for example two hours every Monday morning.</p>
<h2 id="visualize-your-progression">Visualize your progression</h2>
<p>When you have hundreds or thousands of violations, it is helpful to know where you are and how numbers are evolving. It will also motivate your team to decrease the total of warnings.</p>
<p>You can create a second SwiftLint configuration file without excluded files in order to lint everything. Give it a different name, and specify it when running the <code>swiftlint</code> command: <code>swiftlint --config .swiftlint-full.yml</code>.</p>
<p>Your basic configuration is used to show you warnings through Xcode, without files in quarantine, and your CI or an other tool can execute SwiftLint with the second configuration in order to have a global vision. This way, you can <strong>make charts or have the current count to follow your progression</strong>.</p>
<figure><figcaption>
            <p><em>SwiftLint warnings’ eradication through time, on Jenkins.</em></p>
        </figcaption>
</figure>

<p>In a <a href="https://jordanchapuy.com/posts/2019/10/unleash-the-power-of-swiftlint/" rel="">dedicated article</a> about SwiftLint, I wrote a part about the power of visualization. You can find how to easily have charts with Jenkins, or with a sheet of paper if you do not have any CI.</p>
<h2 id="forbid-regression">Forbid regression</h2>
<p>While you are healing infected files, and even when the whole project is healthy, you should prevent new violations. Do not add new errors when you are writing code.</p>
<p>For that, set up your CI to <strong>fail a build if there is new lint errors</strong>. You may otherwise want to take a look at pre-commit git hooks, to forbid a commit containing violations, or at <a href="https://github.com/danger/swift" target="_blank" rel="noopener noreffer">Danger</a> to have automatic messages on your pull requests.</p>
<p>It is also helpful to use a second SwiftLint configuration here, linting infected files, to see hidden warnings. Remember that the quarantine disable analysis on infected file. So you need something to warn you if you are adding new violations inside these files.</p>
<hr>
<h1 id="alternative-ideas-">Alternative ideas 🔀</h1>
<h2 id="disabling-rules">Disabling rules</h2>
<p>The principle of this quarantine strategy is to exclude from analysis each files containing at least one warning or error. Another possibility is to disable every of your rules in your configuration file.</p>
<p>This way, you will end up with zero violation. You can then re-enable a rule and fix corresponding warnings. One rule by one rule. It will give you the flexibility to correct problems progressively. The counterpart is that you will disable rules for every files, even healthy files or new ones.</p>
<h2 id="nested-configurations">Nested configurations</h2>
<p>You can also profit nested configurations with SwiftLint. You can put different configurations in subfolders. Thanks to that, you can choose to specifically enable or disable rules in your project hierarchy.</p>
<p>We can even imagine combining both approaches: putting some files in quarantine, and disabling certain rules on specific subfolders.</p>
<hr>
<h2 id="make-your-code-consistent-and-go-further">Make your code consistent, and go further</h2>
<p>If you want to know more about code consistency and how to unleash the power of SwitLint, you can read two of my previous articles:</p>
<ul>
<li><a href="https://jordanchapuy.com/posts/2019/10/make-your-code-consistent/" rel="">Make your code consistent</a></li>
<li><a href="https://jordanchapuy.com/posts/2019/10/unleash-the-power-of-swiftlint/" rel="">Unleash the power of SwiftLint</a></li>
</ul>
<hr>
<h1 id="and-thats-all-for-now-">And that’s all for now 👋</h1>
<p>Thanks for reading. Feel free to make any feedback and to open discussion. Let’s finish with you. How are you using SwiftLint? Do you have any tips to share when you have too much warnings? What did you learn?</p>
]]></description></item></channel></rss>