← Tous les articles

Comprendre le code pour mieux le générer

Créer FiboCode m'a donné envie de mieux comprendre mon propre projet. Réflexions sur le langage naturel, l'architecture et le travail au long cours avec AI.

Je n'aime pas avoir l'impression de ne pas comprendre mon propre projet.

Les besoins viennent de moi. Les fonctionnalités sont celles que je voulais. Une fois le code généré par AI, j'ai vérifié qu'il fonctionnait. Pourtant, si l'on me demande quels modules une fonctionnalité traverse, pourquoi elle a été conçue ainsi ou ce qu'un autre changement affecterait, je ne sais pas toujours répondre clairement.

En développant FiboCode, cette question prend de plus en plus de place.

Bien sûr, j'aimerais qu'AI écrive davantage de code pour moi. Mais je compte continuer ce projet. Une fois la fonctionnalité du jour terminée, une autre idée arrivera demain. Je ne veux pas devoir prier à chaque modification pour que rien d'autre ne casse.

On répare A et B tombe en panne. On répare B et un nouveau bug apparaît dans A. Il est alors facile de renvoyer les symptômes à AI et d'attendre une nouvelle explication et un correctif.

Après plusieurs tours, pourtant, je me sentirais de moins en moins sûr de moi. Chaque changement paraît raisonnable isolément. Mais, mis bout à bout, qu'ont-ils fait du système ?

J'aimerais le comprendre. En suivant cette question, j'en suis venu à penser que comprendre le code et le générer étaient plus liés que je ne l'imaginais au départ.

Programmer en langage naturel m'a fait réfléchir

Du langage machine à l'assembleur, puis aux langages de haut niveau, nous avons continué à construire des abstractions.

Beaucoup de détails autrefois gérés à la main sont maintenant confiés aux outils. Nous pouvons exprimer des fonctions plus complexes avec moins de code, sans tout reprendre à la base à chaque fois.

Nous montons aujourd'hui d'un cran supplémentaire : décrire les besoins en langage naturel et laisser AI générer le code.

À première vue, cela pourrait sembler une simple nouvelle façon de s'exprimer. Mais je crois qu'un changement mérite notre attention.

Assembleurs et compilateurs transforment le code selon des règles explicites. Les programmes peuvent bien sûr contenir des bugs, mais la transformation n'a pas besoin de deviner mon intention.

Le modèle, lui, doit la deviner.

À partir de mes mots, du code qu'il voit et du contexte, il déduit ce que je veux et choisit une implémentation. Une même phrase peut donner plusieurs résultats. Même un résultat identique peut continuer à réaliser un besoin que je n'ai jamais clairement exprimé.

J'ai ensuite lu un article de Birgitta Böckeler qui aborde cette différence : les grands modèles de langage produisent leurs résultats par inférence ; on ne peut pas simplement les traiter comme des compilateurs de langage naturel. Cela rejoignait mes interrogations.

Alors, que peut-on faire ?

Des besoins plus précis, des conventions écrites et des tests des comportements importants devraient réduire une partie des écarts. Si la conception explique déjà comment les modules collaborent et quelles classes et méthodes interviennent, AI dispose de davantage de repères.

Encore faut-il que la documentation corresponde au code. Décrire en détail une conception abandonnée depuis longtemps peut poser plus de problèmes que l'absence de documentation.

Je ne pense pas non plus que ces pratiques éliminent toute incertitude dans la génération. Mais elles peuvent laisser moins de choses à deviner sur le moment.

Est-ce que je sais vraiment ce que je veux changer ?

En poursuivant cette réflexion, je rencontre un autre problème : nous avons nous aussi du mal à exprimer clairement nos besoins.

Surtout quand nous connaissons mal le code.

Je sais peut-être très bien où je voudrais un bouton de plus, ou quelle opération devrait être plus simple. Mais j'ignore parfois dans quel module l'ajouter, par quels traitements elle doit passer et quels comportements existants doivent être conservés.

Dans ce cas, même si je répète à AI « ne touche pas aux autres fonctionnalités », comment doit-elle interpréter cette phrase ?

Par exemple, « unifie cela » peut désigner la présentation ou la fusion des traitements de données. Ces deux interprétations ne conduisent pas du tout au même périmètre de modification.

Si je ne sais pas définir ce périmètre moi-même, le modèle doit le compléter à partir de ce qu'il voit. Il peut changer plus que prévu ou contourner une convention dont j'ignorais l'existence.

Je découvre ensuite un problème et lui demande de le corriger. À mesure que les changements s'accumulent, la demande initiale, les contraintes ajoutées en route et les relations préexistantes dans le code ne restent pas forcément toutes intactes dans le contexte.

J'ai peur d'aboutir à un code qui fonctionne par endroits, mais qui est rempli de rustines laissées par la correction précédente. On dirait que seule AI accepte encore d'y toucher. Les gens n'osent presque plus le modifier.

Ce genre de code me fait penser à AI slop. Et le rendre à AI ne signifie pas qu'elle maîtrise tout le système. Elle peut encore fournir des explications sans que nous l'ayons enfin compris.

Je suis donc de plus en plus convaincu qu'avant de formuler précisément un besoin, il faut connaître les grandes lignes du projet.

Je n'ai pas besoin de tout comprendre dès le départ. Mais je veux au moins savoir où le changement demandé va se placer.

J'aimerais parler avec AI au niveau de l'architecture

Je ne considère pas que l'usage d'AI exige de relire à la main chaque ligne générée.

Quand un projet devient complexe, notre attention est limitée. Je préfère d'abord en comprendre l'ossature : ses modules centraux, leurs responsabilités, le point d'entrée d'une fonctionnalité, ses traitements et les endroits où les données et l'état changent.

J'aimerais donc organiser une couche de compréhension du système au-dessus de l'implémentation en langage de haut niveau. Je l'appelle pour l'instant « couche d'architecture », sans tenir particulièrement à ce nom.

Ce qui compte, c'est qu'elle reste reliée au code réel.

Quand je vois un module, je peux trouver son implémentation. Quand je vois le parcours d'une fonctionnalité, je connais les classes et méthodes qu'elle traverse. Quand je veux ajuster un comportement, je peux suivre les relations jusqu'aux endroits à examiner.

Cela me donne des sujets plus concrets à discuter avec AI.

Si je sais, par exemple, qu'une opération passe déjà par une file de tâches commune, je peux demander si la nouvelle capacité devrait suivre ce parcours. Si AI ajoute une autre gestion d'état, je peux demander pourquoi l'existante ne convient pas et si les deux ne vont pas interférer plus tard.

Sans m'être posé ces questions, j'aurais peut-être accepté n'importe quelle implémentation qui fonctionne. Maintenant, j'ai au moins une chance de remarquer assez tôt qu'elle complique inutilement les choses.

Je pense que ces comparaisons peuvent aussi améliorer ma façon de programmer.

Une fonction peut être réalisée de plusieurs manières. Pourquoi le projet a-t-il choisi celle-ci ? Une abstraction paraît élégante, mais si chaque changement oblige à intervenir partout, aide-t-elle vraiment ? Un autre module reste très simple et a traversé plusieurs évolutions sans perturber le reste. Qu'a-t-il bien fait ?

Ces questions méritent du temps. En construisant mon propre jugement, je peux plus facilement expliquer à AI ce qu'il faudrait ajuster et comprendre une autre solution qu'elle propose.

Bien sûr, je peux aussi me tromper sur l'architecture. Les liens d'un schéma doivent être vérifiés dans le code. Les fonctions que je crois épargnées demandent aussi des tests et des essais réels. Face à des états importants ou des limites complexes, je peux aller plus loin dans l'implémentation.

J'aimerais arriver peu à peu à savoir où regarder dans la plupart des cas, tout en sachant ce que je n'ai pas encore compris.

C'est une des raisons pour lesquelles je développe l'analyse de code de FiboCode.

Je veux relier le code, les documents et les diagrammes de relations pour trouver la structure dans une implémentation inconnue et suivre une fonctionnalité. Avec cette compréhension, je me sentirais plus à l'aise pour discuter des changements avec AI.

Dans What Is Code?, Unmesh Joshi explique lui aussi comment de bonnes abstractions, des types, des tests et des règles explicites peuvent guider la compréhension et les modifications du modèle. Cette direction me plaît : conserver autant que possible notre compréhension du système dans le projet, pour qu'elle serve encore au changement suivant.

Je veux encore consacrer du temps à lire le code

AI écrit vite. Il est tentant de lui confier aussi la tâche suivante.

Les fonctions se terminent les unes après les autres et le projet semble avancer. Mais je me demande aussi ce que j'ai mieux compris après tout ce travail.

Si je me contente de demander des choses et d'accepter les résultats jusqu'à ce que le projet devienne incontrôlable, je risque ensuite de passer beaucoup de temps à le réparer ou à recommencer. Le temps gagné pourrait disparaître, avec les coûts des appels au modèle et l'effort nécessaire pour réapprendre toute une implémentation.

Je préfère passer une partie de ce temps à lire le code au calme.

Pas nécessairement beaucoup d'un coup. Je peux commencer par comprendre pourquoi un module est découpé ainsi, pourquoi une fonctionnalité exige ces étapes ou quelle technique inconnue AI a utilisée cette fois. Si je ne comprends pas, je peux poser des questions, chercher des indices dans le code et essayer un petit changement pour vérifier mon interprétation.

En cherchant des sources, j'ai découvert une expérience d'apprentissage d'Anthropic. Après des tâches de programmation inconnues réalisées avec AI, les participants ont obtenu un score moyen inférieur à celui du groupe qui programmait à la main, lors d'un test immédiat. Cette petite expérience ne permet pas de prévoir l'évolution des compétences à long terme. Mais elle m'a rendu plus attentif à ce que j'ai réellement appris après une tâche terminée.

J'espère que cette compréhension peut s'accumuler peu à peu. La prochaine fois que je rencontre une conception similaire, j'en reconnaîtrai peut-être le fonctionnement. Devant une proposition d'AI, je pourrai poser une question fondée sur ce que je sais.

Il reste à explorer si FiboCode peut bien aider à cela. Mais c'est une grande partie de ma motivation pour développer l'analyse de code.

Je veux continuer à écrire du code avec AI. Et quand le projet aura grandi, je veux encore pouvoir expliquer comment il fonctionne.


L'idée en une image simple :

Quelques autres lectures