CASEY : Merci [@unclebobmartin] (https://twitter.com/unclebobmartin) d'avoir pris le temps de répondre à ces questions ! Nous pouvons peut-être commencer par quelques éclaircissements.
La plupart des explications sur le Clean Code que j'ai vues de votre part incluent toutes les choses que j'ai mentionnées dans la vidéo - préférer les hiérarchies d'héritage aux instructions if/switch, ne pas exposer les éléments internes (la " loi de Demeter "), etc. Mais on dirait que vous avez été surpris de m'entendre dire cela. Pourriez-vous prendre une minute avant que nous ne commencions pour expliquer plus en détail vos idées sur la conception des types, afin que je puisse me faire une idée de l'endroit où se situe la déconnexion ?
BOB : Le décalage. Hmmm. Je ne suis pas sûr qu'il y en ait un.
J'ai regardé la première moitié de votre vidéo. Après cela, je me suis dit que j'avais compris la dérive. J'ai répondu dans un fil de discussion que je pensais que votre analyse était essentiellement correcte. Je pensais également que votre rhétorique était un peu inexacte en ce qui concerne la représentation du "code propre". Je ne me souviens pas exactement de la nature de cette inexactitude, et cela n'a pas vraiment d'importance.
Donc.... Oui, absolument, les structures que vous avez présentées ne sont pas le meilleur moyen de tirer chaque nanoseconde de performance d'un système. En effet, l'utilisation de ces structures peut vous coûter beaucoup de nanosecondes. Elles ne sont pas efficaces au niveau de la nanoseconde. Il y a longtemps, cela aurait été généralement important. Nous nous préoccupions du coût de la surcharge des appels de fonction et de l'indirection. Nous avons même déroulé des boucles si nous le pouvions. C'était particulièrement vrai dans les environnements en temps réel intégrés.
Mais les types d'environnements où ce genre de parcimonie est important sont aujourd'hui peu nombreux et éloignés les uns des autres. La grande majorité des systèmes logiciels nécessitent moins de 1 % de la puissance d'un processeur moderne. De plus, les processeurs sont tellement bon marché et disponibles qu'il est trivial d'en ajouter à un système. Ces faits modifient le compromis de la performance du programme à la performance de l'équipe de développement et à sa capacité à créer des systèmes et à les faire fonctionner. Et c'est sur ces besoins que les idées de Clean Code entrent en jeu.
Pour la plupart des organisations, il est économiquement plus avantageux de conserver les cycles des programmeurs que ceux des ordinateurs. Donc, s'il y a un fossé entre nous, je pense que c'est seulement dans les types de contextes auxquels nous donnons la priorité. Si vous essayez de tirer chaque nanoseconde d'une batterie de GPU, le Clean Code n'est peut-être pas fait pour vous, du moins dans les boucles internes les plus exigeantes. D'un autre côté, si vous essayez de tirer chaque heure de productivité d'une équipe de développement de logiciels, alors Clean Code peut être une stratégie efficace pour atteindre cet objectif.
CASEY : Pour être un peu plus concret, afin que je comprenne bien ce que vous dites, pouvons-nous prendre des exemples de logiciels spécifiques ? Par exemple, je suppose que nous connaissons tous les deux Visual Studio et CLANG/LLVM. Ces deux logiciels seraient-ils des exemples raisonnables de ce que vous appelez la grande majorité des logiciels qui nécessitent moins de 1 % d'un processeur moderne ?
BOB : Non, il me semble qu'un IDE est un système logiciel très spécialisé. Il n'en existe que quelques-uns, et très peu sont devenus populaires.
Les IDE sont des systèmes intéressants en ce sens qu'ils couvrent un vaste domaine de contextes. Il y a des parties où les nanosecondes sont extrêmement importantes, et d'autres où elles le sont très peu. Un IDE moderne doit être capable d'analyser de grandes quantités de code, touche par touche. S'assurer que le code d'analyse préserve les nanosecondes peut avoir un effet important. D'un autre côté, le code qui met en place un dialogue de configuration n'a même pas besoin d'une infime fraction de ce type d'efficacité.
Par ailleurs, le type d'efficacité dont a besoin le moteur de compilation d'un IDE est plus algorithmique que de type "cycle-lean". Un code sans cycle peut augmenter l'efficacité d'un ordre de grandeur, mais le bon choix d'algorithme peut augmenter l'efficacité de plusieurs ordres de grandeur.
Non, le type de logiciel auquel je faisais référence et qui nécessite moins de 1 % d'un processeur moderne sont les systèmes courants que la plupart des programmeurs utilisent. Un site web, une application de calendrier, un tableau de bord de contrôle de processus (pour un processus simple). En fait, à peu près n'importe quelle application Rails, ou n'importe quelle application Python ou Ruby. Même la plupart des applications Java. Vous ne choisiriez tout simplement pas de tels langages si les nanosecondes étaient votre préoccupation.
Un langage comme Clojure (mon langage de prédilection pour le moment) est probablement 30X plus lent qu'une application Java équivalente, et probablement 60X plus lent qu'une application C équivalente. Mais je ne m'en préoccupe pas tant que ça. Tout d'abord, je peux passer à Java si nécessaire (et je l'ai fait pour des tâches liées au calcul). Deuxièmement, pour de nombreuses applications, l'ajout de processeurs est simple et peu coûteux. Je trouve donc généralement que le gain de temps pour le programmeur est rentable.
Ne vous méprenez pas. Je suis un vieil assembleur et un hacker C des années 70 et 80. J'ai compté assidûment les microsecondes lorsque c'était important (les nanosecondes étaient bien au-delà de tout ce que nous pouvions imaginer). Je sais donc à quel point un code sans cycle peut être important. Mais les processeurs d'aujourd'hui sont 10 000 fois plus rapides que les machines que nous utilisions à l'époque (littéralement). Ainsi, pour la plupart des systèmes logiciels actuels, nous avons la possibilité d'échanger certains de ces cycles supplémentaires par seconde contre l'efficacité du programmeur.
CASEY : Si je vous comprends bien, vous dites qu'il existe deux grandes catégories de logiciels, et que nous devrions peut-être discuter de chacune d'entre elles séparément. Il semble que la plupart des logiciels que j'utilise appartiennent à la catégorie où les "nanosecondes peuvent compter", selon votre terminologie - en d'autres termes, Visual Studio, LLVM, GCC, Microsoft Word, PowerPoint, Excel, Firefox, Chrome, fmmpeg, TensorFlow, Linux, Windows, MacOS, OpenSSL, etc. Je suppose qu'au vu de votre réponse, vous êtes d'accord pour dire que tous ces logiciels doivent se préoccuper des performances ?
BOB : Pas exactement. D'après mon expérience, il existe un large éventail de logiciels qui s'appliquent au niveau des modules. Certains modules doivent fonctionner avec des délais de l'ordre de la nanoseconde. D'autres ont besoin de temps de réponse de l'ordre de la microseconde. D'autres encore ne doivent fonctionner qu'avec des contraintes de l'ordre de la milliseconde. Enfin, certains modules fonctionneraient de manière satisfaisante avec des temps de réponse proches de la seconde.
La plupart des applications sont constituées de modules qui couvrent une grande partie de ce spectre. Par exemple, Chrome doit effectuer un rendu rapide. Les microsecondes comptent lorsque vous remplissez une page web complexe. D'un autre côté, la boîte de dialogue des préférences de Chrome n'a probablement même pas besoin d'être réactive au niveau de la milliseconde.
Si l'on considère les modules d'une application particulière comme un histogramme des temps de réponse, je suppose que l'on observera une sorte de distribution non normale. Certaines applications peuvent avoir de nombreux modules de nanosecondes et peu de modules de millisecondes. Dans d'autres applications, la plupart des modules se situent dans la plage des millisecondes et peu dans celle des nanosecondes.
Par exemple, je travaille actuellement sur une application dans laquelle la grande majorité des modules fonctionnent bien au niveau de la milliseconde, mais quelques-uns nécessitent des performances 20 fois supérieures. Ma stratégie a consisté à écrire les modules à la milliseconde en Clojure car, bien que lent, c'est un langage très pratique. Les modules de la microseconde ont été écrits en Java, qui est beaucoup plus rapide, mais beaucoup moins pratique.
Il existe des langages et des structures qui font abstraction du métal dur de la machine et qui permettent aux programmeurs de se concentrer sur le domaine du problème. Il est beaucoup plus efficace pour un programmeur d'écrire du code au niveau de la milliseconde lorsqu'il n'a pas à se préoccuper de l'optimisation des hits du cache L2. Il peut alors penser aux exigences de l'entreprise et aux autres programmeurs qui devront traiter son code au cours de la prochaine décennie.
CASEY : Je voulais être précis, alors je vais essayer de reformuler ma question. Visual Studio, LLVM, GCC, Microsoft Word, PowerPoint, Excel, Firefox, Chrome, fmmpeg, TensorFlow, Linux, Windows, MacOS et OpenSSL sont-ils des exemples de programmes où les "millisecondes comptent" dans au moins certains de leurs "modules", selon votre terminologie ?
BOB : Les millisecondes ? Bien sûr. Je dirais qu'ils ont tous des modules où la microseconde a de l'importance ; et beaucoup ont des modules où la nanoseconde a de l'importance.
J'ai dû écrire ce qui suit pendant que vous ajoutiez la question suivante. J'avais donc une fusion à faire. Voici donc ce que j'ai dit avant de lire votre question suivante. J'y répondrai bientôt.
Bob : (Le lendemain) Au fait, j'ai regardé l'intégralité de votre vidéo l'autre jour. Je me suis dit que puisque nous étions engagés dans cette discussion, je devais étudier toute l'histoire que vous avez racontée. Et, bien que j'aie été quelque peu contrarié par certains de vos propos, je dois vous féliciter pour votre très belle analyse.
L'idée que les surfaces de certaines formes peuvent toutes être calculées à l'aide de la même formule de base (KxLxW) est un de ces moments que seuls les programmeurs et les mathématiciens peuvent apprécier à sa juste valeur.
Dans l'ensemble, j'ai trouvé que votre vidéo fournissait un bon exemple du type de choses qu'un programmeur doit faire lorsqu'il résout un problème contraint dans un environnement aux ressources limitées. Il est clair (du moins je pense que cela devrait être clair) que l'on ne préférerait pas la solution KxLxW dans un environnement riche en ressources, à moins d'être très sûr que l'entreprise n'étendrait pas le problème à des formes générales. Et, en effet, même si le problème restait limité à des formes permettant la solution KxLxW, la séparation en formules plus traditionnelles correspondrait probablement mieux aux attentes des autres programmeurs et ne les obligerait pas à se poser des questions et à valider à nouveau l'approche relativement nouvelle. Bien que cela puisse les priver d'un délicieux moment de perspicacité, cela leur permettrait de poursuivre leurs tâches sans délai.
Je ne sais pas si vous avez lu les travaux de Don Norman. Il y a longtemps, il a écrit un livre intitulé The Design of Everyday Things. Il vaut la peine d'être lu. Dans ces pages, il énonce la règle empirique suivante : Dans un environnement riche en ressources, je crains que la solution KxLxW ne se heurte à cette règle.
_Ce texte a été écrit avant que Casey ne lise mon commentaire ci-dessus.
CASEY : Très bien, il semble que nous soyons sur la même longueur d'onde en ce qui concerne les catégories de logiciels. J'aimerais donner ma caractérisation de la pratique de codage que vous décrivez telle qu'elle s'applique à quelque chose comme LLVM, puisque c'est un logiciel de la liste que j'ai donnée, et il se trouve qu'il est open source et que nous savons donc exactement comment il est construit (contrairement à, disons, Visual Studio).
Ce que je pense que vous dites dans les paragraphes ci-dessus - et dans vos livres et conférences - c'est que lors de la programmation d'un grand logiciel comme LLVM, les programmeurs n'ont pas besoin de se préoccuper des performances lorsqu'ils programment. Ils doivent avant tout se préoccuper de leur propre productivité. S'il s'agissait, pour reprendre votre exemple précédent, d'une simple application de calendrier, ils ne penseraient idéalement jamais à la performance. Mais en LLVM, puisque c'est dans la catégorie où parfois "les nano/micro/millisecondes comptent", alors ils devront penser à la performance à un moment ou à un autre. Ce moment est celui où ils constatent que le programme tourne trop lentement, quand cela se produit.
Dans le cas de LLVM, c'est peut-être la première fois que quelqu'un essaie de construire un programme vraiment important, comme Unreal Engine, Chrome ou autre. Lorsque cela se produit, alors l'hypothèse est que les problèmes de performance se trouvent dans certaines parties isolées du code (je crois que vous les avez appelées "modules" dans cette discussion), donc juste ces parties devraient maintenant être réécrites pour être orientées vers la performance.
C'est ainsi que j'interprète ce que vous avez dit jusqu'à présent, et aussi comment j'ai interprété des choses que vous avez dites récemment comme "Si mon code Clojure est trop lent, je peux toujours descendre en Java", ce qui signifie que vous pourriez réécrire une partie du code en Java si cette partie avait besoin de plus de performance.
Est-ce que cette caractérisation est juste ?
BOB : Je suis l'un des signataires du Manifeste Agile qui croit encore en une architecture et une conception initiales. (En fait, je suis presque sûr qu'ils y croient tous. Ce sont les derniers zélateurs qui pensaient qu'il valait mieux se lancer dans le code sans aucune réflexion préalable).
Dans le cas que vous avez mentionné, j'espère que j'aurais suffisamment réfléchi au problème pour reconnaître les endroits où je pourrais rencontrer des problèmes de performance et donc traiter ces modules avec plus d'attention. Par exemple, j'aurais pu créer une version très atténuée du module et la soumettre à un test de torture tout en profilant le comportement. Ma préoccupation, bien sûr, serait d'investir beaucoup de temps et d'efforts dans une approche qui, en fin de compte, ne répondrait pas aux besoins de mon client. (Demandez-moi pourquoi je m'inquiète de ce genre de choses ;-).
L'essentiel, bien sûr, est que l'analyse factorielle unique est toujours sous-optimale. Il n'y a pas Une seule vraie méthode. (Un point que j'ai essayé de faire valoir à plusieurs reprises dans Clean Code.)
CASEY : J'ai déjà beaucoup de questions que j'aimerais poser, mais votre dernière réponse est la meilleure pour l'une d'entre elles, alors je vais choisir celle-là :) Au cours de cette conversation, vous avez déjà parlé de plusieurs implications critiques en matière de performances dans l'architecture logicielle : les préoccupations "nanosecondes" d'un analyseur IDE, la division des "modules" en exigences de temps de réponse nano/micro/milli/seconde, la suggestion qu'un programmeur (dans ce cas, vous) pourrait créer "une version très atténuée du module et la soumettre à un test de torture tout en profilant le comportement" avant d'écrire un morceau de logiciel pour s'assurer que la performance serait acceptable, et même l'idée que vous pourriez avoir à choisir différents langages en fonction des exigences de performance (Clojure vs. Java vs. C, dans votre exemple).
En résumé, vous avez dit : "Connaître ces environnements et savoir lequel est le mieux adapté au problème posé est une chose que nous devons tous maîtriser".
Compte tenu de tout cela, j'aimerais revenir à la question initiale : pourquoi avez-vous été surpris que des gens, comme moi, associent le "code propre" à l'opposé de ce que vous avez écrit ici en ce qui concerne la performance ? Aucun des éléments que je viens d'énumérer ne figure en bonne place dans vos enseignements. Je ne veux pas dire que vous ne pouvez pas trouver une phrase ici ou là dans un livre ou un article de blog qui fait un clin d'œil à la performance, bien sûr. Mais en volume, les choses que vous dites ici sont à peine mentionnées.
Pour illustrer concrètement ce que j'entends par là, voici une série de conférences de plusieurs heures, en six parties, que vous avez donnée sur le thème du "code propre" et dans laquelle pas une seule des choses que vous mentionnez ici n'est abordée au cours des neuf heures que dure la conférence :
https://www.youtube.com/playlist?list=PLmmYSbUCWJ4x1GO839azG_BBw8rkh-zOj
Si les questions de performance sont aussi importantes que vous le laissez entendre dans ce fil de discussion, pourquoi n'y a-t-il pas au moins une heure sur neuf consacrée à expliquer au public l'importance de choses telles que l'apprentissage de la performance, la planification à l'avance des parties du code qui peuvent avoir des implications en termes de performance, l'évitement des constructions de programmation nuisibles à la performance dans ces situations, la création de tests de performance à l'avance tels que ceux que vous avez décrits dans la réponse précédente, etc.
Une autre façon de poser la question serait de se demander s'il est possible que vous ayez pris pour acquis l'importance de la sensibilisation à la performance, même pour vous, parce que vous avez peut-être l'habitude de le faire lorsque vous vous programmez, et que vous ne lui avez donc pas donné l'importance nécessaire pour garantir que votre public - qui souvent ne connaît pas grand-chose à la performance - y pense réellement au bon moment et de la bonne façon ? Surtout si, comme vous l'avez dit, "nous devons tous être compétents" dans ce domaine ?
BOB: Franchement, je pense que c'est une critique juste. Et il se trouve que j'ai donné un cours hier dans lequel j'ai passé plus de temps à parler des coûts de performance, ainsi que des avantages de productivité, des disciplines et des principes que j'enseigne. Merci donc pour ce coup de pouce.
Je ne pense pas avoir utilisé le mot "surpris". Ou si je l'ai fait, ce n'était pas en référence au sujet, mais plutôt au ton. J'en ai assez dit à ce sujet.
Vous m'avez demandé si je n'avais pas considéré l'importance des performances comme allant de soi. Après réflexion, je pense que c'est probablement le cas. Je ne suis pas un expert en matière de performance. Mon expertise porte sur les pratiques, les disciplines, les principes de conception et les modèles architecturaux qui aident les équipes de développement de logiciels à construire et à maintenir efficacement des systèmes logiciels complexes et de grande taille. Et comme tout expert le sait, et doit le combattre, les experts marteaux pensent que tout ressemble à un clou.
Vous m'avez également demandé "pourquoi...". Dans la mesure où je n'ai pas répondu à cette question ci-dessus, je vais simplement la retourner et souligner que c'est probablement pour la même raison que votre vidéo était uniquement axée sur l'amplification des performances, au détriment de toute autre préoccupation. Pour un marteau de la performance, tout ressemble à un clou ;-)
Ceci étant dit, je trouve que cette conversation est plus bénéfique que je ne l'avais initialement prévu. Elle m'a incité à changer de point de vue. Il ne faut pas s'attendre à ce que ce changement soit énorme. Il ne faut pas s'attendre à ce que je fasse des vidéos sur l'horreur du Clean Code ;-). Mais si vous regardez la prochaine série de 9 heures de vidéos que je ferai, vous verrez probablement plus qu'un simple clin d'œil aux problèmes de performance. Je pense que vous pouvez vous attendre à deux ou trois clins d'œil ;-)
Parce que, comme vous l'avez souligné, je considère que les problèmes de performance sont suffisamment importants pour être anticipés et planifiés.