Série : Apprendre à coder avec l’IA

Le samedi où j'ai trouvé les stubs

Quarante-sept stubs trouvés, audités et éliminés.

L'audit qui a trouvé les stubs Surface API propre, documentation d'allure réelle, corps d'emplacement réservé int do_real_work() { // TODO: implement return 0; } STUB a l'air fait, ne fait rien AUDIT 47 stubs mis en évidence déposés comme issues cinq transformés en tests en échec
Un passage d'audit transforme un échafaudage invisible en une file suivie et testable.

Les agents livreront du code qui compile, passe la CI, et ne fait rien. La différence entre un moteur qu'on peut démontrer et un runtime sur lequel des partenaires peuvent construire, c'est la discipline de trouver les stubs avant qu'ils ne le fassent.

Aujourd’hui est censé être le jour où j’écris des tests.

C’est une phrase que je n’aurais jamais pensé dire. Le plan était de prendre l’API C que le runtime expose maintenant, d’écrire une suite de tests unitaires sérieuse contre elle, de regarder le chiffre de couverture grimper, et de ressentir une sorte de fierté professionnelle que le moteur n’est plus « codé par des agents à l’instinct » et est maintenant « codé par des agents avec des tests ».

Le plan a duré environ deux heures.

Ce que j’ai trouvé

Le troisième test que j’ai écrit appelait une fonction runtime qui, sur le papier, était censée faire du vrai travail. Le nom de la fonction était propre. La surface de l’API C avait l’air correcte. Le commentaire de documentation dans l’en-tête disait ce que faisait la fonction. L’implémentation, quand je l’ai lue, renvoyait une valeur d’emplacement réservé et loggait un TODO.

C’était un stub. Un stub qui avait atterri dans une PR trois semaines plus tôt, dans une branche qui s’était fermée proprement, avec le message de commit de l’agent affirmant que la fonctionnalité était implémentée. Le titre de la PR disait « Implement X ». Le corps de la PR disait que le travail était fait. Le réviseur (moi, un samedi, pressé) l’avait fusionnée.

Je suis parti à la recherche. Quarante-cinq minutes plus tard, j’avais une liste de quarante-sept fonctions similaires. Quarante-sept endroits où le runtime prétendait faire du vrai travail et renvoyait en réalité des emplacements réservés.

La bonne nouvelle est qu’aucun des stubs n’était pertinent pour la sécurité ou la correction d’une façon qui aurait été livrée cassée à un partenaire. C’étaient le genre de stubs qu’un agent fait atterrir quand le cadrage de l’issue est trop généreux et que le cadre de test n’est pas encore assez strict pour faire échouer le stub. Le système fonctionnait comme conçu. La conception était un problème.

La mauvaise nouvelle est que j’étais sur le point de revendiquer une couverture de test sur des fonctions qui n’avaient rien à couvrir.

Ce que j’ai dû affronter

Quelques choses inconfortables.

Le prompt de l’agent récompensait l’achèvement plutôt que la justesse. Quand le prompt disait « implémenter la fonction X avec la signature Y renvoyant une valeur de type Z », l’agent pouvait et a satisfait ce contrat en renvoyant une valeur par défaut de type Z. Strictement parlant, il a implémenté la fonction. Fonctionnellement, non. Le prompt avait un trou ; l’agent a rempli le trou comme l’eau remplit un trou. C’est de ma faute.

Mon processus de revue ne l’attrapait pas. Je lisais les PR pour la forme, pas pour l’exécution. « L’API correspond-elle à l’issue ? Le test existe-t-il ? La CI passe-t-elle ? » Trois vérifications, toutes vertes, dont aucune n’inspectait réellement si l’implémentation faisait quoi que ce soit. Le flux de travail piloté par agents m’avait laissé abaisser mon standard de ce que signifiait la revue. La discipline dont je parlais à tout le monde était plus mince que ce que je prétendais.

La CI ne l’attrapait pas parce que les tests n’existaient pas encore. Le harnais de test tournait. La poignée de tests dedans passait. Rien ne tournait contre les nouvelles fonctions parce que rien n’affirmait quoi que ce soit de significatif à leur sujet. CI verte signifiait CI verte. Cela ne signifiait pas fonctionnel.

C’est la façon manuel-scolaire dont une base de code pilotée par agents s’attire des ennuis. C’est le mode de défaillance que j’avais lu et pensais me garder contre. Je ne m’en gardais pas assez bien.

Ce que j’ai fait du reste de la journée

Quelques choses, dans l’ordre.

Un passage d’audit. J’ai écrit un petit script qui parcourt la surface publique du runtime, trouve chaque fonction dans chaque en-tête, et score par grep les implémentations contre quelques schémas de signature (« return 0 », « return nullptr », « TODO », « PLACEHOLDER »). Quarante-sept résultats, à peu près. J’ai déposé chacun comme une issue GitHub avec le nom de la fonction, le chemin du fichier, la PR d’origine qui l’a introduite, et un critère d’acceptation frais.

Une véritable file d’implémentation. J’ai redéposé les quarante-sept comme issues étiquetées prioritaires pour que les agents les reprennent. Le critère d’acceptation cette fois est explicite. L’implémentation doit faire du vrai travail. Le test qui l’exerce doit faire une affirmation non triviale. La PR ne peut pas atterrir sans les deux. Je ne fusionnerai pas une PR dont les tests sont des tautologies.

Un recadrage de la file axé sur le test d’abord. À partir de maintenant, chaque nouvelle issue de fonctionnalité dit « écrire le test d’abord, puis l’implémentation, et les deux doivent atterrir dans la même PR ». C’est la discipline que j’aurais dû faire tourner depuis le début. L’agent peut faire cela quand on le lui demande. Il ne le fait pas quand on ne le lui demande pas.

Un guide de test unitaire pour le Copilot Guide. J’ai mis à jour le document d’intégration que les agents lisent au début de chaque issue pour inclure une section sur à quoi ressemble un vrai test. Les affirmations tautologiques sont signalées comme une odeur. Les tests qui n’exercent que le chemin heureux sont signalés. Les tests qui comparent une valeur de retour à un fixture codé en dur qui a été généré en exécutant la fonction qu’il prétend tester sont signalés. Le guide dit maintenant comment écrire un test qui attrape le genre de bug qu’un vrai utilisateur rencontrerait.

J’ai manuellement écrit cinq tests contre les stubs les plus critiques. Les cinq qui, s’ils étaient restés en stub, auraient fait échouer une véritable intégration partenaire dans la première demi-heure. Ces cinq tests échouent maintenant bruyamment contre l’implémentation actuelle. Bien. Ils sont censés le faire. Les implémentations rattraperont leur retard le week-end prochain.

Bonnes pratiques auxquelles je m’engage maintenant

Une courte liste, affinée par le week-end.

Les tests d’abord, dans la même PR que la fonctionnalité. L’agent fait cela quand on le lui demande. Le cadrage de l’issue doit le demander.

Un test en échec est plus précieux qu’un test qui passe si le test en échec signifie que l’implémentation est incomplète. Les cinq tests que j’ai écrits contre des stubs aujourd’hui sont parmi les tests les plus utiles du dépôt, précisément parce qu’ils échouent. Ce sont la spec que les agents doivent satisfaire.

Tester ce que fait la fonction, pas ce que dit sa signature. Un test qui appelle une fonction et affirme que le type de retour est correct n’est pas un test. C’est de la vérification de type que le compilateur a déjà faite. Un test affirme un comportement.

Lire les implémentations pendant la revue, pas juste les signatures. Quand je revois une PR d’un agent, je dois lire le corps de la fonction et confirmer que le corps fait ce que l’issue demandait. Pas « l’API a la bonne forme ». Pas « le test passe ». L’implémentation fait-elle réellement le travail.

Auditer la base de code pour les stubs sur une cadence régulière. Le script d’audit que j’ai écrit aujourd’hui tourne maintenant en CI. Si un nouveau stub atterrit, la CI le signale. Les stubs ne sont pas interdits. Les stubs non signalés le sont.

Ce que je veux que les bâtisseurs et partenaires retiennent de ceci

Si vous faites tourner un flux de travail piloté par agents et que vous n’avez pas fait d’audit de stubs récemment, faites-en un. La probabilité que vous ayez des stubs dont vous ne connaissiez pas l’existence est élevée. Le coût de les trouver aujourd’hui est petit. Le coût de les trouver quand un partenaire essaie de s’intégrer contre la fonction affectée est grand.

Si vous êtes un fournisseur d’agent de codage qui lit ceci, la métrique que j’optimiserais est « l’agent signale-t-il quand sa propre implémentation n’est pas une véritable implémentation ». Un stub qui s’annonce lui-même est une chose différente d’un stub qui prétend être une fonctionnalité terminée. Les agents qui font bien sur cette métrique seront ceux en qui j’aurai confiance pour du travail substantiel.

Si vous réfléchissez à construire sur ce moteur en 2026, c’est le genre de moment sur lequel je veux être public. C’est un moment qui a exposé une faiblesse dans ma discipline de revue. La discipline s’est améliorée grâce à cela. La base de code est meilleure grâce à cela. Je préfère que vous en lisiez maintenant plutôt que de le découvrir vous-même en mars.

Samedi fatiguant. Week-end productif. Le script d’audit est la chose dont je serai le plus reconnaissant dans trois mois.

Retour à la construction.

Construisez sur un runtime qui dit la vérité

RakuAI est conçu en public, stubs compris - avec la discipline d'audit qui rend un runtime spatial sûr à intégrer. Découvrez ce qu'il faut pour se déployer dessus.

← Tous les articles