- Le fichier
pv_controller.gode Kubernetes est le contrôleur qui synchronise le binding PV/PVC et précise dès le haut du fichier qu’il ne faut pas le « simplifier » et qu’il faut conserver le style space shuttle - Ce style consiste à associer un
elseà chaqueifet à laisser des commentaires même sur des conditions qui paraissent évidentes, afin de rendre visibles dans le code les branches examinées et les intentions - Au cœur de la conception se trouve un pointeur bidirectionnel reliant
pvc.Spec.VolumeNameetpv.Spec.ClaimRef, ce qui permet de gérer et de récupérer les situations de concurrence, suppression, modification utilisateur et binding simultané dans un environnement sans transactions - Le contrôleur combine surveillance des changements PV/PVC, cache interne, file d’attente mono-worker, enregistrement d’événements, provisioning dynamique et interface de migration CSI pour gérer les transitions d’état du binding
- Les branches verbeuses et les commentaires servent à préserver dans le code la connaissance métier du comportement et le contexte de reprise après échec ; les modifications futures doivent donc suivre le même style
Rôle et principes de rédaction de pv_controller.go
pv_controller.goest le fichier d’implémentation du PersistentVolumeController dans le package Kubernetespersistentvolume- Ce contrôleur aligne l’état de
PersistentVolumeClaimet dePersistentVolume- contrôleur de cache qui surveille les changements de
PersistentVolume - contrôleur de cache qui surveille les changements de
PersistentVolumeClaim - synchronisation de l’état PV/PVC à partir des événements de changement des deux objets
- contrôleur de cache qui surveille les changements de
- Le commentaire en tête du fichier avertit à plusieurs reprises de ne pas simplifier ce code
- le nom du style est
space shuttle style - il consiste à mettre un
elsecorrespondant à chaque instructionif - l’objectif est de rendre explicites toutes les branches, sauf les simples vérifications d’erreur
- même les comportements qui semblent évidents sont documentés par des commentaires pour permettre aux mainteneurs de suivre la complexité du binding
- le nom du style est
Pourquoi conserver le space shuttle style
- Ce contrôleur est le résultat de la fusion, en un seul composant, d’un travail auparavant réparti entre trois contrôleurs
- Lors de la simplification du sous-système PV, il est devenu nécessaire de traiter explicitement toutes les conditions dans le code
- Le résultat peut donc sembler verbeux, avec beaucoup de commentaires et de branches
- Cette verbosité est un moyen de conserver dans le code la connaissance métier et le contexte du comportement de binding
- Lorsqu’on modifie ce fichier, il faut préserver le
space shuttle styleet, si nécessaire, ajouter des branches et des commentaires selon la même logique
Conception clé : le pointeur bidirectionnel entre PV et PVC
- Au centre de la conception se trouve un pointeur bidirectionnel entre PV et PVC
- pointeur côté PVC :
pvc.Spec.VolumeName - pointeur côté PV :
pv.Spec.ClaimRef
- pointeur côté PVC :
- Cette bidirectionnalité est difficile à gérer dans un système sans transactions, mais elle est nécessaire pour garantir un fonctionnement correct même en cas de panne
- Si une instance HA défaillante du contrôleur crée une situation de concurrence, plusieurs bindings indiscernables peuvent apparaître, avec un risque de perte de données
- Le contrôleur est fondamentalement conçu pour fonctionner en mode de haute disponibilité active-passive
- les transitions d’objet sont aussi conçues pour fonctionner en HA active-active
- toutefois, si deux contrôleurs actifs entrent souvent en conflit, les performances peuvent se dégrader
Méthode de binding et conditions de reprise
- Le contrôleur prend en charge des objets pré-bindés dans les deux sens
- un PVC qui demande un PV spécifique
- un PV réservé pour un PVC spécifique
- Le binding se fait en deux étapes
- d’abord modification de
PV.Spec.ClaimRef - puis modification de
PVC.Spec.VolumeName
- d’abord modification de
- À n’importe quel moment du processus, le PV ou le PVC peut être modifié ou supprimé par un utilisateur ou un autre contrôleur
- Deux contrôleurs ou plus peuvent aussi tenter de binder simultanément des volumes et des claims différents
- Le contrôleur doit pouvoir récupérer de telles situations de conflit
Principaux éléments de la structure du contrôleur
PersistentVolumeControllercontient les listers, fonctions de synchronisation des informers, client Kubernetes, enregistreur d’événements, gestionnaire de plugins de volume, etc., nécessaires à la synchronisation PV/PVC- Les dernières versions connues de PV/PVC sont conservées dans un cache interne
volumes persistentVolumeOrderedIndexclaims cache.Store
- Ce cache reflète à la fois la version la plus récente stockée sur l’API server et la version reçue via les événements etcd
- Un seul binding peut produire environ quatre événements
- mise à jour de
volume.Spec - mise à jour de
volume.Status - mise à jour de
claim.Spec - mise à jour de
claim.Status
- mise à jour de
- Sans cache interne, un informer conservant un état obsolète pourrait tenter de corriger à nouveau un binding déjà terminé
- Dans ce cas, une nouvelle écriture vers l’API server peut provoquer un conflit de version avec l’objet déjà stocké
File de travail et contraintes de concurrence
- Le contrôleur dispose de workqueues séparées pour traiter claims et volumes
claimQueuevolumeQueue
- Chaque file doit avoir exactement un seul thread worker
- En particulier,
syncClaim()n’est pas réentrant - Si deux
syncClaim()s’exécutent en même temps, les problèmes suivants peuvent apparaître- deux claims différents bindés au même volume
- un même claim bindé à deux volumes
- Le contrôleur peut récupérer ces situations via les erreurs de version de l’API server et ses propres vérifications, mais une approche multi-worker peut ralentir l’ensemble
syncClaim : point d’entrée de la synchronisation PVC
syncClaimest la méthode principale appelée lors de la création, mise à jour ou synchronisation périodique d’un claim- Cette méthode ne distingue pas le type d’événement
- Elle commence par définir sur le PVC la bonne migration annotation et le met à jour sur l’API server si nécessaire
- Elle bifurque ensuite selon la présence de l’annotation
AnnBindCompleted- sans annotation :
syncUnboundClaim - avec annotation :
syncBoundClaim
- sans annotation :
- Le traitement réel est séparé entre les méthodes pour claims non bindés et bindés afin d’améliorer la lisibilité
checkVolumeSatisfyClaim : vérification des exigences du PV
checkVolumeSatisfyClaimvérifie que le PV demandé satisfait les exigences du PVC- Les conditions vérifiées sont listées explicitement dans le code
- erreur si le PV a un
DeletionTimestamp - erreur si la capacité du PV est inférieure à celle demandée par le PVC
- erreur si
storageClassNameest différent - si le feature gate
VolumeAttributesClassest activé, vérification de la correspondance deVolumeAttributesClassName - si le feature gate est désactivé mais que le claim ou le volume possède
VolumeAttributesClassName, erreur - erreur si
volumeModen’est pas compatible - erreur si le mode d’accès n’est pas compatible
- erreur si le PV a un
- Si toutes les conditions sont satisfaites, la fonction renvoie
nil
Gestion des événements pour les PVC en delayed binding
emitEventForUnboundDelayBindingClaimcrée un événement informatif pour un claim non bindé en mode delayed binding- La raison par défaut est
WaitForFirstConsumer - Le message par défaut indique que le binding attend la création du premier consumer
- Si un Pod non encore planifié référence ce PVC, la raison devient
WaitForPodScheduled- s’il y a plusieurs Pods, le message inclut tous leurs noms
- le volume scheduling ne prend en compte qu’un seul Pod, mais comme on ne sait pas lequel sera utilisé, ils sont tous mentionnés
syncUnboundClaim : traitement d’un PVC pas encore bindé
- Si
claim.Spec.VolumeNameest vide, cela signifie que l’utilisateur ne demande pas de PV spécifique - Dans ce cas, le contrôleur vérifie le mode de delayed binding du claim et cherche le PV le plus adapté avec
findBestMatchForClaim - S’il n’existe pas de PV adéquat, le traitement suit cet ordre
- si une
StorageClasspar défaut peut être attribuée, le PVC est mis à jour et la synchronisation s’arrête - s’il s’agit d’un delayed binding et que le provisioning n’est pas encore en cours, un événement d’attente est émis
- si le claim a une
StorageClass, tentative de provisioning dynamique avecprovisionClaim - sinon, enregistrement d’un événement
FailedBindingindiquant qu’il n’y a ni PV disponible niStorageClass
- si une
- Si un PV adapté existe,
bindest appelé pour binder le PV et le PVC- en cas de succès, des métriques sur le provisioning + binding sont enregistrées et le cache de timestamps est nettoyé
- si une erreur survient pendant l’enregistrement, un
syncClaimultérieur terminera le binding
Traitement d’un PVC qui demande un PV spécifique
- Si
claim.Spec.VolumeNamen’est pas vide, l’utilisateur a demandé un PV spécifique - Si le PV demandé n’existe pas dans le cache, le statut du PVC est mis à
Pendingpuis réessayé plus tard - Si le PV demandé existe et que
volume.Spec.ClaimRefest vide, cela signifie que le PV n’est pas encore réclamé- les exigences sont vérifiées avec
checkVolumeSatisfyClaim - si elles ne sont pas satisfaites, un événement
VolumeMismatchest enregistré et le PVC reste enPending - si elles sont satisfaites,
bindest appelé
- les exigences sont vérifiées avec
- Si le PV demandé est déjà réclamé par ce PVC,
bindest appelé pour finaliser le binding - Si le PV demandé est déjà lié à un autre claim, le traitement est le suivant
- si le claim n’a pas l’annotation indiquant un binding effectué par le contrôleur, un événement
FailedBindingest enregistré et le claim reste enPending - si le binding semble avoir été fait par le contrôleur mais vers un autre claim, une erreur est renvoyée car c’est un état « should never happen »
- si le claim n’a pas l’annotation indiquant un binding effectué par le contrôleur, un événement
syncBoundClaim : traitement d’un PVC déjà bindé
syncBoundClaimtraite les PVC qui possèdent l’annotationAnnBindCompleted- Si un claim déjà bindé a un
claim.Spec.VolumeNamevide, son état devientClaimLost- le message d’événement indique que le claim bindé a perdu sa référence PV et que les données du volume sont perdues
- Si le PV pointé par le claim n’existe pas, l’état devient également
ClaimLost- le message indique que le claim bindé a perdu son
PersistentVolumeet que les données sont perdues
- le message indique que le claim bindé a perdu son
- Si le PV existe mais que
volume.Spec.ClaimRefest vide, le volume est considéré comme redevenu non bindé etbindest rappelé - Si l’UID de
PV.ClaimRefcorrespond à celui du claim, le binding est considéré comme correct etbindest appelé- dans la plupart des cas, cet appel ne fait rien
- Si le PV pointe vers un autre claimant, la phase du claim passe à l’état terminal
Lost
syncVolume : point d’entrée de la synchronisation PV
syncVolumeest la méthode principale appelée lors de la création, mise à jour ou synchronisation périodique d’un volume- Elle ne distingue pas non plus le type d’événement
- Elle commence par définir sur le PV la bonne migration annotation et le finalizer approprié, puis met à jour l’API server si nécessaire
- Si
volume.Spec.ClaimRefest vide, le volume est considéré comme inutilisé et sa phase passe àAvailable - Si
ClaimRefexiste mais que son UID est vide, le PV est considéré comme réservé pour un PVC spécifique et sa phase passe àAvailable- le PVC n’est pas encore bindé à ce PV, et ce sera traité par la synchronisation PVC
Traitement d’un PV dont le claim est introuvable
- Si un PV est bindé à un claim, le contrôleur cherche le PVC via le namespace/name de
ClaimRef - Si le PVC n’est pas trouvé dans le cache, des vérifications supplémentaires sont effectuées dans certaines conditions
- nouvelle vérification dans le cache de l’informer
- nouvelle vérification sur l’API server
- Pour les PV créés par un provisioner PV externe ou un binder PV externe, le PVC peut ne pas encore être synchronisé dans le cache local sous forte charge
- Une double vérification est donc effectuée afin d’éviter de reclaim par erreur un PV
- Si le claim est considéré comme absent, la phase du volume passe à
ReleasedpuisreclaimVolumeest exécuté- si la phase existante est
Failed, elle n’est pas écrasée - si la policy de reclaim est
Retain, un log indique que le PV référence un claim inexistant
- si la phase existante est
Quand le lien PV/PVC est désaligné
- Si le claim existe mais que
claim.Spec.VolumeNameest vide, cela signifie que le PVC n’a pas encore le nom du PV - Si
volumeModene correspond pas, un événementVolumeMismatchest enregistré des deux côtés, PV et PVC, etsyncClaimest ignoré - En l’absence de mismatch, le claim est ajouté à
claimQueueafin quesyncClaimsoit appelé rapidement- cette méthode accélère le binding des volumes provisionnés
- Si
claim.Spec.VolumeNameest égal au nom du volume courant, le binding est considéré comme correct et la phase du volume passe àBound - Si le claim est bindé à un autre volume, le traitement dépend de la situation
- pour un volume provisionné dynamiquement avec une policy de reclaim
Delete, il est marquéReleasedpuisreclaimVolumeest exécuté - pour un volume bindé par le contrôleur, le nettoyage se fait via
unbindVolume - si le pointeur a été créé par l’utilisateur, il est conservé, mais
unbindVolumeest appelé pour mettre à jour la phase et viderClaimRef.UID
- pour un volume provisionné dynamiquement avec une policy de reclaim
Mise à jour d’état et émission d’événements
updateClaimStatusenregistre le status du PVC sur l’API server- changement de phase
- quand il n’y a pas de volume : réinitialisation de
AccessModes,Capacity,CurrentVolumeAttributesClassName - quand il y a un volume : mise à jour du mode d’accès, de la capacité et du nom courant de la classe d’attributs de volume
- La capacité n’est mise à jour qu’au moment où le claim passe à
Bound- la différence entre la taille du système de fichiers du PVC et celle du périphérique bloc du PV peut être intentionnelle ; on n’écrase donc pas la capacité d’un claim déjà bindé
- Si le feature gate
VolumeAttributesClassest activé,CurrentVolumeAttributesClassNameest défini pendant la transition dePendingversBound- ensuite, cela doit être géré par le resizer ou par un override administrateur ; si le contrôleur continuait à l’écrire, cela pourrait créer une race condition
updateClaimStatusWithEventetupdateVolumePhaseWithEventn’émettent des événements que si le status/la phase a réellement changé
Attribution de la StorageClass par défaut
assignDefaultStorageClassrecherche et attribue uneStorageClasspar défaut lorsqu’un claim n’a pas de classe de stockage- Les claims qui ont déjà une storage class sont ignorés
- S’il n’existe pas de classe par défaut, la fonction ne met rien à jour et renvoie
false - S’il en existe une, son nom est placé dans
claim.Spec.StorageClassNamepuis enregistré via l’API server
Portée du fichier et limites explicites
- D’après les métadonnées affichées sur GitHub,
pv_controller.gofait 2038 lignes, 1864 LOC, 91 KB - Le contenu fourni ici ne va que du début du fichier jusqu’au début de la fonction
bindVolumeToClaim, le reste se poursuivant via un lien vers la vue brute - Ce résumé se limite donc à la structure du contrôleur, aux commentaires de conception, aux principales branches de synchronisation et à la logique de mise à jour d’état visibles dans le code fourni
1 commentaires
Avis de Hacker News
Je ne sais pas s’il est étrange que le code de ce fichier me semble vraiment être du code Go ordinaire. Comme c’est du Go, c’est verbeux, et comme il ne s’appuie pas sur des abstractions profondes, il paraît plus long, mais le code lui-même me semble typique.
L’abstraction est une arme à double tranchant, donc cette approche me paraît acceptable ; sans l’en-tête, je n’aurais probablement pas réfléchi deux fois au style d’écriture. C’est peut-être une différence liée au fait d’avoir plus d’expérience en logiciel d’entreprise qu’en logiciel système. Pour quelqu’un qui contribue régulièrement à Kubernetes, ces commentaires peuvent sembler inutiles, mais pour du code qui sera lu dans un contexte d’entreprise, par un lecteur lointain dans le futur et sans contexte, j’aurais même probablement ajouté davantage de commentaires à ce niveau de complexité.
Avant, ce genre de code paraissait normal, mais depuis environ 10 ans, beaucoup de gens semblent valoriser la concision davantage que l’explicite.
Surtout dans du code aussi important, je préfère de loin l’explicite. À plusieurs reprises dans ma carrière, à cause de code qui regroupait plusieurs conditions et omettait les commentaires expliquant le contexte métier et le sens, je n’ai pas pu déterminer si le comportement actuel était intentionnel ou accidentel. Cette approche tend à produire non pas du code robuste au changement, mais du code qui empêche le changement, ou du moins qui le rend difficile à modifier par quelqu’un d’autre que l’auteur. Créer une barrière de Chesterton inutile va à l’encontre de la maintenabilité.
Ce commentaire a probablement été ajouté après une tentative ratée de simplification du code, comme avertissement aux futurs mainteneurs pour qu’ils y réfléchissent à deux fois avant de tenter la même chose.
Le commit qui ajoute l’avertissement est "Add note about space-shuttle code style"[1], et le commit juste avant était "Revert controller/volume: simplify sync logic in syncUnboundClaim"[2].
[1] https://github.com/kubernetes/kubernetes/commit/de4d193d45f6...
[2] https://github.com/kubernetes/kubernetes/commit/8a1baa4d64ca...
Je pensais à peu près la même chose, puis j’ai vu les gros blocs
ifimbriqués et j’ai changé d’avis. Pour cette partie-là, j’aurais clairement créé des branches avec retour anticipé.On dirait qu’ils se sont arrêtés à la première étape de « le faire fonctionner, le rendre rapide, le rendre joli », sans passer à « le rendre joli ». J’ai déjà écrit du code laid et très commenté de ce genre pour démêler des interactions d’état délicates, mais en général je le nettoie un peu avant la revue. Peut-être qu’il vaudrait mieux simplement mettre une grande bannière en haut du fichier disant « ne pas essayer de simplifier ce code ». Cela dit, ce n’est clairement pas si mauvais.
C’est peut-être étrange, mais tu n’es pas seul. Pour moi aussi, ce code a l’air tout à fait normal. J’ai déjà écrit ce genre de code et de commentaires pour des composants que je considérais comme critiques pour la fiabilité du système.
Je n’ai jamais adhéré à la mode du « code sans commentaires », et les commentaires que j’avais écrits se sont trop souvent révélés précieux pour mon moi futur quand je revenais dessus des mois ou des années plus tard. J’ai du mal à imaginer reconstituer la logique enfouie dans un composant de cette complexité sans commentaires solides.
En particulier, l’affirmation selon laquelle chaque
ifaurait un commentaireelsecorrespondant ne semble pas être toujours vraie. Une grande partie desifsans correspondant sont de simples testsif (err != nil) {ou d’autres retours anticipés, mais même en les excluant, il semble rester desifsans correspondant.Cela dit, d’après mon expérience du logiciel d’entreprise, les commentaires supplémentaires n’étaient pas forcément si nombreux. Les bases de code étaient infestées de commentaires
// end if, mais les vrais commentaires explicatifs étaient rares.Article sur la qualité du logiciel de la navette spatiale : https://archive.is/HX7n4
Pour en citer un extrait, ce qui rend ce logiciel remarquable n’est pas la quantité de choses qu’il fait, mais à quel point il fonctionne bien. Il ne plante jamais, n’a pas besoin de redémarrage, n’a pas de bugs et serait aussi proche de la perfection que les humains aient réussi à l’atteindre. Les trois dernières versions comptaient chacune 420 000 lignes et n’avaient qu’une erreur chacune ; les 11 dernières versions au total comptaient 17 erreurs. Un programme commercial de complexité équivalente aurait eu environ 5 000 erreurs, selon l’article.
Elle est si coûteuse et si lente que prouver la correction du logiciel avec des assistants de preuve modernes serait beaucoup moins cher, plus rapide et en pratique plus sûr. Des projets comme seL4 et CompCert montrent comment faire.
Je comprends l’intention derrière
// KEEP THE SPACE SHUTTLE FLYING., mais c’est un peu drôle de faire référence dans un commentaire à un système qui n’est plus exploité parce que son bilan de sécurité n’était pas bon.Dans une dizaine d’années, les gens se souviendront-ils encore de la navette spatiale en bien ?
Les problèmes de sécurité de la navette spatiale étaient pour l’essentiel des problèmes matériels, pas des problèmes logiciels.
Dans « Appendix F - Personal Observations on Reliability of Shuttle » [0], l’annexe de Richard Feynman au rapport sur l’accident de Challenger en 1986, il écrit ceci :
Il a spécifiquement mis en avant la qualité du logiciel avionique comme exemple du fait que même un grand projet gouvernemental complexe comme la navette pouvait être correctement ingénieré, sans être intrinsèquement condamné à la mauvaise qualité et au danger.
0 : https://www.nasa.gov/history/rogersrep/v2appf.htm
Il a envoyé des personnes et du matériel dans l’espace, puis les a ramenés à la maison, au cours de bien plus de 100 missions réussies. On en garde encore une image positive, et il y a de fortes chances que cela continue. Du point de vue du progrès humain et de l’effet net, c’était une réussite.
Ce qui a mis fin à la navette, ce n’est pas un mauvais bilan de sécurité, mais le coût et l’anticipation d’une dégradation future de la sécurité.
Les deux accidents de la navette ont certes tué plus d’astronautes que toute autre catastrophe de la NASA, mais compte tenu de la difficulté réelle de ce qui était accompli, le bilan de sécurité était vraiment remarquable. Le code paraît très bon.
La situation de la Space Shuttle est plus complexe que de dire simplement que sa sécurité était mauvaise. Si l’on raisonne par mission, son bilan est plutôt meilleur que celui d’autres lanceurs. La navette a connu 2 missions fatales sur 135, tandis que le Soyouz de l’époque soviétique en a connu 2 sur 66, et SpaceShipTwo affiche un bilan effrayant de 1 mission fatale en seulement 12 vols.
Cela dit, la Space Shuttle avait une capacité d’équipage bien supérieure à ce qui était nécessaire pour la plupart des missions. Contrairement à Apollo ou Soyouz, limités à 3 personnes, elle pouvait en embarquer jusqu’à 8 ; et si l’on considère que la plupart des missions soviétiques/Roscosmos, ESA et CNSA étaient entièrement autonomes et sans équipage, il n’y avait tout simplement pas d’équipage à mettre en danger. Peut-être que cette analogie convient mieux à Kubernetes : un système très ingénieré, puissant et polyvalent, mais qui demande beaucoup d’attention et qui est probablement utilisé un peu plus qu’il ne le faudrait.
Selon la mesure la plus courante, celle des passagers-milles, la Space Shuttle fait partie des véhicules les plus sûrs jamais construits et ayant volé.
En tant que personne dont l’enfance s’est précisément déroulée dans les années 1980, je me demande franchement comment on pourrait ne pas en garder un bon souvenir. Est-ce que certains sont trop jeunes pour voir ce programme, toutes ses missions et tous ses accomplissements autrement que rétrospectivement, avec un regard teinté par l’ambiance actuelle centrée sur les prestataires spatiaux privés ?
Le récit de Richard Hipp sur l’adaptation du code de SQLite aux normes aéronautiques est également assez intéressant : https://corecursive.com/066-sqlite-with-richard-hipp/#testin...
Ce passage me fait penser aux vérifications d’exhaustivité dans le code TypeScript. J’essaie toujours de les utiliser.
https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
Le plus récent
satisfies neverest très adapté à cet usage. C’est aussi pratique si, par préférence, on utilise des chaînesif else.ts-patternpourrait aussi vous plaire.https://github.com/gvergnaud/ts-pattern
Si l’on ne considère que les cas où chaque
ifnon totalement trivial reçoit unelseexplicite, je me demande à quel point ce code aurait été simplifié si les auteurs de Kubernetes avaient conçu le tout autour du pattern matching structurel plutôt qu’avec des blocsif/else.Plusieurs langages grand public qui prennent en charge le pattern matching structurel disposent d’outils capables de vérifier à la compilation que le matching est exhaustif ; cela seul peut augmenter la densité d’information du code tout en constituant une solution idiomatique.
Discussion de 2018 : https://news.ycombinator.com/item?id=18772873
Je n’ai fait que parcourir rapidement le code, mais franchement il ne me paraît pas si mauvais. Il y a des choses que j’aurais faites autrement, mais j’ai vu beaucoup de code bien pire.
Au moins, ce code suit une règle, tout semble avoir été écrit après réflexion, et on a l’impression qu’il y a une méthode dans ce chaos. Je choisirais sans hésiter ce genre de code plutôt que le fouillis typique que j’ai vu tant de fois, fait de styles mélangés, de code paresseux et de structures illogiques.
Je me demande pourquoi on invente de nouvelles pratiques de « sécurité » tout en ignorant les bonnes pratiques de génie logiciel documentées.
Les modules de 2 000 lignes, les méthodes de 200 lignes et les
ifimbriqués sur 3 ou 4 niveaux sont considérés comme nocifs. Les commentaires qui disent seulement ce que fait le code, et non pourquoi, ne sont pas utiles et risquent de diverger du code réel. On voit aussi des usages inutiles denil. Sans même entrer dans des problèmes plus profonds comme le couplage ou le principe de responsabilité unique, ces points apparaissent déjà en surface.Si vous pensez que ces choses sont nocives, je vous recommande de lire « John Carmack on Inlined Code ».
http://number-none.com/blow/john_carmack_on_inlined_code.htm...
« Le code de contrôle de vol de la fusée d’Armadillo ne faisait que quelques milliers de lignes, alors j’ai pris la fonction principale tic et j’ai commencé à mettre toutes les sous-routines en ligne. Je ne peux pas dire que j’ai trouvé un bug caché susceptible de provoquer un vrai crash, mais j’ai trouvé quelques variables assignées plusieurs fois et quelques flux de contrôle qui semblaient un peu suspects, et le code final était plus petit et plus propre. »
Si Carmack a trouvé de la valeur dans cette approche, il ne faut probablement pas l’écarter trop vite. Le commentaire de suivi vaut aussi le détour.
« Dans les années qui ont suivi l’écriture de ce texte, je suis devenu beaucoup plus favorable, même en C/C++, à la programmation fonctionnelle pure dans des limites raisonnables... Quand cela devient difficile à gérer, cherchez un moyen d’extraire un bloc en fonction pure. »
Parfois, c’est un cas de « il n’y a pas d’autre solution(TM) ».
Les limites arbitraires de nombre de lignes ont tendance à créer une fragmentation inutile. Ajoutez les include, les licences, le code de glue et les commentaires, et vous obtenez des spaghettis difficiles à appréhender. Essayez de maintenir des méthodes à 200 lignes dans du code haute performance, et les performances peuvent chuter comme le vol d’Icare.
En lisant les commentaires du code, on voit qu’une énorme quantité de savoir-faire a été intégrée pour simplifier ce code en un module unique, le rendre accessible et, plus important encore, durable. Pour quelqu’un qui ne connaît pas le langage ou la logique, les commentaires qui donnent une vue d’ensemble de ce que fait le code sont très utiles. Six mois plus tard, même son propre code paraît étranger, donc c’est aussi utile pour soi-même.
J’ai écrit assez longtemps de cette manière « sûre », mais cela m’a fait produire beaucoup plus de bugs qu’une gestion des erreurs en chemin de fer avec retours anticipés, et il m’a aussi fallu bien plus de temps pour les corriger
Ajouter un
elseexplicite à chaque blociffait exploser la complexité, car il faut garder en mémoire le contexte courant. Je pense qu’il serait raisonnable de remplacer cette règle par : « chaque bloc de conditionifdoit soit effectuer un retour anticipé, soit avoir un blocelsecorrespondant ». Le motifif (cond) { traitement spécial }est clairement bien plus dangereux que le retour anticipé et rend le raisonnement plus difficile.Il n’existe pas un unique ensemble officiel de bonnes pratiques
La longueur d’une fonction ou le nombre de lignes de code d’un fichier ne sont pas intrinsèquement nuisibles ni bénéfiques. Chaque langage a sa propre vision de la façon d’organiser le code, mais aucune ne peut prétendre être « la meilleure pratique ». Go n’est pas un langage qui privilégie le découpage du code en une multitude de petits fichiers.
Une méthode de 200 lignes n’est pas intrinsèquement mauvaise. Si le code interne est linéaire et reste au même niveau d’abstraction, cela peut être le meilleur choix
Créer à la place 40 méthodes de 5 lignes peut être pire. Pour comprendre l’ensemble, il faut sauter d’un endroit à l’autre, et l’on peut aussi se tromper dans l’ordre des appels. Il y a 40! permutations possibles.
Ce genre de code semble être un candidat idéal pour être déplacé vers un système déclaratif, fondé sur des règles et piloté par des tables
Cette approche est plus facile à comprendre et à vérifier qu’un code impératif bricolé, saturé de clauses
if. Ce type de code désordonné est généralement le signe qu’il manque une abstraction.