Vai al contenuto

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.

1
2
3
4
5
6
7
// Esempio: test che riproduce il bug
func TestGetOrdersEmpty(t *testing.T) {
    svc := NewService(&fakeRepo{orders: nil})
    result, err := svc.GetOrders(context.Background(), "user-123")
    require.NoError(t, err)
    require.Empty(t, result) // Questo test FALLISCE — il bug è riprodotto
}

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.
1
2
3
4
# Dopo il fix
go test ./orders/... -v          # Test di riproduzione
go test ./... -count=1           # Suite completa
git diff                         # Verifica diff minimale

Template di prompt 📋

Template 1 — Diagnosi

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CONTESTO: [descrizione del bug]
TEST DI RIPRODUZIONE: [comando che fallisce]
STACK TRACE / LOG: [output completo]

TASK:
1. Esamina lo stack trace e individua la sequenza di chiamate e i file coinvolti.
2. Formula 3 ipotesi sulla causa radice. Per ogni ipotesi:
   - Quale condizione ha scatenato l'errore
   - Il meccanismo di propagazione
   - Cosa ispezionare per confermarla
NON applicare modifiche. Rispondi solo con l'analisi.

Template 2 — Correzione

1
2
3
4
5
6
7
CONTESTO: L'ipotesi confermata è [DESCRIZIONE]. Il test [NOME_TEST] fallisce con [ERRORE].
TASK:
1. Applica la correzione strettamente necessaria in [FILE_TARGET].
2. Assicurati che la modifica rispetti type check e gestione eccezioni del repository.
3. Verifica che il test passi.
4. Rimuovi ogni log temporaneo.
5. NON modificare file adiacenti non responsabili dell'errore.

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 📚

Ultimo aggiornamento il