Plonger dans le cerveau d'un senior pour transformer son IA en senior

Plus ça va, plus nous pouvons créer des prototypes bluffants, avec l'IA. Il faut désormais amener cette dernière au niveau d'expertise le plus pointu, pour arriver jusqu'au produit fini.
Plonger dans le cerveau d'un senior pour transformer son IA en senior

Récemment, un fichier d’instruction Claude m’a été partagé. Il s’est avéré exceptionnellement populaire pour son efficacité : https://github.com/multica-ai/andrej-karpathy-skills/blob/main/CLAUDE.md

Si nous regardons ce fichier, nous voyons qu’il est relativement court, mais son efficacité repose sur un certain nombre de choix que font naturellement les développeurs expérimentés après des années à se casser les dents sur des concepts paraissant pertinents au premier abord, mais qui s’avèrent ravageurs pour nos produits à terme…

Voyons donc quelques petits enseignements que les développeurs acquièrent en 10 ans – parfois même 20 – de pratique intensive.

Réfléchir avant de coder

Parce que la réponse n’est pas toujours évidente…

Lorsque nous définissons l’architecture de son code, on est invariablement confronté à des choix. Quelle que soit la fonctionnalité que nous voulons implémenter, il existe toujours plusieurs façons de faire équivalentes. Certains arbitrages sont évidents : bonnes pratiques du langage et des technos choisies, contraintes de l’architecture existante, performance, etc.

D’autres sont plus subtils et nécessitent de pousser plus loin la réflexion. Parfois, plusieurs approches sont à ce point équivalentes qu’il n’y a vraiment pas d’argument permettant de trancher autre que le ressenti personnel.

Lorsque nous déléguons le développement à un agent IA, celui-ci peut trancher les choix purement rationnels, dont les arbitrages sont évidents, mais ne pourra pas toujours se débrouiller sur les cas moins évidents. A ce moment-là, le développeur doit pouvoir reprendre la main, comprendre la situation, et trancher pour l’IA.

Baliser le terrain

Nous pouvons tout de même fournir d’autres aides à l’IA pour prendre des décisions informées, comme un guide de développement allant au-delà des bonnes pratiques (nommage des variables, complexité maximum des fonctions, etc.) et fournissant des arguments pour trancher lorsque les choix sont plus d’ordre cosmétique que réellement fonctionnels.

Par exemple, si nous demandons à l’IA de gérer l’affichage d’un bouton d’action, elle saura le coder. Par contre, elle pourra coder le bouton à la main à chaque fois, créer un modèle unique réutilisable, ou surcharger le style global de l’application… Les trois fonctionnent et le choix doit être fixé pour l’IA, sinon elle risque de changer d’avis d’un prompt à l’autre et rendre le projet impossible à maintenir… Lui imposer une convention esthétique et architecturale dès le départ lui évite d’avoir à lancer un dé à chaque ligne de code, ou de composant graphique.

L’IA en harmonie avec son environnement

Une autre piste permettant de faciliter certains choix pour l’IA peut être de privilégier la cohérence avec le code existant. L’idée vient du travail en équipe : lorsque plusieurs développeurs collaborent sur un même projet, mais viennent d’horizons différents, ils apportent également avec eux des habitudes spécifiques. Et si chacun s’en tient à ces dernières, le code une fois mit en commun peut être difficile à suivre pour les uns par rapport aux autres, et encore plus pour un nouvel arrivant, qui se retrouve face à un gros plat de spaghettis emmêlés.

Dans ces conditions, les développeurs s’accordent alors sur des bonnes pratiques qui n’auront pas d’impact direct sur la performance ou la robustesse du code, mais qui faciliteront la collaboration de tous. Un exemple typique est la convention de nommage des variables et des fonctions comme l’idée de rappeler le type de variable (chaîne de caractère, nombre, date, etc.), ou d’imposer une longueur minimum.

Appliqué au travail de l’IA, cela peut aider à trancher des choix jouant sur de petits détails, et les fixer en dur dans les règles évitera à l’IA de changer d’avis au moindre changement de configuration du modèle, tout en facilitant la relecture (humaine ou non) du code.

Voici un exemple typique issu de notre référence :

### 3. Surgical Changes
When editing existing code:
* Don't "improve" adjacent code, comments, or formatting.
* Don't refactor things that aren't broken.
* Match existing style, even if you'd do it differently.

De la sorte, son auteur interdit explicitement tout excès de zèle en obligeant l’IA à se concentrer exclusivement sur son sujet, sans toucher aux portions adjacentes et en respectant le style existant plutôt que d’innover.

Privilégier la simplicité

Parmi les bonnes pratiques que les développeurs mettent souvent en avant se trouve la simplicité du code. Cela a donné des principes bien connus du milieu, comme le “Keep It Simple Stupide” (KISS), ou le “Don’t Repeat Yourself” (DRY). Ces principes sont assez explicites de par leur formule même. Pour comprendre d’où vient ce raisonnement, il faut avoir en tête une bell curve classique chez les développeurs :

image

On voit qu’au tout début, les développeurs utilisent des solutions simples pour résoudre leurs problèmes, car ils ne connaissent que ça. Par la suite, plus ils découvrent de fonctionnalités et idiomes de leur langage de programmation, plus ils veulent en utiliser… Mais leur code perd en lisibilité et devient de plus en plus complexe à maintenir, justement parce que ce niveau de maîtrise du langage devient un prérequis pour se plonger dedans. Le développeur expérimenté en revient donc à des solutions simples et efficaces, utilisant les structures complexes uniquement là où elles sont incontournables (ce qui correspond généralement à l’usage pensé par les concepteurs du langage).

Lorsque nous instruisons notre IA sur la façon de coder, autant l’amener d’emblée à la fin de la bell curve, plutôt que de lui faire endurer tous les stades du parcours… 

Isolation du code

En parlant de simplicité, l’un des axes de travail concerne ce qu’on appelle la « Separation of concern », ou séparation du code basée sur les domaines respectifs. Cette règle est importante, pour éviter un fléau qui frappe les applications dès qu’elles deviennent lourdes… Dans ces applications, lorsque nous modifions une petite fonctionnalité à une extrémité de l’application, nous découvrons (souvent après avoir livré le produit aux utilisateurs) qu’une autre partie, qui n’a apparemment rien à voir a été également impactée…

Choix pragmatiques

Ce genre de problème se produit lorsqu’une fonction unique est utilisée aux deux endroits (ce qu’on appelle une dépendance), et qu’elle a été modifiée pour changer le comportement d’un côté, sans savoir que cette fonction est aussi utilisée de l’autre côté.

Sur une petite application, il reste assez simple de tirer le fil et voir ce qui bouge sans tout casser, mais lorsque l’application devient vraiment complexe, que les dépendances sont noyées dans des centaines de lignes de code, on ne parle plus de pelote de laine, mais de plat de spaghetti (et si vous avez déjà tenté de retirer une pâte du milieu du plat, je pense que l’image vous parlera).

Le principe de « Separation of Concerns » peut paraître contradictoire avec Le DRY (Don’t Repeat Yourself, ou réutiliser le code) que nous avons vu juste avant, car elle consiste à isoler des fonctions, quitte à dupliquer du code, lorsque nous voyons que ces fonctions sont utilisées dans des contextes différents. Le DRY est utile pour éviter les copier-coller de code, mais… peut nous amener à créer des abstractions susceptibles d’induire les soucis dont nous venons précisément de parler. C’est là où l’expérience d’un développeur expérimenté marque la différence, car il saura déterminer à quel moment exactement il vaut mieux se répéter.

Découpage logique

En effet, les « concerns » en question dans « Separation of Concerns » ne sont pas forcément évidents à identifier pour un débutant, mais nous pouvons nous appuyer sur les fonctionnalités au sens produit du terme. Lorsque nous cherchons à traiter les besoins des clients, nous identifions des étapes distinctes du parcours de l’utilisateur, avec potentiellement des chemins alternatifs correspondant à des particularités que certains utilisateurs demandent. Chaque étape du parcours peut être considérée comme étant un « concern » spécifique.

Par exemple sur un formulaire, nous aurons un contenant commun (le formulaire lui-même) et du code spécifique pour traiter les champs de texte, les dates, ou les checkbox. Les variations sur une fonctionnalité partent normalement d’une base de code commune (correspondant à ce qui est commun à cette fonctionnalité) avec des comportements surchargés. Prenons le cas d’un bouton radio permettant d’afficher ou cacher des options de configuration avancées : ces options seront gérées par du code indépendant du reste du formulaire pour éviter d’en casser l’affichage si le reste du formulaire est modifié.

Le focus

Ainsi, lorsque nous laissons l’IA manipuler le code, nous pourrons nous assurer qu’elle modifie une partie de notre site web sans toucher à quoi que ce soit d’autre (même par inadvertance), et qu’elle sera capable de facilement nettoyer des fonctionnalités devenues inutiles ou inutilisées (ce qu’on appelle le code mort ou les orphelins : lorsqu’aucune page de l’application ne pointe dessus, ou lorsqu’une fonction n’est appelée nulle part).

Nous voyons ici que l’IA se comporte comme la plupart des développeurs : pour être efficace, elle doit pouvoir se concentrer sur la fonctionnalité qu’elle est en train de traiter, sans se laisser distraire, ou craindre d’être distrait, par d’autres pans de l’application.

Remplir la Definition of Done

L’agilité à la rescousse

Ce dernier point rejoint ce que nous avons vu avec les méthodes agiles. Lorsque nous alimentons notre feuille de route et notre sprint avec des tâches à réaliser, celles-ci traitent de besoins utilisateurs. Chaque besoin correspond à une histoire (ou « Story » en anglais), avec un but : la satisfaction d’un besoin donné de l’utilisateur.

Grâce à la démarche produit et aux méthodes agiles, lorsque le sujet atterrit entre les mains du développeur (humain ou IA), nous avons clarifié une solution claire à implémenter : pas le code à implémenter, mot pour mot, mais le périmètre de la fonction à intégrer. C’est ce que nous appelons la Definition of Done : une série de critères qui, lorsque les cases sont cochées, garantissent que la fonctionnalité se comporte comme attendue (ainsi, une fois déployée, l’utilisateur verra ce que nous voulions lui soumettre, et il nous dira si ça répond bien à son besoin).

L’IA plus agiliste que l’agiliste

L’IA est encore plus demandeuse de ce genre de règles. Nous pouvons lui fournir beaucoup d’éléments de contextes (parfois même trop, il faut toujours rester prudent sur la consommation de tokens), ce qu’elle va chercher en priorité, est de savoir ce que nous attendons concrètement d’elle. Donc lorsque nous lui soumettons une tâche, exprimer clairement « voici tes objectifs » est le moyen le plus efficace pour l’orienter dans la bonne direction.

Quand nous disons d’éviter le contexte trop lourd, les critères fournis doivent être tout de même aussi précis que possible pour éviter à l’IA de partir dans une direction qui n’est pas voulue. Par exemple, un simple « fait que ça marche » est loin d’être suffisant et nécessitera des allers-retours avec le modèle pour affiner le critère… Donc autant fournir directement les éléments les plus clairs possibles d’emblée.

Conclusion

En fin de compte, l’important est d’éviter à l’IA d’avoir à deviner ce que nous avons en tête : elle doit bénéficier d’une compréhension aussi fine que nous des attentes de nos utilisateurs et des bonnes pratiques de développement à appliquer.

Plus le temps passera, plus nous disposerons d’outils prêts à l’emploi pour le second point. Pour le premier, les besoins évoluent tout le temps, car plus nous résolvons de besoins humains, plus l’humain se trouve de nouveaux besoins… Et nous aurons toujours à garder la main sur les critères de validation d’une implémentation par l’IA.

Par contre, la nuance entre bonne pratique et attente utilisateur peut être bien plus ténue qu’il n’y paraît au premier abord. L’exemple le plus flagrant concerne les choix de design : ceux-ci concernent l’ergonomie et l’accessibilité, pour adapter l’interface au public cible de notre application. Les règles pourront ainsi évoluer au fil des moyens à notre disposition, et des goûts des utilisateurs.

Comprendre ces éléments de contexte, et connaître un minimum les règles autant de design que de développement nous évite bien des surprises au moment de contrôler le travail que l’IA a fourni… Donc au dernier moment.

Ressources


Write a comment