[2/3] 3 acronymes que TOUS les développeurs devraient connaître : KISS
Comment écrire du code simple et pérenne au sein d'une équipe ?
Keep It Stupid Simple : gardez votre code le plus simple possible ! Voilà pour l’acronyme KISS.
Merci, à la semaine prochaine pour le prochain acronyme !
Mais non ne partez pas ! C’est évidemment bien plus complexe qu’il n’y paraît. Comment faire pour écrire du code simple ? Et qu’est-ce que ça veut dire “simple” d’ailleurs ?
Accrochez vos ceintures, car nous allons toucher ici au cœur même du métier de développeurs : gérer la complexité !
“Sous le masque de la complexité, la simplicité se questionne.” - Monique Keurentjes
Vous ne connaissez pas Monique Keurentjes ? Moi non plus. Mais cette citation sert tout à fait mon propos : la simplicité émerge de notre compréhension de la complexité et la manière dont nous la gérons.
Commençons donc par aborder les deux principaux types de complexités qui existent dans un projet.
Les deux types de complexités à gérer
La complexité métier
Cette complexité est celle inhérente au domaine dans lequel vous évoluez.
Elle concerne donc les différentes règles de gestion propres aux problématiques métiers que votre logiciel est censé régler.
Vous bossez dans une compagnie aérienne et devez faire un logiciel pour simplifier la gestion des bagages ? Il existe probablement plein de règles à respecter en fonction du poids des bagages, du nombre de passagers, du temps de vol, etc.
Votre projet concerne un service public ? Il est probable que vous deviez vous plier à l’application de quelques lois bien farfelues.
Bref, vous l’aurez compris, cette complexité fait partie intégrante du métier et c’est la raison même pour laquelle vous devez développer un logiciel / une application.
Est-il possible de limiter cette complexité ?
Gérer la complexité métier
Lorsqu’un produit est développé, il faut garder en tête que ce qui est mis en production, et donc utilisé par les utilisateurs finaux, c’est la compréhension qu’ont eu les développeurs de la complexité métier, des besoins.
Ces besoins sont généralement exprimés à travers des user stories dans les méthodes agiles. Afin de les rendre le plus compréhensible possible par tous il est extrêmement important d’impliquer aussi les développeurs dans leur rédaction.
Ce faisant, la connaissance des problématiques métiers est partagée entre toute l’équipe. Cela limite les effets de téléphone arabe que l’on peut observer dans les entreprises fonctionnant en silos. À chaque fois qu’une information passe d’un silo à un autre, comme des Business Analysts aux Products Owners, puis des Products Owners aux développeurs par exemple, on prend le risque de transformer et/ou perdre des informations.
Que les développeurs comprennent précisément ces problématiques est donc primordial, et cela est d’autant plus efficace si les stories ainsi créées sont courtes : déployables en production en 1 ou 2 jours, idéalement.
Réduire les user stories à un périmètre déployable en 1 ou 2 jours est un excellent moyen de prôner la simplicité en réduisant la complexité. Encore faut-il ne pas tomber dans le piège de la complexité que l’on se crée nous-même en codant : la complexité accidentelle.
La complexité accidentelle
Cette complexité est liée au code en lui-même.
“Ah relou…Y’a toujours 40000 fichiers à ouvrir dès qu’on doit ajouter la moindre petite fonctionnalité”
“À chaque fois que je dois modifier ou ajouter une fonctionnalité, je ne sais jamais dans quel fichier chercher…”
“Qu’est-ce que c’est que ce code ? Pourquoi ça n’a pas été fait comme d’habitude ?”
Je suis sûr que vous avez déjà entendu quelques-unes de ces remarques sinon toutes, et probablement d’autres du même genre.
Ces réflexions révèlent toutes le même mal : le code est trop complexe pour le développeur qui le lit.
Est-ce que cette complexité est liée aux problématiques que notre logiciel est censé résoudre ? Absolument pas ! C’est de la complexité créée par le code. C’est ce que l’on appelle la complexité accidentelle.
On dit que l’Enfer est pavé de bonnes intentions, voyons comment cette complexité accidentelle peut apparaître suite à ce qui semblait pourtant être de bonnes intentions :
Vouloir réduire les duplications, mais créer de mauvaises abstractions
Si vous avez lu mon article de la semaine dernière sur le principe DRY, vous devez vous souvenir qu’une abstraction à un coût. Elle crée un niveau d’indirection et une mauvaise abstraction ajoute de la charge mentale inutile.
Lorsqu’on lit le code, on doit systématiquement se référer à l’implémentation pour comprendre ce que fait cette abstraction : de la complexité accidentelle est créée.
Vouloir rendre le code modulable, mais se retrouver avec trop de couches
Parfois, dès le début d’un projet, on souhaite le rendre le plus modulable possible. Ce faisant, on crée alors plusieurs couches d’abstractions plutôt que de tout faire au même endroit.
Mal appliqué, ce principe peut résulter en ce que l’on appelle un code en “lasagnes” (on aime bien les pâtes dans le dev’) : trop de couches. Que ce soit pour trouver où modifier le moindre petit détail d’une fonctionnalité ou pour en ajouter une nouvelle, il faut ouvrir énormément de fichiers. On dit alors que le code manque de cohésion.
C’est l’une des causes principales de génération de complexité accidentelles.
Vouloir écrire du “beau” code bien “léché” et “propre”, mais se rendre compte que les collègues ne peuvent plus le lire
C’est je pense la complexité accidentelle la plus sujette à débat et qui génère le plus de frustration.
Lorsqu’un développeur est convaincu de bien faire en appliquant tel ou tel design pattern, ou en faisant tel ou tel refactoring, mais que ses collègues s’empressent de dire pendant la code review :
“Oulalala, c’est compliqué pour rien je trouve non ?”
…c’est un signe que de la complexité accidentelle a été créée.
Est-ce que ces patterns avaient réellement un intérêt ? Est-ce que ces refactoring améliorait le code ? Peut-être, mais à moins que vous ne travailliez seul, vous ne devez jamais oublier que votre code doit être lu, compris et modifiable par vos collègues.
Donc même si vous appliquez ce qui est reconnu depuis 30 ans comme étant les bonnes pratiques, si vos collègues ne les connaissent pas alors vous ne ferez que les ralentir, pénalisant ainsi le projet ! Il va donc falloir faire quelques compromis…
Garder un code simple pour l’équipe sans sacrifier la qualité
Qu’appelle-ton un code “simple” ?
Demandez à vos collègues. Vous pourriez avoir ce genre de réponses :
“Je dois pouvoir comprendre facilement ce que fait le code en le lisant”
“Je dois pouvoir ajouter rapidement une nouvelle fonctionnalité”
“Quand j’ai du code à écrire, je sais exactement où je dois l’écrire, sans avoir à chercher pendant de longues minutes si je dois plutôt le mettre dans le fichier A plutôt que le fichier B”
“Le code doit être simple à débugger”
etc…
Après tout, que pourraient-ils dire d’autre ? Je suis sûr que, vous aussi, vous êtes d’accord avec ça.
Plongeons-nous maintenant dans le cerveau de ces différents collègues. À l’aune de leur propre expérience et de leurs habitudes, ces quelques phrases peuvent se traduire très différemment :
“Pour moi, comprendre facilement un code en le lisant signifie que je n’ai pas besoin d’ouvrir 5 fichiers pour comprendre ce que va faire telle ou telle fonction”
“Pour moi, il s’agit avant tout de voir apparaître les concepts. Il faut donc que ceux-ci soient correctement abstraits pour ne pas avoir à se soucier des détails d’implémentation”
“Pour ajouter une nouvelle fonctionnalité de façon simple, pour moi ça veut dire avoir juste besoin de copier-coller quelques bout de code par-ci par-là qui font déjà le taf, en modifiant légèrement ce que j’ai besoin de modifier”
“Selon moi, la façon la plus simple d’ajouter une fonctionnalité c’est d’utiliser le polymorphisme pour ne pas modifier du code existant”
“Quand je dois écrire du code, je sais exactement dans quel fichier l’écrire. Notre arborescence de fichier est très simple, et du coup une fonctionnalité est toujours implémentée de A à Z dans le même fichier, depuis le controller jusqu’à la base de données !”
“Quand je dois écrire du code, je sais exactement dans quel fichier l’écrire car en fonction de sa nature il trouve sa place dans une couche différente de l’application. L’architecture hexagonale m’apporte donc la sérénité de savoir exatement où placer tel ou tel fichier.”
“Un code simple à débugger c’est selon moi un code où l’on sait tout de suite à quel endroit chercher le bug, et que l’on sait exactement comment le résoudre car on a plusieurs fois rencontré ce genre de bugs par le passé”
“Un code simple à débugger pour moi c’est un code qui n’a pas besoin d’être souvent débuggé. Des méthodologies comme le TDD aident énormément.”
Qu’observe-t-on avec ce petit exemple ? Que la notion de simplicité est subjective.
Le “Framework Mental”
Chaque développeur a en effet un “schéma de pensée”, un “framework mental” dans lequel il évolue pour faciliter ses choix, ses décisions, et qui s’est dessiné au fil de ses expériences. Lorsque le code rentre dans son “framework mental”, il en devient du code “simple”.
Il est donc impératif de créer un “framework mental” commun à l’équipe, qui impliquera donc une certaine souplesse mentale pour tordre quelque peu ses principes personnels si cela va à l’encontre de la vision de la “simplicité” de l’équipe.
Mais attention ! Jamais aux détriments de la qualité ! Vouloir appliquer des méthodes simples à une problématique complexe va s’avérer totalement contre-productif puisqu’en retour cela générera énormément de dette technique, et donc de complexité accidentelle !
Voilà pourquoi appliquer le principe KISS est un processus itératif. Quelques petits conseils :
commencer par l’implémentation la plus simple possible
faire des pull/merge requests les plus petites possibles pour avoir des retours des autres développeurs de l’équipe rapidement
“écouter” son code : lorsqu’il devient pénible de modifier ou ajouter une fonctionnalité ou qu’il y a trop de bugs, c’est le temps de faire un refactoring pour éviter de générer trop de complexité accidentelle
lorsqu’un développeur de l’équipe propose quelque chose de nouveau, discuter ensemble des avantages et inconvénient pour voir si cette pratique peut rentrer dans le “framework” mental de l’équipe
si un développeur propose d’anticiper un futur problème, mais que cela paraît trop compliqué pour le reste de l’équipe, le noter quelque part pour appliquer tout de suite ce changement si le problème survient effectivement plus tard.
C’est la quête de toute une vie de développeur que de garder un code “KISS” ! Et surtout un code qui vous parait simple aujourd’hui était probablement le code qui vous paraissait compliqué il y a quelques années, car votre “framework” mental a évolué en même temps que vous avez gagné en expérience.
Happy Coding !