Debug e bug fixing 🐛
Il debug è il caso d’uso dove l’agente è più utile e più pericoloso allo stesso tempo. Utile perché può correlare stack trace, log e codice in pochi secondi. Pericoloso perché un agente che “risolve” un bug nascondendone i sintomi è peggio di nessuna risoluzione. La differenza sta nella qualità del contesto che gli fornisci e nella tua capacità di valutare se la diagnosi ha senso.
Un bug risolto senza comprenderne la causa radice non è un fix: è un ponte verso il prossimo bug.
Quando usare l’agente per questo caso 🎯
- Stack trace chiaro — hai un errore con stack trace completo e vuoi capire la causa.
- Bug riproducibile — sai come replicare il problema e vuoi una diagnosi rapida.
- Log strutturati disponibili — hai log di runtime che l’agente può correlare al codice.
- Bug semplici-medium — null pointer, indice fuori bound, errore di tipo, logica errata.
- Aggiunta di test di regressione dopo il fix — vuoi che il bug non torni.
Quando NON usare l’agente ⛔
- Race condition e concorrenza — l’agente non può osservare l’interleaving dei thread in runtime.
- Performance e colli di bottiglia — serve profiling, non analisi statica.
- Bug architetturali — problemi di design che richiedono riprogettazione.
- Non hai il contesto — se non riesci a descrivere il problema, l’agente nemmeno.
- L’agente è in loop — dopo 2 tentativi falliti, la sessione va resettata.
Prompt di apertura 📝
“CONTESTO: [descrizione del problema]. STACK TRACE: [stack trace completo]. FILE RILEVANTI: [elenco]. TASK: analizza la causa radice e proponi la correzione minima. NON applicare modifiche.”
Esempio concreto:
“CONTESTO: il endpoint GET /orders restituisce 500 quando l’utente non ha ordini. TEST: TestGetOrdersEmpty fallisce con ’nil pointer dereference’. STACK TRACE: orders/handler.go:47 → orders/service.go:112. FILE RILEVANTI: orders/handler.go, orders/service.go, orders/repository.go. TASK: analizza la causa radice e proponi la correzione. NON applicare modifiche.”
Flusso di lavoro 🔧
1. Riproduzione del bug 🎯
Prima di tutto, assicurati di poter replicare il problema in modo deterministico. Se non esiste un test che fallisce, crealo prima di chiedere all’agente di fixare.
|
|
Il test fallente è la tua ancora di convalida. Senza di esso, non puoi sapere se il fix funziona davvero.
2. Formulazione delle ipotesi 🧠
Chiedi all’agente di analizzare il contesto e formulare tre ipotesi distinte sulla causa radice, ordinate per probabilità. Senza applicare modifiche.
“Analizza stack trace e codice. Formula 3 ipotesi sulla causa radice, ordinate per probabilità. Per ogni ipotesi: (1) quale condizione ha scatenato l’errore, (2) il meccanismo di propagazione, (3) cosa ispezionare per confermarla. NON applicare modifiche.”
Questo passaggio è critico: se l’agente salta direttamente alla correzione senza formulare ipotesi, sta facendo shotgun debugging.
3. Verifica delle ipotesi 🔍
Per ogni ipotesi, verifica che sia verificabile. Se l’agente non riesce a determinare la causa con certezza, chiedi di inserire log temporanei o asserzioni per verificare empiricamente.
“Inserisci log temporanei per verificare l’ipotesi #2: [descrizione]. Esegui il test e riporta l’output.”
4. Correzione atomica ✏️
Una volta confermata l’ipotesi, chiedi la correzione minima necessaria. Non voglio un refactoring del modulo: voglio il fix per il bug.
“Applica la correzione strettamente necessaria in [file] per risolvere [ipotesi confermata]. Verifica che il test passi. Rimuovi ogni log temporaneo.”
5. Validazione e regressione ✅
Dopo il fix:
- Esegui il test di riproduzione: deve passare.
- Esegui l’intera suite di test: nessuna regressione.
- Verifica il diff: nessuna modifica collaterale.
|
|
Template di prompt 📋
Template 1 — Diagnosi
|
|
Template 2 — Correzione
|
|
Anti-pattern 🚫
- Shotgun debugging — lasciare che l’agente modifichi codice casualmente nella speranza che l’errore sparisca. Prima riproduci, poi diagnostica, poi fixa.
- Loop di errore — se l’agente propone sempre la stessa soluzione dopo 2 tentativi, resettare la sessione con nuovi vincoli.
- Mascheramento delle eccezioni — accettare fix che racchiudono il codice in try-catch vuoti o ritornano valori di default arbitrari.
- Delega completa — “risolvi il mio bug” senza contesto è come chiedere al medico di curarti senza raccontargli i sintomi.
- Log permanenti — non lasciare nel codice finale le istruzioni di debug introdotte durante la diagnosi.
Criteri di accettazione 📏
| Criterio | Requisito | Validazione |
|---|---|---|
| Minimal repro | Test che riproduce l’errore prima del fix | Verificare che fallisca prima e passi dopo |
| Nessuna regressione | Suite di test completa verde | go test ./... o equivalente |
| Igiene del codice | Nessun log temporaneo nel diff finale | git diff |
| Causa radice | La modifica affronta il meccanismo scatenante | Code review dell’ipotesi e della patch |
Note importanti ⚠️
Il debug è un processo iterativo ma controllato. Ogni ciclo deve avere: contesto → ipotesi → verifica → fix → validazione. Se un ciclo fallisce per più di due volte consecutivamente, terminare la sessione e riavviare con un prompt che integri l’esito negativo come vincolo esplicito.
Quando fermarsi e chiedere aiuto
L’agente non è lo strumento giusto per tutti i bug. Se il problema coinvolge:
- Concorrenza e race condition
- Performance e profiling
- Architettura e design
- Ambiente di esecuzione (produzione vs sviluppo)
…allora l’agente può aiutarti a inserire log e telemetria, ma la diagnosi e la decisione restano tue.
Riferimenti della serie 📚
- Writing prompts that work — i template di prompt in questo contesto
- Surviving sessions — gestione loop di errore
- Adding features to an existing codebase — quando il bug emerge durante lo sviluppo