Free

Laisser Claude refactoriser

Un refactor n'a pas de symptômes — tu ne vois pas quand Claude se trompe. Trois garde-fous : tests, commits atomiques, clic manuel.


Le refactoring est la tâche où Claude est le plus dangereux. Un bug a des symptômes — bouton qui ne fait rien, valeur undefined, stack trace — tu peux donc dire si le fix de Claude est bon. Un refactor n'en a pas. « Ça tourne encore » peut vouloir dire « les tests passent encore » pendant que le comportement a changé en silence, tu n'as pas vu, et la prod explose une semaine plus tard.

J'ai récemment fait un refactor d'une certaine taille avec Claude sur how2claude : migration des paiements crypto x402 d'un PaymentHandler + FacilitatorClient fait main (139 lignes) vers la gem x402-rails, et en même temps extraction du mapping de champs de Purchase.create! / Subscription.create! — dupliqué dans deux controllers — vers des méthodes de classe sur le model. Un commit, 4 fichiers modifiés, 2 supprimés, 2 ajoutés.

Mon prompt tenait en un mot : « refactor ».

Il pouvait être aussi court parce qu'il y avait des garde-fous autour.

Garde-fou #1 : pas de refactor sans tests

Au moment où la branche en était là, elle avait 221 tests. Tous les chemins critiques du flow de paiement étaient couverts.

Le réflexe par défaut de Claude avant un refactor n'est pas « regarde d'abord les tests ». Alors je lui ai demandé de lancer bin/rails test d'abord, de confirmer que c'était vert, et ensuite de toucher à quoi que ce soit.

Après le refactor, relancer. Toujours vert. Ça ne veut pas dire « aucune régression » — ça veut dire que le comportement connu n'est pas cassé.

Si ton codepath n'a pas de couverture, fais écrire à Claude un test minimal qui fige le comportement actuel. Fais-le tourner, commite. Ensuite refactor. Sinon ce qu'il fait n'est pas un refactor, c'est une réécriture — et tu n'as aucun moyen de vérifier l'équivalence.

Garde-fou #2 : fais-lui découper le changement en commits atomiques

Ce refactor, c'était en réalité deux choses :

  1. Backend x402 : fait main → gem
  2. Mapping de champs de Purchase / Subscription : controller → méthodes de classe sur le model

Côté frontend, il y avait une troisième : réécrire le flow de signature côté JS avec viem + x402-fetch.

J'ai fait découper Claude selon les frontières naturelles : backend + extraction de model dans un commit (9f3e239), frontend dans un commit séparé (93746d8). Chaque commit porte une description complète, la liste des fichiers, et le pourquoi du changement.

Bénéfices :
- Les diffs restent lisibles. Un commit, une chose.
- Granularité du rollback. Si la prod attrape un bug frontend, git revert 93746d8 n'annule que le frontend, le backend reste.
- L'attention de Claude lui-même reste concentrée. Un commit, une chose — et son attention ne couvre que cette chose-là.

Garde-fou #3 : lis le diff avant de dire que c'est fini

Une fois le refactor terminé, je fais arrêter Claude et je lui demande de me montrer git diff --staged. Pas de tests, pas d'appli lancée, lire le diff d'abord.

Signaux que je scanne :

  • Qu'a-t-il supprimé ? app/services/x402/payment_handler.rb supprimé entièrement — OK, c'est l'objet de la migration vers la gem. Mais s'il supprime quelque chose que je ne lui ai pas demandé de toucher, je m'arrête et je demande.
  • Le mapping de champs a-t-il changé ? Purchase.create!(wallet_address: verify_result["payer"], ...)Purchase.record_x402!(payment:, settlement:) lit maintenant payment[:payer]. La source a changé (request.env de la gem vs valeur de retour de l'ancien client), mais les champs doivent se correspondre un à un.
  • Les changements « au passage ». Claude adore corriger ce qui « a l'air faux » pendant qu'il refactorise — reformuler les messages d'erreur, renommer des variables, extraire une méthode qu'il juge nécessaire. Méfie-toi. La promesse d'un refactor, c'est « équivalent en comportement ». Un changement de passage casse cette promesse.

Deux choses que Claude a ratées cette fois

Piège 1 : les Stimulus controllers de la gem n'ont pas été chargés, en silence

La gem x402-rails embarque ses propres Stimulus controllers. Claude a écrit le code, les tests sont passés au vert. J'ai cliqué à la main sur le bouton de paiement — rien.

Raison : dans config/importmap.rb, le pin pour @hotwired/stimulus pointait vers un fichier vendor inexistant, et importmap a silencieusement supprimé ce pin. Les controllers de la gem ne se sont jamais chargés. Les tests ne peuvent pas attraper ça parce que bin/rails test n'exécute pas le JS.

Piège 2 : YAML a parsé 0x... comme un entier

wallet_address: 0x833589... — sans guillemets. YAML a vu le préfixe 0x, l'a lu comme un entier hexadécimal. Le facilitator a reçu une non-chaîne et a refusé. Claude ne s'est pas arrêté pour penser aux règles de parsing YAML en écrivant la config.

Les deux pièges ont été attrapés parce que j'ai cliqué sur le vrai bouton. Tests au vert ≠ feature qui marche. La vérification manuelle après un refactor n'est pas optionnelle.

Le flow complet d'un refactor avec Claude

  1. Lance toute la suite de tests. Confirme le vert. Si le code cible n'a pas de couverture, fais écrire à Claude un test minimal qui fige le comportement actuel et commite-le.
  2. Dis quelle surface tu veux extraire. « Refactor » suffit quand la duplication est évidente ; sinon dis « extrais le mapping de champs de X vers des méthodes de classe sur Y ».
  3. Découpe en commits atomiques. Un commit, une chose.
  4. Lis le diff toi-même. Ce qui a été supprimé, si le mapping de champs tient, s'il y a eu un changement de passage.
  5. Relance les tests. Vert.
  6. Si ça touche un chemin visible par l'utilisateur, clique manuellement dans la feature. Les couches que les tests n'atteignent pas — JS, importmap, CDN, parsing YAML — il faut les voir de ses propres yeux.

Le refactoring est le scénario « laisser Claude conduire » à plus haut risque. Les garde-fous ne sont pas pour Claude. Ils sont pour toi — pour que quand Claude se trompe, tu l'attrapes en 5 minutes plutôt que dans un incendie de prod.