
Depuis début 2018, j'occupe le poste de leader/chef/developer principal dans l'équipe - appelez cela comme vous voulez, mais l'essentiel est que je suis entièrement responsable d'un des modules et de tous les développeurs qui y travaillent. Ce poste m'offre une nouvelle perspective sur le processus de développement, car je suis impliqué dans un plus grand nombre de projets et participe plus activement à la prise de décisions. Récemment, grâce à ces deux circonstances, j'ai réalisé à quel point le niveau de compréhension influence le code et l'application.
La pensée que je souhaite exprimer est que la qualité du code (et du produit final) est étroitement liée à la mesure dans laquelle les personnes qui conçoivent et écrivent le code comprennent ce qu'elles font.
Vous pensez peut-être maintenant : « Merci, capitaine. Bien sûr, il serait bon de comprendre ce que l'on écrit. Sinon, on pourrait aussi bien engager un groupe de singes pour taper aléatoirement sur des touches, et se satisfaire de cela. » Et vous avez tout à fait raison. Par conséquent, je suppose comme acquis que vous comprenez qu'avoir une vue d'ensemble de ce que vous faites est nécessaire. Cela peut être qualifié de niveau zéro de compréhension, et nous ne l'aborderons pas en détail. Nous examinerons en détail ce qu'il est nécessaire de comprendre et comment cela impacte les décisions que vous prenez chaque jour. Si j'avais su ces choses à l'avance, cela m'aurait évité de nombreuses heures perdues et du code douteux.
Bien que vous ne voyiez ici aucune ligne de code, je pense néanmoins que tout ce qui a été dit ici revêt une grande importance pour écrire un code de qualité et expressif.
Premier niveau de compréhension : Pourquoi ça ne fonctionne pas ?
Les développeurs atteignent généralement ce niveau au début de leur carrière, parfois même sans aide extérieure - du moins, c'est ce que j'ai observé. Imaginez que vous recevez un rapport de bug : une certaine fonction dans l'application ne fonctionne pas, il faut la réparer. Comment allez-vous agir ?
Le schéma standard ressemble à ceci :
- Trouver le morceau de code qui pose problème (comment cela se fait - c'est un sujet distinct, que j'aborde dans mon livre sur le code obsolète)
- Apporter des modifications à ce morceau
- S'assurer que le bug est corrigé et qu'aucune erreur de régression n'est survenue
Concentrons-nous maintenant sur le deuxième point : les modifications du code. Il existe deux approches pour ce processus. La première consiste à comprendre exactement ce qui se passe dans le code actuel, identifier le bug et le corriger. La seconde est plus expérimentale : ajouter, par exemple, +1 dans une instruction conditionnelle ou une boucle, vérifier si cette fonction fonctionne dans le scénario souhaité, puis essayer autre chose et ainsi de suite indéfiniment.
La première approche est la correcte. Comme l'explique Steve McConnell dans son livre Code Complete (que je recommande d'ailleurs vivement), chaque fois que nous modifions le code, nous devons être capables de prédire avec confiance comment cela affectera l'application. Je cite de mémoire, mais si le correctif ne fonctionne pas comme prévu, cela devrait beaucoup vous alerter et vous devriez remettre en question l'ensemble de votre plan d'action.
Pour résumer, afin de réaliser un bon bugfix qui n'altère pas la qualité du code, il est essentiel de comprendre toute la structure du code ainsi que la source du problème spécifique.
Deuxième niveau de compréhension : Pourquoi cela fonctionne-t-il ?
Ce niveau est beaucoup moins intuitif que le précédent. En tant que développeur débutant, je l'ai compris grâce à mon supérieur, et par la suite, j'ai souvent expliqué ce principe à des novices.
Cette fois, imaginons que vous recevez deux rapports de bugs simultanément : le premier concerne le scénario A, le second le scénario B. Dans les deux cas, quelque chose ne va pas. Vous commencez donc par le premier bug. En vous basant sur les principes que nous avons établis pour le premier niveau de compréhension, vous vous plonger dans le code lié au problème, découvrez pourquoi il fait agir l'application de cette manière dans le scénario A, et y apportez des modifications raisonnables qui donnent exactement le résultat attendu. Tout se passe bien.
Ensuite, vous passez au scénario B. Vous répétez le scénario en essayant de provoquer l'erreur, mais — surprise ! — tout fonctionne maintenant comme prévu. Pour confirmer votre hypothèse, vous annulez les modifications apportées lors du traitement du bug A, et le bug B réapparaît. Votre correctif a résolu les deux problèmes. Quelle chance !
Vous ne vous y attendiez pas du tout. Vous avez trouvé un moyen de corriger l'erreur du scénario A et vous ne savez pas pourquoi cela a fonctionné pour le scénario B. À ce stade, il y a une forte tentation de penser que les deux tâches ont été accomplies avec succès. C'est logique : l'idée était de corriger les erreurs, n'est-ce pas ? Mais le travail n'est pas encore terminé : vous devez encore comprendre pourquoi vos actions ont corrigé l'erreur dans le scénario B. Pourquoi ? Parce qu'il fonctionne peut-être sur des principes incorrects, et vous devrez alors chercher une autre solution. Voici quelques exemples de tels cas :
- Étant donné que la solution n'a pas été adaptée précisément à l'erreur B en tenant compte de tous les facteurs, vous avez peut-être, sans le savoir, cassé la fonction C.
- Il est possible qu'un troisième bogue soit également caché quelque part, lié à cette même fonction, et que votre correctif lie le bon fonctionnement du système dans le scénario B à ce dernier. Pour l'instant, tout semble en ordre, mais un jour, ce troisième bogue sera remarqué et corrigé. Alors, l'erreur réapparaîtra dans le scénario B, espérons-le seulement là.
Tout cela apporte du chaos dans le code et finira par vous tomber dessus — probablement au pire moment. Vous devrez rassembler votre courage pour vous forcer à prendre le temps de comprendre pourquoi tout fonctionne en apparence, mais cela en vaut la peine.
Troisième niveau de compréhension : Pourquoi ça fonctionne ?
Ma récente révélation est justement liée à ce niveau, et c'est probablement celui qui m'apporterait le plus d'avantages si j'y avais pensé plus tôt.
Pour clarifier, prenons un exemple : votre module doit être compatible avec la fonction X. Vous n'êtes pas particulièrement familier avec la fonction X, mais on vous a dit que pour être compatible avec elle, vous devez utiliser le framework F. D'autres modules qui s'intègrent à X fonctionnent justement avec celui-ci.
Votre code n'a jamais été en contact avec le framework F depuis le premier jour de sa création, donc son intégration ne sera pas si simple. Cela aura de sérieuses conséquences sur certaines parties du module. Néanmoins, vous vous investissez à fond dans le développement : vous passez des semaines à écrire du code, à le tester, à déployer des versions pilotes, à obtenir des retours, à corriger des erreurs de régression, à découvrir des complications inattendues, à dépasser les délais initialement fixés, à écrire encore du code, à tester, à recevoir des retours, à corriger des erreurs de régression — tout cela pour intégrer le framework F.
Et à un moment donné, vous réalisez soudainement — ou peut-être entendez-vous de quelqu'un — que le framework F ne vous permettra peut-être pas d'avoir la compatibilité avec la fonction X. Peut-être que tout ce temps et tous ces efforts ont été consacrés à quelque chose de totalement inutile.
Quelque chose de semblable m'est arrivé lors d'un projet dont j'étais responsable. Pourquoi cela s'est-il produit ? Parce que je comprenais mal la nature de la fonction X et son lien avec le framework F. Que devrais-je faire ? Demander à la personne qui émet la demande de développement d'expliquer clairement comment le plan d'action proposé aboutit au résultat souhaité, au lieu de simplement répéter ce qui avait été fait pour d'autres modules ou de croire sur parole que c'était nécessaire pour le fonctionnement de la fonction X.
L'expérience de ce projet m'a appris à refuser de commencer le processus de développement tant que nous n'avons pas une compréhension claire des raisons pour lesquelles nous sommes invités à effectuer certaines actions. Refuser de manière franche. Lorsque vous recevez une tâche, le premier réflexe est de s'y atteler immédiatement pour ne pas perdre de temps. Mais la politique de « nous gelons le projet jusqu'à ce que nous ayons compris tous les détails » peut réduire considérablement le temps perdu.
Même si l'on essaie de vous mettre la pression pour commencer à travailler alors que vous ne comprenez pas la raison, résistez. D'abord, comprenez les objectifs sous-tendant la tâche, et déterminez si ce chemin est le bon vers cet objectif. J'ai dû apprendre cela à mes dépens — j'espère que mon expérience pourra faciliter la vie de ceux qui lisent cela.
Quatrième niveau de compréhension : ???
En programmation, il y a toujours quelque chose à apprendre, et je pense que je n'ai effleuré que la surface de compréhension. Quels autres niveaux de compréhension avez-vous découverts au fil des années passées à coder ? Quelles décisions ont eu un impact positif sur la qualité du code et de l'application ? Quelles décisions se sont révélées erronées et vous ont enseigné une leçon précieuse ? Partagez vos expériences dans les commentaires.
Source : habr.com
