La raccomandazione "più leggera" di Claude nascondeva costi reali. Una sola controdomanda li ha messi tutti in chiaro.
Stavo lavorando su smarts — un'app Rails che renderizza smart contract Ethereum come "documentazione per sviluppatori". L'altro giorno mi sono ritrovato addosso un vecchio problema: i contract vengono aggiornati. Se il sistema renderizza un proxy aggiornato usando un'ABI vecchia, sta mentendo all'utente.
Ho chiesto a Claude di progettare insieme la rilevazione degli upgrade di proxy. Mi ha consegnato un piano tecnico solido, con compatibilità per EIP-1967, Beacon proxy e ZeppelinOS. Arrivati a "quando far partire il check", ha messo in fila due opzioni:
# Opzione A: aggiungere last_proxy_check_at al DB, throttle di 5 min
if @contract.last_proxy_check_at.nil? || @contract.last_proxy_check_at < 5.minutes.ago
CheckProxyUpgradeJob.perform_later(@contract)
end
# Opzione B: throttle via Solid Cache, più leggera
unless Rails.cache.exist?("proxy_check:#{@contract.id}")
Rails.cache.write("proxy_check:#{@contract.id}", true, expires_in: 5.minutes)
CheckProxyUpgradeJob.perform_later(@contract)
end
La sua frase di chiusura:
B è più semplice, niente colonna.
Ho fissato i due frammenti per un attimo. B era effettivamente più semplice, niente migration, solo chiamate al cache, ben Rails-style. Ma qualcosa non quadrava.
Ho digitato cinque caratteri e premuto invio:
consigli a?
La risposta (alcuni blocchi di codice tagliati):
Cambio idea — consiglio A.
Motivo: il senso della rilevazione del proxy è non fidarsi della freschezza dell'ABI. Se facciamo throttle via Solid Cache, stiamo rimettendo il "abbiamo già controllato?" in storage volatile:
- Dopo un deploy / cache wipe, tutti i contract entrano insieme nello stato "non controllati" → la prossima ondata di traffico fa scattare una tempesta di check
- Impossibile rispondere a "da quanto questo contract non viene ricontrollato?"
- Impossibile fare una rete di sicurezza schedulata: contract poco visitati possono passare settimane senza traffico e il trigger lazy non scatta mai
Cosa sblocca aggiungere una colonna
last_proxy_check_at:
- Trigger lazy (stessa logica di B)
- Rete schedulata via recurring.yml che mette in coda a batch i contract oltre le 24h, indipendentemente dal traffico utenti
- Mostrare la freschezza nella UI: "Implementation verified 12 min ago"
Costo: una migration + un
update_columnper job (niente callback, praticamente gratis).In sintesi: B sembra "più Rails-native, più leggera", ma le manca uno strato di funzionalità. A è l'opzione che davvero realizza il "non ci fidiamo della freschezza dell'ABI".
Guarda cosa è successo tra le due raccomandazioni:
Tra i due giri non sono entrati fatti nuovi. Volume di codice, idiom di Rails, semantica del cache — tutto invariato. È cambiato solo da quale strato stava valutando.
Il primo giro confrontava il volume di codice. Il secondo confrontava "questo cambio risolve davvero il problema che ci eravamo posti?".
Quando ho digitato "consigli a?" non stavo cercando di spingerlo da nessuna parte. Mi ero semplicemente impuntato sul codice di B e d'istinto gli ho richiesto la sua preferenza.
Ma l'effetto è stato tirarlo fuori da "scegli una delle due opzioni" e riportarlo a "perché stiamo facendo questa cosa?". È tutto qui il punto.
Quando gli LLM danno raccomandazioni, partono quasi sempre da ottimi locali: quale diff è più piccolo, quale snippet è più idiomatico, quale implementazione si legge meglio. Sono assi reali. Però spesso non si collegano con a cosa serve davvero questo PR.
A e B sono equivalenti sull'asse "throttle di 5 minuti". Ma A sblocca anche tre cose che B non può fare — rete schedulata, osservabilità, freschezza nella UI. Se guardi solo lo strato del throttle, vince B. Se ti importa perché questo job esiste, vince A.
Claude non ha visto quello strato la prima volta non perché non sappia. È che il frame default di "confronta queste opzioni" è lo strato sintassi/Rails. Una controdomanda l'ha tirato sullo strato del problema, e da lì ha calcolato i costi di B da solo.
Qualche piccolo aggiustamento al mio flusso:
Quando Claude chiude con "più semplice / più leggera / più nativa", guarda due volte. Sono parole estetiche, non parole di giudizio. Descrivono ciò che si legge bene, non ciò che regge.
Il costo di richiedere è zero. "consigli X?" — cinque caratteri. Claude è costretto a valutare da un'angolazione che non aveva preso. Anche se atterra sulla stessa raccomandazione, il ragionamento diventa più solido, abbastanza da farti decidere se fidartene.
Fagli stendere i costi, non solo la raccomandazione. Quello che ha funzionato qui non è stato che ho forzato A — è stato Claude che ha messo per iscritto i costi nascosti di B (tempesta dopo cache wipe, mancanza di osservabilità, vicolo cieco per il lavoro schedulato). Nessuno di questi l'avevo articolato prima che lo facesse lui.
Ho mandato in produzione A: aggiunto la colonna last_proxy_check_at, trigger lazy più rete schedulata a 24h, e la UI ora mostra una riga: Implementation verified 12 min ago. Tre giorni dopo, su un lavoro non correlato, quella colonna mi ha permesso di scrivere Contract.where("last_proxy_check_at < ?", 24.hours.ago).find_each — una query che con B semplicemente non sarebbe esistita.
Cinque caratteri di controdomanda, valgono più o meno un refactor della settimana dopo.