Mon expérience avec le « vibe coding », par Gabriella Gonzalez De temps à autre, je teste le « vibe coding », pour diverses raisons :
- parfois, je me laisse emporter par l'engouement général et, avec un sentiment de culpabilité, je me tourne vers le « vibe coding » quand je suis bloqué
- d'autres fois, quelqu'un me dit que cette technologie de pointe a franchi une étape décisive
- j'aimerais aussi que le « vibe coding » ou quelque chose de similaire fonctionne (je suis généralement favorable à l'IA)
Cependant, chaque fois que j’essaie le « vibe coding », je ne suis pas impressionné par les résultats. Ne vous méprenez pas : je pense que le « vibe coding », dans son état actuel, est assez incroyable et constitue une démo technique sympa, mais qu’il passe à côté de son objectif en tant qu’outil d’ingénierie logicielle.
Je vais illustrer ce que je veux dire avec un exemple de session Claude Code que j’ai réalisée. J’ai choisi Claude Code pour mon récent essai de « vibe coding » parce que certains de mes collègues et pairs sur les réseaux sociaux en disaient beaucoup de bien.
Le défi
Au cours du week-end, je travaillais sur mon projet Grace (un langage spécifique à un domaine pour l'ingénierie des prompts - instructions génératives) et l'un des problèmes auxquels j'étais confronté était que la compilation Nix pour GHCJS échouait avec cette erreur après la mise à jour vers Nixpkgs 24.11 :
Configuration : dépendance manquante sur une bibliothèque externe :
* Fichier d'en-tête manquant (ou incorrect) : ghc/utils/unlit/fs.h
* Fichier d'en-tête manquant (ou incorrect) : ghc/utils/unlit/fs.h
J'ai commencé le débogage et, au bout d'environ une heure, j'avais une « solution » (plutôt une solution de contournement). Puis je me suis arrêté et j'ai réalisé que cela constituerait en fait un bon exemple concret pour m'essayer au vibe coding, car c'est le genre de problème auquel je suis régulièrement confronté (tant dans le cadre professionnel que dans mes projets open source). J'avais également mon propre travail sur lequel je pouvais comparer le vibe coding.
Le résultat
Je me suis donc abonné à Claude Code Pro (pour 17 $ par mois), j’ai installé Claude Code, je l’ai lancé dans mon terminal et j’ai commencé par cette instruction générative :
Je souhaite corriger un échec de compilation dans ma configuration Nix. Pour replacer les choses dans leur contexte, cette compilation fonctionne sur une autre machine, mais pour une raison quelconque, elle ne fonctionne pas sur celle-ci. Plus précisément, pour reproduire l'échec de compilation, il faut charger le shell Nix en définissant .envrc sur "use flake “.#ghcjs”" et direnv reload, ou simplement nix build '.#website'.
- rester engagé/présent (en suivant le fil des pensées et les commandes de Claude)
- intervenir pour clarifier les exigences
- essayer de ne pas influencer le processus avec les connaissances que j’avais acquises lors de mes propres recherches
- essayer d’approuver automatiquement autant que possible, dans la mesure du raisonnable
- confirmer manuellement toutes les modifications apportées au code
La seule fois où j’ai dû intervenir, c’est lorsque Claude a commencé à essayer de contourner le problème en effectuant une compilation pour x86_64-darwin au lieu de aarch64-darwin. Mon commentaire a été :
Tu n’as pas besoin d’exécuter Rosetta. Cette compilation devrait sans aucun doute fonctionner sur aarch64-darwin. Je sais qu’elle fonctionne sur une autre machine et que persister avec une compilation x86_64-darwin est une impasse.
Malheureusement, la première tentative de solution de Claude n’était pas bonne, et je vais la coller ici :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | diff --git a/flake.nix b/flake.nix
index 465442e..957ca26 100644
--- a/flake.nix
+++ b/flake.nix
@@ -16,8 +16,59 @@
};
haskell = super.haskell // {
+ compiler = super.haskell.compiler // {
+ ghcjs =
+ let
+ originalGhcjs = super.haskell.compiler.ghcjs;
+ originalBootGhcjs = originalGhcjs.passthru.bootGhcjs;
+ emsdk = "/nix/store/rsd7naynyq6r8cry568r1widlzgxvqzg-emsdk";
+
+ # Patch bootGhcjs to fix the fs.h path issue
+ patchedBootGhcjs = originalBootGhcjs.overrideAttrs (bootOld: {
+ patches = (bootOld.patches or []) ++ [ ./ghcjs-fix-fs-header-path.patch ];
+ });
+ in
+ # The final GHCJS runs ghcjs-boot using bootGhcjs
+ originalGhcjs.overrideAttrs (final: prev: {
+ nativeBuildInputs = [ patchedBootGhcjs ] ++
+ (builtins.filter (x: x != originalBootGhcjs) prev.nativeBuildInputs);
+
+ # Rewrite buildPhase to use patched bootGhcjs
+ buildPhase = ''
+ export HOME=$TMP
+ mkdir $HOME/.cabal
+ touch $HOME/.cabal/config
+ cd lib/boot
+
+ mkdir -p $out/bin
+ mkdir -p $out/lib/ghcjs-8.10.7
+ lndir ${patchedBootGhcjs}/bin $out/bin
+ chmod -R +w $out/bin
+ rm $out/bin/ghcjs-boot
+ cp ${patchedBootGhcjs}/bin/ghcjs-boot $out/bin
+ rm $out/bin/haddock
+ cp ${patchedBootGhcjs}/bin/haddock $out/bin
+ cp ${patchedBootGhcjs}/bin/private-ghcjs-hsc2hs $out/bin/ghcjs-hsc2hs
+
+ wrapProgram $out/bin/ghcjs-boot --set ghcjs_libexecdir $out/bin
+
+ wrapProgram $out/bin/ghcjs --add-flags "-B$out/lib/ghcjs-8.10.7"
+ wrapProgram $out/bin/haddock --add-flags "-B$out/lib/ghcjs-8.10.7"
+ wrapProgram $out/bin/ghcjs-pkg --add-flags "--global-package-db=$out/lib/ghcjs-8.10.7/package.conf.d"
+ wrapProgram $out/bin/ghcjs-hsc2hs --add-flags "-I$out/lib/ghcjs-8.10.7/include --template=$out/lib/ghcjs-8.10.7/include/template-hsc.h"
+
+ env PATH=$out/bin:$PATH $out/bin/ghcjs-boot --with-emsdk=${emsdk} --no-haddock
+ '';
+ passthru = prev.passthru // {
+ bootGhcjs = patchedBootGhcjs;
+ };
+ });
+ };
+
packages = super.haskell.packages // {
"${compiler}" = super.haskell.packages."${compiler}".override (old: {
+ ghc = if compiler == "ghcjs" then self.haskell.compiler.ghcjs else old.ghc or null;
+
overrides =
let
hlib = self.haskell.lib;
diff --git a/ghcjs-fix-fs-header-path.patch b/ghcjs-fix-fs-header-path.patch
index 534bffc..ac19b29 100644
--- a/ghcjs-fix-fs-header-path.patch
+++ b/ghcjs-fix-fs-header-path.patch
@@ -1,19 +1,20 @@
-diff --git a/ghcjs.cabal b/ghcjs.cabal
-index 1234567..abcdefg 100644
--- a/ghcjs.cabal
+++ b/ghcjs.cabal
-@@ -43,7 +43,7 @@ library
- ghc/libraries/ghc-prim/cbits
- ghc/utils/fs
- ghc/rts
-- ghc/utils/unlit/fs.h
-+ ghc/utils/fs/fs.h
- GHC-Options: -Wall -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind -fprof-auto-calls -fobject-code
- if flag(disable-optimizer)
- cpp-options: -DDISABLE_OPTIMIZER
-@@ -916,4 +916,4 @@ executable ghcjs
- Main-Is: Main.hs
- Build-Depends: base, ghcjs
- Default-Language: Haskell2010
+@@ -43,7 +43,7 @@
+ ghc/includes/HsFFI.h
+ ghc/includes/Cmm.h
+ ghc/includes/stg/*.h
+- ghc/utils/unlit/fs.h
++ ghc/utils/fs/fs.h
+ ghc/driver/utils/cwrapper.h
+ ghc/driver/utils/getLocation.h
+ utils/wrapper/getline.h
+@@ -916,7 +916,7 @@
+ hs-source-dirs: ghc/utils/unlit
+ Main-Is: unlit.c
+ C-Sources: ghc/utils/unlit/fs.c
- Includes: ghc/utils/unlit/fs.h
+ Includes: ghc/utils/fs/fs.h
+
+ Executable private-ghcjs-touchy
+ if flag(compiler-only) || !os(Windows) |
C'est une pratique d'ingénierie logicielle plutôt médiocre (j'expliquerai pourquoi dans la section suivante), mais je voulais donner à Claude une chance de se rattraper ; j'ai donc commencé à m'impliquer davantage dès que j'ai compris que Claude n'était pas vraiment fait pour un fonctionnement de type « fire and forget ». Cependant, j'ai continué à éviter de m'appuyer sur mes recherches antérieures et je ne lui ai donné de retours que sur la base de ce qu'il avait lui-même rassemblé, appris et présenté.
Tout d'abord, j'ai demandé à Claude d'améliorer sa solution :
Cette solution présente donc quelques problèmes : (A) tu ne devrais pas avoir codé en dur les chemins /nix/store (comme /nix/store/rsd…-emdsk) et (B) tu ne devrais pas copier/coller/modifier la buildPhase (car cela entraînerait une dérive de configuration si, par exemple, je mettais à jour Nixpkgs)
❯ Le remplacement de chaînes n’est pas non plus une solution Nix idiomatique au problème. En général, la bonne façon de procéder dans ce genre de situation est d'utiliser un .override(…) ou un .overrideAttrs(…) bien placé, ou quelque chose de similaire
Inutile de dire que je n'étais pas impressionné. Si je voulais consacrer du temps et de l'argent à encadrer un ingénieur junior, je préférerais les consacrer à apprendre à un humain comment devenir un meilleur ingénieur logiciel.
Une ingénierie de mauvaise qualité
Je souhaite expliquer en détail pourquoi la solution de Claude relevait d'une mauvaise ingénierie, car pour un œil non averti, elle pourrait sembler être une solution acceptable au problème. Cependant, les LLM sont connus pour générer des solutions/idées qui semblent bonnes à première vue, mais qui s'effondrent lorsqu'on les examine de plus près.
Le premier problème est le suivant : la solution de Claude n'est absolument pas reproductible sur d'autres machines. En fait, elle n'est même pas nécessairement reproductible sur ma propre machine : la solution de Claude finira par cesser de fonctionner sur ma machine dès la première fois que j'exécuterai un ramassage des ordures Nix.
Cela s'explique par le fait que Claude code en dur le chemin du magasin Nix /nix/store/rsd7naynyq6r8cry568r1widlzgxvqzg-emsdk, ce qui est à proscrire absolument dans Nix pour la raison que je viens de mentionner (manque criant de reproductibilité, même au niveau local).
L'autre problème avec cette solution est que Claude n'a pas su comment patcher de manière idiomatique la compilation de ghcjs ; il a donc eu recours à un gros hack pour contourner son manque de connaissances :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | + buildPhase = ''
+ export HOME=$TMP
+ mkdir $HOME/.cabal
+ touch $HOME/.cabal/config
+ cd lib/boot
+
+ mkdir -p $out/bin
+ mkdir -p $out/lib/ghcjs-8.10.7
+ lndir ${patchedBootGhcjs}/bin $out/bin
+ chmod -R +w $out/bin
+ rm $out/bin/ghcjs-boot
+ cp ${patchedBootGhcjs}/bin/ghcjs-boot $out/bin
+ rm $out/bin/haddock
+ cp ${patchedBootGhcjs}/bin/haddock $out/bin
+ cp ${patchedBootGhcjs}/bin/private-ghcjs-hsc2hs $out/bin/ghcjs-hsc2hs
+
+ wrapProgram $out/bin/ghcjs-boot --set ghcjs_libexecdir $out/bin
+
+ wrapProgram $out/bin/ghcjs --add-flags "-B$out/lib/ghcjs-8.10.7"
+ wrapProgram $out/bin/haddock --add-flags "-B$out/lib/ghcjs-8.10.7"
+ wrapProgram $out/bin/ghcjs-pkg --add-flags "--global-package-db=$out/lib/ghcjs-8.10.7/package.conf.d"
+ wrapProgram $out/bin/ghcjs-hsc2hs --add-flags "-I$out/lib/ghcjs-8.10.7/include --template=$out/lib/ghcjs-8.10.7/include/template-hsc.h"
+
+ env PATH=$out/bin:$PATH $out/bin/ghcjs-boot --with-emsdk=${emsdk} --no-haddock
+ ''; |
Je sais par hasard (grâce à mes propres recherches sur le problème) que Claude n'a pas rédigé ce code de manière réfléchie ; il l'a copié/collé/modifié à partir d'ici.
Ce n'est pas une bonne pratique en génie logiciel, car si buildPhase en amont change, la copie que Claude a intégrée dans mon fichier flake.nix ne tiendra pas compte de ces modifications. De plus, cela signifie que la dérivation risque fort de ne plus fonctionner lorsque je mettrai à jour (de la même manière que le verrouillage ou l'intégration d'une dépendance a tendance à empêcher les mises à jour). Encore une fois, c'est le genre de problème qui semble acceptable à court terme, mais qui cause des problèmes à long terme.
Une manière beaucoup plus directe de faire ce que Claude essayait de faire aurait été quelque chose comme ceci :
| Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | diff --git a/flake.nix b/flake.nix
index 465442e..f10cb22 100644
--- a/flake.nix
+++ b/flake.nix
@@ -16,6 +16,19 @@
};
haskell = super.haskell // {
+ compiler = super.haskell.compiler // {
+ ghcjs810 = super.haskell.compiler.ghcjs810.override (old: {
+ ghcjsDepOverrides = self: super: {
+ ghcjs = super.ghcjs.overrideAttrs (old: {
+ postPatch = (old.postPatch or "") +
+ ''
+ ./utils/makePackages.sh copy
+ '';
+ });
+ };
+ });
+ };
+
packages = super.haskell.packages // {
"${compiler}" = super.haskell.packages."${compiler}".override (old: {
overrides = |
Cependant, d'après mon expérience en matière de débogage, je sais que ni la solution de Claude ni son équivalent plus simple ne résolvent réellement le problème (elles ne font que repousser l'échec de la compilation à un nouvel échec qui se produira plus tard). J'aborderai le véritable problème et la solution dans la section suivante.
Je ne m'attends pas à ce que Claude soit parfait, mais j'aurais aimé qu'il reconnaisse que ce code était fragile/difficile à maintenir et qu'il s'arrête pour me demander mon avis/mes conseils avant de foncer tête baissée avec cette approche. Par exemple, Claude aurait pu dire : « La solution que j'ai en tête pourrait ne pas être maintenable pour les raisons X, Y et Z. Cela vous convient-il ? Je risque d’épuiser mon budget de jetons si je cherche de meilleures solutions ».
Le problème plus grave, cependant, est qu’une personne peu familière avec Nix pourrait être induite en erreur et penser que cette solution est acceptable, voire impressionnante ! Ça « marche »… jusqu’à ce que ça ne marche plus. C’est pourquoi je suis assez sceptique face aux affirmations selon lesquelles le « vibe coding » permettrait de « faire passer au niveau supérieur » les personnes travaillant en terrain inconnu, car elles ne sont pas nécessairement qualifiées pour comprendre les implications, les compromis et les inconvénients du code généré par les LLM (et les solutions de « vibe coding » ne semblent pas encore en mesure de reconnaître et de communiquer efficacement ces compromis). Chaque fois que je code intuitivement dans un domaine relevant de ma propre expertise, je ne suis pas impressionné par les résultats, ce qui me rend très sceptique quant à la capacité du « vibe coding » à fonctionner correctement lorsque j’opère en dehors de ma zone de confort.
Solution humaine
La cause réelle de l’échec de la compilation est un problème connu dans Nixpkgs, pour lequel une demande de modification (pull request) est en cours de traitement. Plus précisément :
- cette commande de prétraitement gcc -E échouait de manière inattendue
- cet échec ne se produit que sur les versions récentes de macOS lorsque gcc comporte des entrées LC_RPATH en double
- la machine sur laquelle la compilation a réussi fonctionnait parce qu'elle tournait sous une version plus ancienne de macOS
- le « mode strict de Bash » était commenté, de sorte que l'échec de la compilation était très éloigné de la cause première
La solution de contournement que j'ai utilisée consistait à exécuter et à mettre en cache la compilation à l'aide de mon CI Garnix existant (qui fonctionne sur une ancienne version de macOS ne présentant pas ce problème). Ce n'est toujours pas idéal du point de vue de la reproductibilité, mais c'est à peu près aussi reproductible que peuvent l'être les compilations Darwin dans l'écosystème Nixpkgs. La meilleure solution possible serait de faire aboutir la proposition de modification mentionnée ci-dessus. Une solution intermédiaire consisterait à modifier l'étape gcc -E pour utiliser un préprocesseur C différent (par exemple clang) uniquement pour cette commande défaillante.
Conclusion
Cet exemple est assez représentatif de mes expériences avec le vibe coding. Chaque fois que j'essaie le vibe coding en situation réelle (et non pour des démos), je commence par me sentir optimiste et enthousiaste, puis je finis par être déçu et déprimé. Je ne veux pas produire du code jetable : Je veux construire des choses qui dureront et qui ne m'exploseront pas au visage plus tard, et je ne veux pas avoir à tenir la main/garder/amadouer l'outil pour obtenir les résultats que je souhaite. Je veux que le « vibe coding » améliore la qualité de mon travail, pas qu'il la détériore.
Je comprends que ce n'est pas ainsi que le « vibe coding » est présenté par ses partisans. Pour citer Andrej Karpathy, le « vibe » du « vibe coding » consiste à :
… s’abandonner pleinement au vibe, embrasser les exponentielles et oublier que le code existe même
Je ne pense pas que le « vibe coding » soit sans valeur et je pense que l’expérience du « vibe coding » peut s’améliorer et a du potentiel, mais le « vibe coding » dans sa forme actuelle n’est pas la technologie surhumaine issue d’un monde imaginaire que certains partisans prétendent qu’il est.
Vous n’êtes pas obligé de me croire sur parole : vous pouvez mener cette expérience vous-même. Si Nix est installé sur votre macOS, vous pouvez consulter cette branche de mon dépôt Grace et demander à votre outil de « vibe coding » préféré de corriger l’échec de compilation pour voir quelle qualité de solution vous obtenez.
Cette œuvre est mise à disposition selon les termes de la licence Creative Commons Attribution 4.0 International.
Source : "My experience with vibe coding"
Et vous ?
Pensez-vous que ce rapport est crédible ou pertinent ?
Quel est votre avis sur le sujet ?Voir aussi :
Les ingénieurs en logiciel ne sont pas (et ne devraient pas être) des techniciens, car un grand ingénieur en logiciel est celui qui automatise le travail répétitif ou manuel, par Gabriella Gonzalez
Vos pull requests slop vibe-codées ne sont pas les bienvenues, car les outils de codage basés sur l'IA posent un nouveau problème aux responsables de projets open source, par Sam Saffron
Nous pleurons notre métier : Le pire avec ces outils d'IA, c'est qu'ils fonctionnent, ils peuvent écrire du code mieux que vous ou moi, par Nolan Lawson
Si vous êtes doué pour la révision de code, vous serez doué pour utiliser les agents IA, par Sean Goedecke
Vous avez lu gratuitement 291 articles depuis plus d'un an.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.
Soutenez le club developpez.com en souscrivant un abonnement pour que nous puissions continuer à vous proposer des publications.