Si vous continuez à vous tirer une balle dans le pied, réparez l’arme
- Dans une équipe, il existe souvent des erreurs récurrentes liées au système, sans que l’on réfléchisse vraiment à des moyens de les réduire
- Dans ce cas, il est important d’améliorer le système pour diminuer ces erreurs
- Expérience :
- Lors du développement iOS avec CoreData, les mises à jour de l’UI ne peuvent se faire que sur le thread principal
- Les callbacks d’abonnement se déclenchaient à la fois sur le thread principal et en arrière-plan, ce qui causait souvent des problèmes
- Les membres historiques de l’équipe le savaient et le géraient bien, mais cela revenait souvent dans les reviews des nouveaux
- Quand une erreur survenait, on regardait le rapport de crash et on ajoutait
DispatchQueue.main.async
- Pour corriger cela, la couche d’abonnement a été modifiée afin d’appeler les abonnés sur le thread principal. Cela a pris exactement 10 minutes.
- Toute une classe de crashes et un peu de charge mentale ont été éliminés
- C’était un problème qui aurait semblé évident à n’importe qui après quelques minutes de réflexion
- Mais comme il n’y a pas de moment naturel pour le résoudre, ce genre de problème dure étrangement longtemps
- Autrement dit, plus on reste longtemps dans l’équipe, plus ce type de problème a tendance à se fondre dans le décor
- Il faut changer d’état d’esprit
- Il faut parfois se rappeler qu’on peut rendre sa propre vie, et celle de l’équipe, plus facile face à ce type de problème
Trouver l’équilibre entre qualité et vitesse
- Il y a toujours un arbitrage entre la vitesse d’implémentation et la confiance dans l’exactitude
- Il faut se demander à quel point il est acceptable de livrer des bugs dans la situation actuelle
- Si la réponse à cette question ne change pas votre manière de travailler, c’est que vous êtes trop rigide
- Dans mon premier poste, je travaillais sur des projets de traitement de données avec un bon système permettant de retraiter les données rétroactivement
- L’impact d’un bug en production était très limité et, dans un tel environnement, on peut s’appuyer davantage sur les garde-fous et avancer plus vite
- Une couverture de tests à 100 % ou un processus QA très lourd ne ferait que ralentir le développement
- Dans mon deuxième poste, on gérait des données financières à forte valeur et des informations personnelles identifiables dans un produit utilisé par des dizaines de millions de personnes, donc les bugs pouvaient être catastrophiques
- Même un petit bug nécessitait une analyse post-mortem
- On livrait les fonctionnalités très lentement, mais je pense qu’on a terminé l’année avec 0 bug en production
- Dans la plupart des cas, on n’est pas dans une situation comme celle de la deuxième entreprise
- Quand les bugs ne sont pas catastrophiques (par exemple dans 99 % des web apps), il vaut mieux livrer vite et corriger vite
- On progresse plus ainsi qu’en passant trop de temps à sortir une fonctionnalité parfaite dès le départ
Prendre le temps d’aiguiser sa scie vaut presque toujours le coup
- Il est important de bien maîtriser ses outils
- Il faut pouvoir écrire du code rapidement, connaître les principaux raccourcis et être à l’aise avec son OS et son shell
- Vous allez très souvent renommer, naviguer vers une définition de type, rechercher des références, etc.
- Il faut connaître tous les raccourcis importants de l’éditeur et taper vite avec assurance
- Il est aussi important de savoir bien utiliser les outils de développement du navigateur
- Bien choisir ses outils et les utiliser avec maîtrise est un énorme avantage
- L’un des plus grands signaux positifs chez un nouvel ingénieur est l’intérêt porté au choix des outils et à leur usage compétent
Si vous n’arrivez pas à expliquer simplement une difficulté, c’est probablement de la complexité accidentelle, et ce problème mérite d’être résolu
- Mon manager préféré avait l’habitude de continuer à pousser chaque fois que j’affirmais qu’une implémentation était difficile
- Sa réponse était souvent du type : « Au fond, ce n’est pas juste envoyer X quand on fait Y ? » ou « Ce n’est pas comme Z qu’on a fait il y a quelques mois ? »
- C’étaient des objections très haut niveau, pas au niveau réel des fonctions et des classes que j’essayais d’expliquer
- L’opinion habituelle est qu’un manager qui simplifie les choses de cette manière est simplement agaçant
- Pourtant, de façon surprenante, très souvent, quand il continuait à poser des questions, je réalisais que la majeure partie de la complexité que je décrivais était de la complexité accidentelle
- Et qu’en la résolvant d’abord, on pouvait réellement rendre le problème aussi trivial qu’il le disait
- Ce type d’approche tend aussi à faciliter les changements à venir
Essayez de corriger les bugs à un niveau plus profond
- Au lieu de corriger un bug en surface, il est important d’identifier et de résoudre sa cause racine
- Supposons un composant React de dashboard qui manipule un objet
User issu de l’état de l’utilisateur actuellement connecté
- Un bug report Sentry indique que
user valait null pendant le rendu
- On peut rapidement ajouter
if (!user) return null
- Ou bien, en creusant un peu, on découvre que la fonction de déconnexion effectue deux mises à jour d’état distinctes
- La première met l’utilisateur à
null, la seconde redirige vers la page d’accueil
- En inversant l’ordre des deux, plus aucun composant ne rencontrera ce bug
- Car, dans le dashboard, l’objet utilisateur ne devrait jamais être
null
- Si vous continuez à faire le premier type de correction, vous allez accumuler du désordre,
mais si vous continuez à faire le second type de correction, vous obtiendrez un système propre et une compréhension profonde des invariants
Ne sous-estimez pas la valeur de fouiller l’historique pour enquêter sur un bug
- J’étais déjà assez bon pour déboguer des problèmes étranges avec les outils classiques comme
println et le débogueur
- Donc je ne regardais pas beaucoup git pour comprendre l’historique d’un bug, mais pour certains bugs c’est pourtant crucial
- Récemment, un serveur semblait avoir une fuite mémoire persistante, se faisait tuer par OOM, puis redémarrait
- Toutes les causes plausibles avaient été écartées, et il était impossible de reproduire le problème en local
- J’avais l’impression de lancer des fléchettes les yeux bandés
- En consultant l’historique des commits, j’ai vu que cela avait commencé après l’ajout du support des paiements Play Store
- Je n’aurais jamais pensé regarder là, car cela ne paraissait être que quelques requêtes HTTP
- Il s’est avéré qu’après l’expiration du premier access token, le code entrait dans une boucle infinie pour en récupérer un nouveau
- Chaque requête n’ajoutait peut-être qu’environ 1 kB en mémoire, mais avec des retries toutes les 10 ms sur plusieurs threads, cela s’accumulait vite
- Normalement, cela aurait provoqué un stack overflow, mais comme on utilisait de la récursivité asynchrone en Rust, il n’y a pas eu de stack overflow
- Je n’y aurais jamais pensé, mais le fait d’examiner un morceau de code clairement lié au problème a soudainement fait émerger la théorie
- Il n’existe pas de règle pour savoir quand utiliser cette approche
- C’est en partie de l’intuition : une autre forme de « hein ? » face à un bug report peut déclencher ce type d’enquête
- On peut développer cette intuition avec le temps, mais savoir que cette approche peut parfois avoir énormément de valeur suffit déjà
- Quand le problème s’y prête, essayez
git bisect
- Si vous avez un commit que vous savez mauvais et un autre que vous savez bon
Le mauvais code donne du feedback, le code parfait n’en donne pas. Mieux vaut se tromper en écrivant du mauvais code
- Écrire un code terrible est vraiment facile
- Mais écrire un code qui suit absolument toutes les bonnes pratiques l’est aussi
- Il faudrait passer par des tests unitaires, d’intégration, de fuzzing, de mutation, et une startup aura épuisé son cash avant cela
- Une grande partie de la programmation consiste à trouver le bon équilibre
- Si vous vous trompez du côté du code écrit rapidement...
- Vous vous mettrez parfois dans l’embarras à cause d’une mauvaise dette technique
- Vous apprendrez qu’« il faut absolument ajouter de bons tests au traitement de données »
- Parce que corriger plus tard est souvent impossible
- Vous apprendrez aussi qu’« il faut vraiment bien réfléchir à la conception des tables »
- Parce qu’un changement sans downtime peut être très difficile
- Si vous vous trompez du côté du code parfait...
- Vous n’obtiendrez aucun feedback
- Tout prendra universellement plus de temps
- Vous ne saurez pas où vous investissez bien votre temps et où vous le gaspillez
- Les mécanismes de feedback sont essentiels à l’apprentissage, et vous vous en privez
- Clarification sur ce que signifie un code « mauvais »
- Cela ne veut pas dire : « Je ne me souvenais plus de la syntaxe pour créer une hashmap, donc j’ai fait deux boucles imbriquées »
- Cela veut dire, par exemple :
- Au lieu de réécrire la collecte de données pour rendre certains états impossibles à représenter, on ajoute quelques assertions d’invariants à des points de contrôle clés
- Le modèle serveur est exactement identique au DTO à écrire, donc on le sérialise tel quel. On pourra toujours écrire un DTO plus tard si le besoin apparaît, au lieu de produire tout le boilerplate dès maintenant
- Ces composants sont triviaux et même s’ils contiennent un bug, ce ne sera pas grave, donc on saute l’écriture des tests
Facilitez le débogage
- Au fil des années, j’ai accumulé plein de petites astuces pour rendre un logiciel plus facile à déboguer
- Si vous ne faites aucun effort en ce sens, chaque problème finira par vous coûter énormément de temps à mesure que le logiciel devient plus complexe
- Vous finirez par avoir peur de faire des changements, parce qu’il pourrait vous falloir une semaine pour comprendre quelques nouveaux bugs
- Faites attention au temps passé, pendant le débogage, à la configuration, à la reproduction et au nettoyage après coup
- Si cela dépasse 50 %, il faut trouver un moyen de rendre les choses plus faciles, même si cela prend un peu plus de temps cette fois-ci
- Toutes choses égales par ailleurs, corriger des bugs devrait devenir plus facile avec le temps
Quand vous travaillez en équipe, posez toujours des questions
- Il existe un spectre allant de « essayer de tout comprendre seul » à « déranger ses collègues avec des questions triviales »
- Je pense que la plupart des personnes en début de carrière penchent beaucoup trop vers le premier extrême
- Il y a toujours autour de vous quelqu’un qui connaît mieux la codebase, maîtrise bien mieux la techno X, connaît mieux le produit, ou a simplement plus d’expérience
- Pendant les six premiers mois dans un nouveau contexte, on passe souvent plus d’une heure à essayer de comprendre quelque chose alors qu’on pourrait avoir la réponse en quelques minutes
- Posez des questions. Le seul cas où cela agace vraiment quelqu’un, c’est quand il est évident que vous auriez pu trouver la réponse vous-même en quelques minutes
Le cycle de déploiement est extrêmement important. Réfléchissez sérieusement à la manière de déployer vite et souvent
- Une startup a une runway limitée, et les projets ont des deadlines
- Quand on quitte son emploi pour devenir indépendant, ses économies ne durent que quelques mois
- Idéalement, la vitesse d’un projet augmente de façon composée avec le temps, au point de permettre de livrer des fonctionnalités plus vite qu’on ne l’aurait imaginé
- Déployer vite exige beaucoup de choses
- Un système qui ne soit pas fragile face aux bugs
- Des temps de turnaround rapides entre équipes
- La volonté de couper 10 % d’une nouvelle fonctionnalité (la partie qui prendrait 50 % du temps d’ingénierie) et la lucidité nécessaire pour identifier cette partie
- Des patterns cohérents et réutilisables, combinables pour créer de nouveaux écrans/fonctionnalités/endpoints
- Des déploiements rapides et simples
- Des processus qui ne ralentissent pas (tests instables, CI lente, linter pénible, reviews de PR lentes, JIRA traité comme une religion, etc.)
- Et des millions d’autres choses
- Livrer lentement devrait faire l’objet d’une analyse post-mortem au même titre qu’une panne en production
- Notre industrie ne fonctionne pas ainsi, mais cela ne veut pas dire qu’on ne peut pas, à titre personnel, garder comme étoile polaire l’objectif d’un déploiement rapide
6 commentaires
"Se tirer une balle dans le pied" = ça signifie se retrouver piégé par ses propres actes ?
Si un problème survient à cause d’un code défectueux (un pistolet cassé) (se tirer une balle dans le pied), cela veut dire qu’il faut réparer le pistolet.
Le choc, on dirait qu’il a sorti exactement ce qu’il y a dans ma tête, c’est fou..
Bonne lecture !!
Je l’ai bien lu.
Je ne suis pas développeur, mais il y a beaucoup de points auxquels je m’identifie.