Free

Lasciare che Claude faccia i refactor

Un refactor non ha sintomi — non vedi quando Claude sbaglia. Tre guardrail: test, commit atomici, clic manuale.


Il refactoring è il compito in cui Claude è più pericoloso. Un bug ha sintomi — il bottone non fa niente, il valore è undefined, stack trace — quindi riesci a dire se il fix di Claude funziona. Un refactor non ne ha. "Ancora gira" può voler dire "i test ancora passano" mentre il comportamento è silenziosamente cambiato, non te ne sei accorto, e una settimana dopo la produzione esplode.

Di recente ho fatto un refactor abbastanza grosso con Claude su how2claude: migrazione dei pagamenti crypto x402 da un PaymentHandler + FacilitatorClient scritti a mano (139 righe) alla gem x402-rails, e nello stesso momento estrazione del mapping di campi di Purchase.create! / Subscription.create! — duplicato in due controller — in metodi di classe sul model. Un commit, 4 file modificati, 2 cancellati, 2 aggiunti.

Il mio prompt era una parola: "refactor."

Poteva essere così breve perché intorno c'erano i guardrail.

Guardrail #1: mai refactorizzare senza test

Quando il branch è arrivato a quel punto aveva 221 test. Tutti i percorsi critici del flusso di pagamento erano coperti.

L'azione di default di Claude prima di un refactor non è "guarda prima i test." Quindi gli ho detto di lanciare bin/rails test prima, confermare verde, e poi toccare qualunque cosa.

Dopo il refactor, lanciarli di nuovo. Ancora verdi. Questo non significa zero regressioni — significa che il comportamento noto non è rotto.

Se il tuo codepath non ha copertura di test, fai scrivere a Claude un test minimo che blocchi il comportamento attuale. Fallo girare, committalo. Poi refactor. Altrimenti quello che sta facendo non è refactor, è riscrittura — e non hai modo di verificare l'equivalenza.

Guardrail #2: fagli dividere il cambiamento in commit atomici

Questo refactor erano in realtà due cose:

  1. Backend x402: scritto a mano → gem
  2. Mapping di campi di Purchase / Subscription: controller → metodi di classe sul model

Sul frontend c'era una terza: riscrivere il flusso di signing lato JS con viem + x402-fetch.

Ho fatto dividere a Claude sui confini naturali: backend + estrazione del model in un commit (9f3e239), frontend in un commit separato (93746d8). Ogni commit porta una descrizione completa, elenco file, e il perché del cambio.

Benefici:
- I diff restano leggibili. Un commit, una cosa.
- Granularità di rollback. Se la produzione incappa in un bug frontend, git revert 93746d8 rollbacka solo il frontend, lasciando il backend.
- L'attenzione di Claude stessa resta focalizzata. Un commit, una cosa — e la sua attenzione copre solo quella cosa.

Guardrail #3: leggere il diff prima di dire "fatto"

Una volta completato il refactor, faccio fermare Claude e gli faccio mostrare git diff --staged. Non lanciare i test. Non lanciare l'app. Leggi il diff prima.

Segnali che scansiono:

  • Cosa ha cancellato? app/services/x402/payment_handler.rb cancellato per intero — ok, quello è il punto della migrazione alla gem. Ma se cancella qualcosa che non gli ho chiesto di toccare, mi fermo e chiedo.
  • Il mapping di campi è cambiato? Purchase.create!(wallet_address: verify_result["payer"], ...)Purchase.record_x402!(payment:, settlement:) ora legge payment[:payer]. La sorgente è cambiata (request.env della gem vs valore di ritorno del vecchio client), ma i campi devono corrispondere uno a uno.
  • Modifiche "lungo la strada." Claude adora sistemare roba che "sembra sbagliata" mentre refactorizza — riformulare messaggi di errore, rinominare variabili, estrarre un metodo che secondo lui dovrebbe esistere. Stai attento a queste. La promessa di un refactor è "equivalente nel comportamento." Un cambio al volo la rompe.

Due cose che Claude ha sbagliato questa volta

Trappola 1: i controller Stimulus della gem non si sono caricati in silenzio

La gem x402-rails porta con sé i propri Stimulus controller. Claude ha scritto il codice, i test sono passati verdi. Ho cliccato manualmente il bottone di pagamento — niente.

Motivo: config/importmap.rb aveva un pin per @hotwired/stimulus che puntava a un file vendor inesistente, e importmap ha scartato quel pin silenziosamente. I controller della gem non si sono mai caricati. I test non possono beccare questo perché bin/rails test non esegue JS.

Trappola 2: YAML ha parsato 0x... come intero

wallet_address: 0x833589... — senza virgolette. YAML ha visto il prefisso 0x, l'ha letto come intero esadecimale. Il facilitator ha ricevuto un non-stringa e ha rifiutato. Claude non si è fermato a pensare alle regole di parsing YAML mentre scriveva la config.

Entrambe le trappole sono state prese perché ho cliccato il bottone vero. Test passati non è la stessa cosa di feature funzionante. La verifica manuale dopo un refactor non è opzionale.

Il flusso completo di refactor con Claude

  1. Lancia tutta la suite di test. Conferma verde. Se il codice target non ha copertura, fai scrivere a Claude un test minimo che blocca il comportamento attuale, e committalo.
  2. Di' quale superficie vuoi estrarre. "Refactor" basta quando la duplicazione è ovvia; quando non lo è, di' "estrai il mapping di campi da X in metodi di classe su Y."
  3. Dividi in commit atomici. Un commit, una cosa.
  4. Leggi il diff tu stesso. Cosa è stato cancellato, se il mapping di campi regge, se ha cambiato qualcosa di passaggio.
  5. Rilancia i test. Verde.
  6. Se tocca un percorso visibile all'utente, clicca attraverso la feature manualmente. I layer che i test non raggiungono — JS, importmap, CDN, parsing YAML — li devi vedere con i tuoi occhi.

Il refactoring è lo scenario "lascia guidare Claude" a più alto rischio. I guardrail non sono per Claude. Sono per te — perché quando Claude sbaglia qualcosa, lo prendi in 5 minuti invece che in un incendio di produzione.