1 points par GN⁺ 2024-06-10 | 2 commentaires | Partager sur WhatsApp
  • Une startup qui venait d’activer la monétisation a subi une panne de paiement des abonnements, mais comme elle n’était pas reproductible en interne, l’identification de la cause a pris 5 jours
  • Le problème est parti de la copie d’un format de conversion Prisma/TypeScript→Python/SQLAlchemy généré par ChatGPT, où une chaîne d’ID codée en dur s’est retrouvée utilisée comme valeur par défaut au lieu d’une fonction de génération d’UUID
  • En raison d’une architecture composée de 8 tâches AWS ECS avec 5 instances chacune, les utilisateurs tombaient sur l’un d’un maximum de 40 pools d’ID uniques, et le problème était masqué en journée par des déploiements fréquents
  • La nuit, lorsque les déploiements s’arrêtaient, l’ID unique de chaque serveur s’épuisait, puis les nouvelles tentatives d’abonnement échouaient à cause d’une collision d’ID unique
  • Sur la base de 50 plaintes par jour, pendant 5 jours, et d’un abonnement à 40 $/mois, la perte est estimée à 10 000 $ de revenus mensuels ; l’absence de tests, de logs et d’alertes, ainsi que la copie de code, ont amplifié la gestion de l’incident

Une panne d’abonnement révélée juste après la monétisation

  • La startup a activé pour la première fois la monétisation en mai et a obtenu son premier client moins d’une heure après le lancement
  • Le lendemain matin, plus de 40 plaintes d’utilisateurs s’étaient accumulées dans Gmail
    • Les utilisateurs ne pouvaient pas finaliser leur abonnement
    • Ils signalaient qu’un spinner de chargement infini apparaissait après avoir cliqué sur le bouton d’abonnement
  • L’équipe a créé un nouveau compte pour vérifier elle-même, mais l’abonnement fonctionnait normalement en interne et la cause n’a pas pu être reproduite
  • Pendant les heures de bureau, il y avait très peu de plaintes, et l’incident s’accumulait surtout pendant la nuit

Mise en place de la monétisation sous pression

  • Le mois de mai correspondait au début du batch YC S23, et l’équipe n’était pas sûre de la meilleure direction à prendre après le lancement
  • Dalton, group partner chez YC, a conseillé de prendre les abonnés payants comme indicateur pour décider de la direction, et de doubler le prix mensuel envisagé
  • Le prix final a été fixé à 40 $ par mois
  • Le projet était à l’origine un full-stack NextJS, mais une migration vers Python/FastAPI a été menée autour du travail de monétisation
    • ChatGPT a été utilisé pendant la migration
    • L’intégration de Stripe a également été finalisée
  • Pendant les 5 jours qui ont suivi, le sommeil a fortement diminué et il a fallu traiter chaque jour 30 à 50 e-mails de plainte

Le format de conversion de modèles généré par ChatGPT

  • Lors de la migration du backend, les modèles de base de données ont été déplacés de Prisma/TypeScript vers Python/SQLAlchemy
  • La conversion des modèles était fastidieuse et, ChatGPT semblant bien s’en charger, il a été utilisé pour presque toute la migration
  • Le code généré a été copié-collé puis vérifié en fonctionnement, et comme il semblait normal en production, l’équipe a continué ainsi
  • À ce moment-là, les insertions en base de données étaient encore gérées par l’API Next, et le backend Python ne faisait que lire la base
  • En implémentant la fonctionnalité d’abonnement, l’équipe a commencé pour la première fois à insérer des enregistrements en base depuis Python
    • Le nouveau modèle SQLAlchemy a été créé manuellement, mais le format généré par ChatGPT dans les modèles existants a été copié tel quel
    • Le même problème s’est retrouvé dans la méthode de génération d’ID de tous les modèles

La vraie cause et pourquoi elle ne se voyait pas en journée

  • L’erreur centrale était de ne pas passer une fonction ou une lambda générant un UUID, mais une unique chaîne d’ID codée en dur
  • Une fois qu’un utilisateur finalisait un abonnement avec cet ID sur une instance backend donnée, les tentatives d’abonnement suivantes sur la même instance provoquaient une collision d’ID unique
  • La configuration du backend a masqué le problème plus longtemps
    • 8 tâches ECS tournaient sur AWS
    • Chaque tâche exécutait 5 instances backend
    • Les utilisateurs pouvaient potentiellement arriver sur l’un de 40 ID différents
  • En journée, 10 à 20 commits étaient faits directement chaque jour sur la branche main, déclenchant à chaque fois un nouveau déploiement backend
    • À chaque déploiement, 40 nouveaux ID devenaient disponibles pour les clients
  • La nuit, lorsque les commits et les déploiements s’arrêtaient, l’ID unique de chaque serveur s’épuisait rapidement
    • Au début, près de 40 serveurs permettaient encore de s’abonner, mais au fil du temps ce nombre tombait presque à 0

Montant de la perte et mesures prises ensuite

  • La perte a été calculée comme 50 emails/day x 5 days x $40/month, soit une estimation de 10 000 $ de chiffre d’affaires mensuel perdu
    • Ce calcul ne prend en compte que les utilisateurs ayant envoyé une plainte
  • Il a fallu 5 jours, de très nombreux e-mails, des centaines de logs Sentry, une longue conversation Discord avec un ingénieur Stripe et la revue de cinq fichiers clés pour trouver la cause
  • Une fois la cause découverte, Adam a rapidement publié un correctif
  • Ensuite, de solides tests unitaires/d’intégration, des alertes et du logging ont été ajoutés
  • Cet incident montre que, lorsque se combinent erreur humaine, manque de tests, copie de code et push direct sur main, même une petite ligne peut entraîner une importante perte de chiffre d’affaires

2 commentaires

 
znjadong 2024-06-11

Hein, le code généré automatiquement par une IA doit évidemment être relu, pourquoi l’utiliser tel quel ?

 
GN⁺ 2024-06-10
Avis de Hacker News
  • C’est l’absence de monitoring qui a fait perdre 10 000 dollars. L’application générait en continu, en grand nombre, des exceptions de base de données, mais personne n’a reçu d’alerte
    S’il y avait eu ce type d’alerte, l’enquête aurait pris 5 minutes au lieu de 5 jours. Si le système d’alerte n’a pas été corrigé, alors en réalité rien n’a été corrigé

    • Exact. Le message de log aurait probablement indiqué que l’ID n’était pas unique, ce qui aurait énormément réduit le temps de débogage
      Programmer est facile quand tout fonctionne bien ; ce qui est difficile, c’est de gérer les problèmes
    • Ce genre de chose devient de plus en plus courant. Les entreprises et les fondateurs croient que le fournisseur cloud qu’ils ont choisi va tout faire comme par magie, et ne réfléchissent pas à l’infrastructure
      Dès qu’il y a des clients payants, il faut quelqu’un qui ait les connaissances et l’expérience pour gérer les logs, le monitoring, les alertes, la sécurité, etc. Il ne faut pas traiter le DevOps en amateur
    • L’absence de tests est un problème, et utiliser du code généré par l’IA sans le vérifier trois fois est aussi risqué
      Mais le vrai point délirant, c’est qu’il n’y avait ni journalisation des erreurs ni alertes sur la base de données. Ce n’est pas du vieux code legacy vieux de 20 ans, c’est un nouveau produit, et ce n’est pas non plus du code d’une époque où l’on utilisait les erreurs DB comme validation de données
    • Le fait d’avoir déployé puis d’être allé dormir tout de suite ressemble ici à un signal d’alerte. Il aurait fallu déployer vers 9 h du matin et surveiller les problèmes pendant les heures de bureau
    • On dirait que ChatGPT n’a pas dit qu’il fallait du monitoring
  • Le billet de blog renvoie un 404, donc voici le lien Web Archive
    https://web.archive.org/web/20240610032818/https://asim.bear...
    L’auteur a ajouté une correction importante : les pratiques décrites ici étaient très mauvaises et franchement embarrassantes, et depuis, ils ont ajouté des tests unitaires/d’intégration solides ainsi que des alertes/logs. En fin de compte, c’était une erreur humaine, et avec le recul, quelque chose d’évidemment évitable
    Il a aussi ajouté que cela s’était produit pendant les toutes premières semaines de l’entreprise, sous une forte pression temporelle, et qu’il fallait voir cela comme une histoire amusante sur une reproductibilité étrange d’un bug en production

  • L’erreur sautait aux yeux. J’ai du respect pour l’équipe, mais cela n’a pas grand-chose à voir avec ChatGPT ; c’est surtout lié au fait qu’ils ont utilisé un modèle de programmation qu’ils ne maîtrisaient pas suffisamment
    Même si le code avait passé la code review, il y a de fortes chances que cela aurait été détecté avec de simples outils de monitoring configurables en cinq minutes

    • Pour être honnête, si je n’avais pas cherché ce bug précisément, je pense que je ne l’aurais pas vu. Cela dit, il est vrai qu’avec du monitoring ou même les tests manuels les plus basiques, il aurait été repéré immédiatement
    • On a aussi l’impression que l’équipe n’était même pas capable de faire un dépannage de base à partir des logs de base de données ou des logs applicatifs. C’était une erreur simple, et ça inquiète de voir comment ils réagiraient face à des erreurs transitoires comme un verrouillage implicite de table
    • Ce n’est pas une erreur naïve : le titre est délibérément putaclic et optimisé pour le référencement. Il suggère que ChatGPT a fait une erreur afin de susciter des clics anxieux
      Un titre comme « Une erreur de programmation commise en utilisant un LLM, faute d’assurance qualité, a coûté 10 000 dollars » ne provoquerait pas chez les dirigeants une réaction du type « Si ChatGPT sabote quelque chose, quel est notre risque financier ? ». Il va y avoir énormément de managers intermédiaires et supérieurs qui vont partager cet article sur LinkedIn
      Un LLM ne peut pas « faire d’erreur ». Ce n’est pas déterministe, cela ne peut ni raisonner, ni penser, ni exécuter une logique. C’est un générateur de salade de mots statistique extrêmement sophistiqué, et comme il n’y a aucune garantie que la sortie soit correcte ou exacte, parler d’« erreur » n’est même pas approprié par définition
      Édition : après avoir été massivement downvoté pour des raisons évidentes, l’article a soudainement grimpé dans le classement, ce qui laisse penser qu’un modérateur l’a boosté : https://hnrankings.info/40627558/
      C’est assez drôle qu’un article suffisamment putaclic pour justifier un changement de titre selon les règles ait été boosté par un modérateur. Et le fait que l’auteur semble appartenir à une société Y Combinator est sûrement une pure coïncidence : https://news.ycombinator.com/item?id=40629998
    • Fait intéressant, l’autre entité qui a trouvé cette erreur était ChatGPT-4o. Je ne peux pas partager le chat avec images, mais j’ai collé une image du code incorrect et demandé « qu’est-ce qui ne va pas dans ce code ? », et voici ce qu’il m’a indiqué
      Il a expliqué que, pour la génération d’UUID de clé primaire, il fallait utiliser directement le callable uuid.uuid4 plutôt que str(uuid.uuid4()), parce que SQLAlchemy appelle la fonction lorsqu’il génère la valeur. Il a aussi dit que la valeur par défaut de date server_default=text("(now())") pouvait ne pas se comporter comme prévu et a recommandé func.now(), a suggéré de vérifier les imports de uuid et de text de SQLAlchemy, et a aussi proposé d’envisager DateTime(timezone=True) pour la gestion du fuseau horaire
      Ensuite, il a proposé comme correctif id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()), unique=True, nullable=False), et ici l’ajout de lambda: a corrigé le problème
    • Si l’on a très peu d’expérience en Python, on a pu interpréter uuid.uuid4() comme une définition de schéma dans Prisma ou quelque chose du genre. Donc le bug en lui-même ne me surprend pas, et j’aurais moi aussi pu faire la même erreur
      Malgré tout, un simple kubectl logs aurait suffi à le corriger immédiatement. Et puis passer de Next.js et Prisma à Python ? Pourquoi ?
  • L’erreur elle-même se comprend. Même sans ChatGPT, cela semble assez facile à laisser passer en écrivant le code
    En revanche, je ne comprends pas pourquoi elle n’a pas été détectée après le premier échec. Cette entreprise n’avait-elle aucun logging ? Le fait que le backend tentait de réutiliser un UUID aurait dû apparaître immédiatement dans l’erreur

    • Ils ne se sont même pas rendu compte qu’il y avait une erreur avant qu’un client ne se plaigne. Il faut savoir quelle erreur s’est produite avant le client, et du logging, des alertes, n’importe quelle forme de monitoring aurait aidé
    • C’est le vrai problème. Si c’est un projet qui avance vite, il y a de fortes chances qu’un autre bug de production de ce genre se reproduise un jour. Espérons simplement que la prochaine fois, il ne faudra pas cinq jours pour l’identifier
    • Cinq jours pour comprendre ce bug, c’est assez dingue
    • Que des cas particuliers, comme plusieurs abonnements Stripe, échappent à des tests unitaires classiques, c’est tout à fait plausible. Comme l’article le dit, cela n’aurait probablement pas été facile à reproduire avec des tests d’acceptation non plus, et se focaliser sur ChatGPT paraît excessif, aussi bien de la part de l’auteur que des autres
      Se tromper en passant une chaîne à une fonction qui attend un callable au lieu d’un String, c’est courant. Si l’on n’avait pas utilisé d’ORM, ce problème précis aurait probablement été évité, mais c’est peut-être juste mon biais personnel contre les ORM. Des bugs comparables peuvent très bien survenir dans d’autres contextes que les bases de données
      Ceux qui affirment avec aplomb qu’ils auraient forcément trouvé ce bug sont soit de bien meilleurs ingénieurs que moi, soit, plus probablement, légèrement dans l’illusion quant à leurs propres capacités
      En revanche, l’absence de logs, ou le fait de ne pas les avoir consultés, est vraiment difficile à comprendre. Avec ECS, j’aurais pensé qu’une exception Duplicate Key serait remontée vers CloudWatch sans configuration particulière ; je me demande donc si cela n’a pas été le cas, ou si cela l’a été mais que personne n’a vérifié pendant la nuit quelles exceptions s’étaient produites
    • C’est à cause de ce type d’échec de processus qu’Amazon a un processus de correction des erreurs, c’est-à-dire de post-mortem : https://aws.amazon.com/blogs/mt/why-you-should-develop-a-cor...
      Dans une telle situation, il est utile de se demander pourquoi la détection a été tardive et pourquoi le diagnostic a pris si longtemps
  • J’ai vu plusieurs fois la même erreur dans du code écrit par des humains aussi. Surtout en React / TypeScript / JavaScript, il arrive souvent que quelqu’un oublie une lambda
    J’ai l’impression que l’article de blog n’explique pas correctement la cause profonde du problème et passe directement à la faute de ChatGPT. Quand on travaille dans l’urgence et qu’on envoie sur la branche principale un commit avec de gros changements ou sans revue par des collègues, ce genre de chose arrive
    Le vrai problème, c’est que si on se précipite, qu’on prend des raccourcis et qu’on ne fait pas assez de tests ni de revue de code par les pairs, des erreurs se produisent. Il semble qu’un simple test essayant plusieurs options d’inscription aurait permis de le repérer immédiatement

    • Mon modèle mental de ChatGPT, c’est un ingénieur junior qui ne sera jamais promu. Quelqu’un qui finira par être licencié, mais qui peut taper à une vitesse infinie, donc qui peut être utile si on l’utilise avec énormément de prudence
      Si on place ce genre de personne près d’un code financièrement critique, on obtiendra des problèmes similaires, et je remettrais en question le jugement de la personne qui a décidé de déployer ce code avec presque aucun test
    • Ce type de problème est courant à plein d’endroits. Par exemple, les props de composants Vue peuvent avoir des valeurs par défaut, mais si on utilise comme valeur par défaut un objet ou tableau littéral au lieu d’une fonction qui retourne un objet ou un tableau, cela peut très mal tourner
      Je suis surpris qu’il n’y ait pas eu de règle de lint pour ce cas
    • ChatGPT n’est qu’un élément de distraction. Ce qui compte, ce n’est pas ce qui a généré le code, mais ce qu’on fait de ce code
    • Le code était écrit par ChatGPT, poussé par ChatGPT, puis relu ensuite par ChatGPT. /s
      J’espère que ce n’était pas le cas
  • Le passage « le projet était au départ un NextJS full-stack, mais je voulais d’abord tout migrer vers Python/FastAPI » ouvre les yeux
    Je ne vois pas comment une startup sans clients peut justifier une réécriture

    • Je pensais la même chose au point de me croire fou. Je trouvais qu’une réécriture aussi tôt n’avait aucun sens, et j’avais l’impression que personne dans les commentaires ne le relevait
      Qu’il y ait des clients ou non, je ne vois pas pourquoi faire, si tôt, ce qui est essentiellement un déplacement horizontal de Node vers Python. S’il y avait des centaines de clients et qu’on envisageait de passer à quelque chose comme Go, je pourrais à la rigueur le comprendre, mais même là j’aurais encore des doutes
    • En plus, ils étaient en train de réécrire dans un langage qu’ils maîtrisaient si peu qu’ils n’étaient même pas capables de reconnaître un bug pourtant évident
    • Dans mon cas, sur un vrai projet en cours, ce pourrait être parce que C# est un bon langage et que le runtime ainsi que le framework web sont corrects, mais qu’ils ralentissent la vitesse de développement et que les points de douleur s’accumulent sans cesse
      Par exemple, il faut créer une quantité énorme d’objets DTO, mais AutoMapper ne fonctionne pas avec la combinaison de versions et la configuration de projet que j’utilise, et Entity Framework comme la sérialisation/désérialisation JSON apportent plus de douleur que de bénéfices
      Bien sûr, on peut résoudre cela progressivement. En creusant profondément la documentation, en bricolant un peu, en mettant à niveau des paquets et en réécrivant la configuration. Mais en tant qu’humain, on a envie de prendre un bidon d’essence métaphorique, de tout incendier, puis de construire un deuxième système meilleur. Évidemment, en pratique, il n’est pas réellement meilleur, il crée juste d’autres points de douleur, et il se peut même qu’il ne fasse pas tout ce que faisait le premier système, ou pas correctement
      J’ai la même impulsion chaque fois que je vois un système legacy ou pénible au travail. Il faut un effort actif et constant pour résister à la partie du cerveau qui crie de tout réécrire. Parfois, une réécriture ou un changement d’architecture comme l’introduction de conteneurs fonctionne bien, mais le plus souvent cela mène aux flammes ou à un travail sans fin
      Sauf quand on a un haut niveau de confiance que cela améliorera l’exploitation du système ou l’expérience développeur des autres développeurs, il vaut mieux ne pas céder à cette impulsion
    • Dans cette histoire, le seul échec de ChatGPT est que, par sa capacité, il a fait croire qu’une réécriture avant le lancement était une manière raisonnable et facile de consommer la runway
  • ChatGPT a plutôt été ce qui a permis à l’app de générer de l’argent. Sans ChatGPT, ils n’auraient pas eu la capacité de l’implémenter
    Ce sont l’incapacité à coder, déboguer, journaliser et superviser qui ont fait perdre 10 000 dollars, et dans cette histoire ChatGPT a un effet net positif

    • Il suffit de regarder les projets de cette équipe sur github.com/reworkd pour voir immédiatement le niveau de maturité du produit et de l’équipe. C’est du développement piloté par les émojis
      Tous les messages de commit contiennent des émojis. Singes, bananes, fusées, feux d’artifice, il y a de tout
    • C’est encore plus vrai quand on considère que ça coûte 20 dollars par mois
    • 10 000 dollars, c’est de la petite monnaie. Elon pourrait perdre 500 milliards de dollars aujourd’hui à cause de la bourde xAI sortie ce jour
      https://grook.ai/share?id=e269e88a7b1a71eff4f176c864b30161&x...
    • L’implémentation existait déjà, donc ils semblaient bien avoir les compétences
      C’était à l’origine un NextJS full-stack, et ils migraient le backend vers Python/FastAPI en traduisant le modèle de base de données Prisma/Typescript en Python/SQLAlchemy. D’après eux, comme ce travail était ennuyeux et qu’ils avaient constaté que ChatGPT s’en sortait plutôt bien, ils l’ont utilisé pour presque toute la migration
      S’il n’y avait pas eu ChatGPT au départ, ils n’auraient probablement pas tenté cette migration préalable, donc il est difficile de dire que l’effet net est positif. L’ancienne stack avait peut-être une meilleure journalisation des erreurs, ou peut-être pas, et comme le code aurait été écrit directement par eux, ils en auraient peut-être mieux connu la structure et en auraient eu moins besoin
      La décision elle-même de « réécrire tout le code une seconde fois » avant même d’activer la monétisation est aussi intéressante
  • Il y a cette phrase : « Je veux d’abord dire que les pratiques ici étaient mauvaises et auraient pu être évitées. Cela s’est produit à une autre période où la pression temporelle était forte. Merci de lire cela en gardant ce point à l’esprit. »
    C’est ce genre de contraintes qui rend les abonnements logiciels inquiétants

    • Je respecte beaucoup le fait que l’auteur ait rendu cette histoire publique, surtout avec une telle introduction. Savoir quelles erreurs font les autres est vraiment utile, mais rendre publiques ses propres erreurs peut être assez embarrassant
    • Même si on n’aime pas les abonnements, l’ancien monde où l’on achetait des licences à plusieurs centaines de dollars par siège n’était pas formidable non plus
    • J’ai déjà travaillé avec du code legacy de souscription, et cela peut être assez sale
      Il m’est même arrivé de facturer un utilisateur deux fois à cause d’une condition de concurrence. Du coup, quand je vois un timeout ou une erreur liés à de l’argent, je deviens assez paranoïaque pour supposer d’abord que le paiement est passé, puis vérifier ensuite
    • L’alternative, c’est de l’écrire soi-même, mais alors toutes les autres contraintes s’accumulent
    • Faire une réécriture sous une « forte » pression temporelle est aussi inhabituel
  • Du code en TypeScript et en Python, des frameworks comme Next.js, 5 instances pour chacune de 8 tâches AWS, et au final 40 dollars de chiffre d’affaires pour seulement quelques semaines de développement ?
    Je me suis demandé ce qui avait bien pu se passer. Ils ont corrigé en disant que le code était en désordre à cause de contraintes de temps, mais c’est encore pire qu’ils aient passé du temps à refactorer entre langages et à construire un système distribué sans aucune raison
    C’était une complexité autodestructrice à jongler en même temps avec les fonctionnalités et une complexité technique absurde. Je ne comprends pas ce qu’ils avaient en tête
    Édition : c’est une boîte du batch été 2023 de YC, et il semble qu’à l’été 2024 le produit soit encore derrière une liste d’attente. Sans doute parce qu’ils sont en train de tout réécrire en Rust

    • C’est parce qu’ils avaient 500 000 dollars de financement initial, 1,2 million de plus par-dessus, et aussi des crédits AWS gratuits à brûler
  • On dirait qu’il n’a même pas écrit 1 000 lignes de Python au total, et pourtant il a bien identifié le problème
    Python a un défaut : il n’a pas correctement repris la stratégie d’évaluation de Common Lisp. Si, dans l’expression de valeur par défaut d’un argument optionnel, on a quelque chose comme foo=obj.whatever(), alors obj.whatever() est évalué non pas au moment de l’appel de la fonction, mais au moment où la définition de la fonction est traitée
    Je soupçonne que c’est intentionnel pour des raisons d’efficacité. Python a aussi un autre défaut : il n’existe pas de vraie syntaxe littérale pour des objets courants comme les listes. [1, 2, 3] n’est pas un littéral, mais quelque chose qui ressemble davantage à un constructeur, et à chaque évaluation il faut créer une nouvelle liste et y insérer les valeurs
    Le concepteur n’a probablement pas voulu qu’un paramètre comme list=[] crée une nouvelle liste vide à chaque fois qu’un argument est omis. En Lisp, '(1 2 3) et '() sont de vrais littéraux et pointent vers le même objet à chaque référence. Le programmeur peut choisir d’écrire (list 1 2 3) ou '(1 2 3) comme expression de valeur par défaut
    Le premier crée, comme [1, 2, 3], un nouvel objet mutable à chaque fois, tandis que le second renvoie presque certainement le même objet, qui ne peut pas être modifié de façon fiable et portable. Les langages populaires modernes ont récupéré la plupart des fonctionnalités de Lisp, si bien que ça ressemble à une blague disant qu’il n’y avait rien à perdre

    • Ce qui est dit est vrai, mais ce n’est pas ce qui s’est passé dans le billet de blog. Le problème n’était pas dans la définition de fonction, mais dans la définition de classe
    • L’affirmation selon laquelle, dans foo=obj.whatever(), obj.whatever() est évalué non pas au moment de l’appel de la fonction mais au moment du traitement de la définition de la fonction, ne semble pas pouvoir être correcte
      Je ne vois pas ce qu’il se passerait si .whatever() dépendait d’un état interne qui change après l’initialisation de l’objet