1 points par GN⁺ 2024-08-17 | 1 commentaires | Partager sur WhatsApp
  • En s’appuyant sur une citation de Linus Torvalds, une bonne conception commence avant l’écriture du code par la mise en place de structures de données et de relations stables
  • Un modèle de données bien conçu simplifie naturellement la logique applicative et rend le logiciel plus fiable et plus facile à comprendre
  • Remettre le modèle de données à plus tard augmente la charge de travail ensuite, alors qu’une bonne structuration dès le départ facilite les migrations et l’extension de systèmes complexes
  • Dans un projet, au lieu d’optimiser davantage un algorithme complexe, une restructuration des données a permis d’éliminer toute une catégorie de problèmes, en remplaçant une fonction de 500 lignes par une fonction de 50 lignes et une structure de données adaptée
  • En pratique, il faut appliquer des types plus stricts aux interfaces et aux bases de données, et concevoir d’abord le flux de données et les interactions entre composants avant les détails du code

Les structures de données déterminent la conception du code

  • Linus Torvalds considère Git comme une conception simple reposant sur des structures de données stables et documentées, et insiste sur l’idée d’organiser le code autour des données
    • La phrase clé est la citation : « Les mauvais programmeurs se préoccupent du code, les bons programmeurs se préoccupent des structures de données et de leurs relations »
    • L’une des raisons du succès de Git tient à sa conception centrée sur les données
  • De bonnes structures de données facilitent la conception et la maintenance du code, tout en améliorant la fiabilité du logiciel, la compréhension du système et la lisibilité du code
    • La logique applicative suit souvent le modèle de données
    • Réfléchir au modèle de données trop tard augmente la charge de travail par la suite
    • Un modèle de données bien conçu facilite ensuite les migrations et l’extension de systèmes complexes
  • Dans un cas concret de projet, une restructuration des données a eu plus d’impact que des raffinements supplémentaires d’un algorithme complexe
    • Le changement de structure de données a supprimé une catégorie entière de problèmes
    • Une fonction de 500 lignes a été remplacée par une fonction de 50 lignes et une structure de données bien pensée
    • Le nouveau code était plus rapide, plus facile à comprendre et plus simple à maintenir
    • En contrepartie, comme les données existantes ont dû être restructurées, l’effort s’est déplacé vers les couches inférieures

Il vaut mieux déplacer la complexité vers les données

  • Dans The Art of Unix Programming, la « Rule of Representation » explique qu’il faut mettre la connaissance dans les données afin de rendre la logique du programme simple et robuste
    • La logique procédurale est difficile à vérifier pour les humains, tandis que des structures de données complexes sont plus faciles à modéliser et à raisonner
    • Un diagramme d’arbre de pointeurs de 50 nœuds peut être plus expressif et plus explicite qu’un organigramme représentant 50 lignes de programme
    • Exprimer une table de transformation par une initialisation de tableau est plus transparent et plus clair que d’écrire le même contenu avec une instruction switch
    • S’il faut choisir où placer la complexité entre le code et les structures de données, il vaut mieux la déplacer vers les structures de données

En pratique, il faut d’abord concevoir le flux de données

  • La manière la plus directe de l’appliquer consiste à commencer par les données
    • Appliquer des types plus stricts aux interfaces ou aux bases de données peut réduire la complexité du code
    • Il faut consacrer davantage de temps en amont à réfléchir aux structures de données
    • Cela ne signifie pas que le code n’est pas important ; tous les éléments comptent ensemble
    • Avant d’entrer dans les détails du code, une approche de plus haut niveau qui examine comment les données circulent et comment les composants interagissent est utile
  • Comme exemple d’exigences pour un Senior Engineer (L5), la rédaction de documents de conception de haut niveau pour des systèmes plus complexes fait généralement partie des attentes dans les entreprises FAANG
    • Cela inclut aussi la conduite de la planification d’équipe et l’élaboration de bonnes feuilles de route pour des fonctionnalités de taille moyenne à grande
    • La capacité à concevoir d’abord le flux de données et les interactions entre composants est liée à un niveau d’impact en ingénierie plus élevé

1 commentaires

 
GN⁺ 2024-08-17
Avis sur Hacker News
  • Cet article Substack semble avoir simplement recopié plusieurs citations de ce billet Stack Exchange : https://softwareengineering.stackexchange.com/questions/1631...

    • L’URL a été changée depuis https://read.engineerscodex.com/p/good-programmers-worry-abo.... Il y a quelques mois, on avait déjà eu pendant un moment ce genre de spam Substack, et il semble que le même acteur malveillant ait recommencé
    • C’est assez culotté de ne même pas avoir changé l’ordre des citations
  • « Montrez-moi vos organigrammes [code] et cachez-moi vos tables [schéma], et je resterai perplexe. Montrez-moi vos tables [schéma], et je n’aurai généralement pas besoin de vos organigrammes [code] ; ils seront évidents. » — Fred Brooks, “The Mythical Man Month”, chapitre 9

    • Quand j’étais jeune et obsédé par Quake, j’avais écrit à John Carmack pour lui demander s’il avait des conseils à donner à un apprenti programmeur et des livres préférés. À ma grande surprise, il m’a répondu de façon assez réfléchie, avec notamment ceci :
      « Lis The Mythical Man Month. Je me souviens avoir pensé qu’un livre aussi ancien ne pouvait rien dire de pertinent sur le développement logiciel aujourd’hui, mais j’avais tort. »
    • Je suis venu ici pour partager cette citation, tellement elle est juste. Il y a toutefois une exception : le moment où le coût de modification d’un schéma de base de données devient bien supérieur au coût de modification du code.
      À ce moment-là, les développeurs applicatifs commencent à abuser de la base de données parce qu’ils sont plus rapides et ont beaucoup à faire
  • Les structures de données et les types ne sont pas la même chose. Une structure de données, ce sont des motifs de bits et des références à d’autres motifs de bits, c’est-à-dire des pointeurs ou des relations.
    Les types imposent des contraintes à ces motifs de bits, dans l’usage qu’en font les langages de programmation, mais ils peuvent aussi exprimer beaucoup d’autres fonctionnalités du langage. Construire une hiérarchie de types complexe avec des abstractions inutiles ne signifie pas « se soucier des structures de données » ; c’est un mode d’échec dans lequel même des ingénieurs intelligents tombent souvent

    • C’est un point subtil mais important. Les types peuvent être un outil utile pour restreindre et spécifier le schéma d’une structure de données, mais se préoccuper des types et se préoccuper des structures de données sont deux choses assez différentes
    • Les types sont des structures de données reconnues par le langage. C’est ce qui permet aux outils d’effectuer des vérifications qu’ils ne peuvent pas faire sur de simples structures de données ordinaires
    • Une structure de données est un algorithme à l’arrêt. À chaque opération, on mélange et on déplace des choses, mais elle reste globalement immobile, comme une machine de Turing dont on ne tourne la manivelle que de temps en temps.
      Les types sont des bits sur le disque
    • Bonne remarque. Assimiler les structures de données aux types est une simplification excessive qui passe à côté de l’essentiel.
      Ce que l’idée originale voulait dire ici ressemble plutôt à : réfléchissez plus profondément au problème et ne choisissez pas une structure qui vous fera trébucher plus tard. Regardez par exemple jusqu’où les pipes Unix se sont diffusés, et à combien de domaines et de cas d’usage ils se sont étendus. C’est une excellente façon de visualiser la manière de construire des systèmes tout en respectant les contraintes des humains et des machines.
      Il a fallu pas mal de temps à Ken Thompson et à d’autres pour comprendre qu’un mécanisme comme les pipes avait du sens dans Unix. Ce n’était pas une intuition facile : il a fallu de l’acharnement et du travail ensuite pour trouver les bons blocs de construction du système
    • Une même structure de données peut recevoir différents types. C’est ce que fait l’opérateur typedef de Pascal
  • Linus a toujours su bien résumer ce que les autres percevaient confusément. Ce dont parle le texte ressemble aussi à DDD, devenu un savoir-faire perdu.
    Par « perdu », je veux dire que la plupart des développeurs que je rencontre aujourd’hui s’intéressent davantage à déplacer des algorithmes et du JSON dans tous les sens qu’à comprendre le domaine sur lequel ils travaillent et à modéliser les entités et leurs interactions. Dans les architectures modernes basées sur AWS, cela se manifeste par des paquets de GSI DynamoDB peu justifiés, des objets anémiques, et des couches de « services » qui ressemblent à des scripts empilant hack sur hack. Il y avait peut-être l’hypothèse implicite que le contexte de domaine serait suffisamment bien défini à l’intérieur des frontières de service, mais je ne pense pas que ce soit une bonne hypothèse.
    Je ne sais pas où notre industrie a perdu la rigueur de conception : à l’école, dans les pipelines d’entretien, parce qu’on a abaissé les exigences, ou tout cela à la fois.

    • Je pense que l’industrie n’a jamais vraiment pris la conception logicielle au sérieux. Elle a toujours été traitée comme un gros mot, associée à des personnes jugées politiquement incorrectes ou insignifiantes, et elle attire quantité de commentaires expliquant que tout est mauvais parce que quelqu’un, une fois, a mal fait quelque chose.
      Pire encore, la conception commet le grand péché de ne pas être facile à automatiser. Les gens suivent donc sans esprit critique la conception imposée par les outils, et sont mal à l’aise à l’idée de devoir réfléchir plus profondément à ce qu’ils font. Tout le monde veut externaliser cette réflexion à des « experts ».
      Le fait qu’elle ne soit pas correctement enseignée, qu’il faille des années pour l’apprendre par soi-même, et qu’elle soit considérée comme moins réelle que le code, donc moins importante, pose aussi problème. Mais cette croyance finit par limiter le niveau de ce qu’on peut construire à celui d’un débutant avancé. Les programmeurs choisissent collectivement de maintenir les standards aussi bas que possible, et sur ce sujet il semble presque y avoir une mentalité de crabes qui se tirent mutuellement vers le bas.
    • Le modèle de domaine anémique a été identifié comme antipattern il y a assez longtemps[1]. Il apparaît généralement avec l’obsession des types primitifs[2], ce qui conduit à disperser partout toutes sortes de validations et de contrôles autour de types primitifs comme les chaînes et les nombres.
      Il y a aussi beaucoup de duplication de code qui ne paraît pas être de la duplication syntaxiquement, mais qui fait fonctionnellement la même chose.
      1 https://martinfowler.com/bliki/AnemicDomainModel.html
      2 https://wiki.c2.com/?PrimitiveObsession
    • L’industrie récompense surtout le fait d’écrire du code, pas de concevoir du logiciel.
      Je pense que c’est parce que les conséquences du mauvais code sont moins visibles. Un mauvais pont s’effondre, mais un mauvais code est simplement refactoré ou remplacé par encore plus de code. Un fichier texte que la direction ne comprend pas est remplacé par un autre fichier texte que la direction ne comprend pas.
      Et dès que quelque chose fonctionne, c’est l’extinction des feux. Rien n’est aussi permanent qu’un hack temporaire qui fonctionne parfaitement. Mais 1 000 hacks temporaires ne font pas un système bien conçu. À mon avis, mûrir dans le développement logiciel, c’est se concentrer sur les données et les relations plutôt que sur l’écriture de code. Il faut être capable de transformer cela en code, mais il faut transformer les données et les relations en code, pas transformer du code qui fonctionne en modèle de données.
    • Je n’ai toujours pas vu de raison convaincante expliquant pourquoi les objets anémiques seraient si tabous. La plupart des fonctions DDD que j’ai vues n’étaient que des getters et setters verbeux.
      Ce n’est pas parce qu’une entité de domaine peut contenir toute la logique qu’elle doit nécessairement le faire. Par exemple, s’il faut vérifier si un nom d’utilisateur existe déjà, comment est-on censé faire cela dans une entité de domaine qui ne peut pas « dépendre » de la couche d’accès aux données ? On recommande souvent quelque chose comme un « service de domaine », mais alors la logique métier se retrouve dispersée à plusieurs endroits, ce qui me semble aller à l’encontre de l’esprit de DDD.
      J’aime plutôt DDD comme philosophie, mais je déteste profondément les patterns de « DDD tactique ». Je pense que trop de gens assimilent Domain-Driven Design à Domain-Driven Implementation. J’essaie de créer des domaines riches là où c’est approprié, mais ce n’est pas adapté à tous les projets et j’essaie de ne pas me laisser piéger par la terminologie. Que le type « Name » soit un objet-valeur ou une racine d’agrégat ne m’intéresse pas. Ce qui compte avant tout, ce sont les contextes bornés. J’admets aussi que DDD peut parfois accroître la complexité applicative pour un gain presque nul. Je ne dirais certainement pas que c’est une solution miracle.
      Je continuerai à utiliser DDD, mais j’ai du mal à me défaire de l’impression que DDD ressemble à une tentative de faire passer le message : « vous voyez, la programmation orientée objet n’est pas si mauvaise que ça, non ? ». Et je ne suis même pas sûr qu’elle atteigne cet objectif.
    • Pendant des décennies, les performances CPU, la quantité de mémoire, l’espace disque, les débits réseau, etc., ont connu une croissance exponentielle, ce qui a fait disparaître l’essentiel du coût des mauvaises conceptions. Il est donc généralement devenu possible de s’en sortir même en produisant du code poubelle aussi vite que des code monkeys peuvent marteler leur clavier.
  • Cela m’intéresse parce qu’avant de commencer l’ingénierie professionnelle, je faisais quotidiennement des données et de l’analyse statistique avec des systèmes statistiques comme Matlab, R et les premiers Python.
    Mon point de vue d’ingénierie a donc toujours reposé sur deux choses : la gestion de l’état fonctionnel et des workflows de données.
    Après dix ans de génie logiciel comme métier, j’ai constaté que la plupart des ingénieurs « scientifiques » comme Minsky ou Shannon décrivaient le monde du calcul en termes de gestion d’état, de transformation de données et de gestion du surcoût de calcul. Les grands noms et pionniers du logiciel accordaient tous une très grande importance aux données et à l’état ; l’informatique des débuts était essentiellement cela, et l’on s’attendait à ce que ce schéma se poursuive.
    À l’inverse, dans la conception des systèmes d’ingénierie, il n’existe aucune cohérence dans les hypothèses fondamentales censées être toujours vraies et suivies par tous ; quand il y en a, elles relèvent généralement davantage de la mode. Dans la plupart des logiciels en production, les calendriers business influencent bien davantage les priorités et la structure d’ingénierie que la robustesse, l’antifragilité ou la gestion d’état.
    Les organisations professionnelles comme les guildes ou les syndicats sont presque universellement rejetées par les ingénieurs logiciels. Comme il n’y a aucune conséquence à ne pas prendre l’IEEE au sérieux, en pratique personne ne le prend au sérieux. Résultat : il n’existe pas de mécanisme imposant la pratique ou l’autorégulation comme en génie civil ou biomédical, et même là-bas ces mécanismes sont à peine utilisés.
    Dans l’ensemble, l’état actuel du développement logiciel est complètement détaché de ses racines très nobles et philosophiques, et il est de fait piloté par des entreprises qui donnent la priorité aux systèmes permettant aux gens qui ont de l’argent d’en gagner davantage. Dès lors, ce qui est « bon » n’a presque aucun rapport avec ce qui est encouragé par les incitations.

  • « Montrez-moi vos organigrammes [code] et cachez-moi vos tables [structures de données], et je resterai perplexe. Montrez-moi vos tables, et en général je n’aurai pas besoin de vos organigrammes. Ils seront évidents. » — Fred Brooks

    • Cette citation semble passer à côté du fait que le modèle de persistance et les structures de données réelles peuvent être différents, et devraient peut-être l’être.
      Faire correspondre le tout en 1:1 avec les tables sous-jacentes est extrêmement contraignant et mène, à mon avis, à des modèles qui passent à côté de l’expressivité offerte par les langages modernes
  • C’est essentiellement le point de vue de la programmation fonctionnelle et de la théorie des catégories.
    On dispose d’un certain objet de données, et l’on fournit des contraintes sur la manière dont sa structure peut être transformée. Toute la logique du programme porte alors sur des transformations qui préservent cette structure.
    Les transformations deviennent plus simples et plus faciles à raisonner, et il reste au final un graphe où les transformations sont les arêtes et les structures les nœuds. C’est généralement plus facile à raisonner qu’un programme impératif arbitraire

    • Ce n’est pas le point de vue de la programmation fonctionnelle ni de la théorie des catégories. C’est le point de vue de toutes les philosophies de langage, et les personnes qui préfèrent l’orienté objet ou le procédural diraient exactement la même chose. Définir correctement les types de données est important et s’applique à tous les langages et paradigmes.
      Le point de vue de la programmation fonctionnelle est plutôt qu’il ne faut pas transformer les objets et qu’il faut éviter les mutations, ce qui est distinct de cette discussion. Le cœur de la théorie des catégories est de traiter des motifs de relations qui apparaissent communément dans plusieurs domaines des mathématiques, et cela n’a absolument rien à voir avec ce qui est discuté ici. Peut-être était-il question de théorie des types, mais ce n’est pas pertinent non plus
  • Voici la conclusion à laquelle j’étais arrivé autrefois : tout ce que nous faisons dans le code a de fortes chances de survivre bien moins longtemps qu’une bonne décision prise sur les données.
    https://www.swyx.io/data-outlasts-code-but

    • Les bonnes décisions sont invisibles. Seules les mauvaises semblent survivre éternellement
  • Ce principe s’applique aussi au niveau métier. Je continue d’avoir affaire à des analystes métier qui se focalisent sur les processus (le code) sans prendre d’abord le temps de comprendre les entités et leurs relations (les données).
    Résultat : au moment de construire quelque chose, ils n’arrivent pas à communiquer aux développeurs à quoi devrait ressembler le modèle de données. Les processus sont implémentés, tandis que le modèle de données, au lieu d’être soigneusement conçu, est bricolé au coup par coup