modulo 07 / lezione 53

Leggere e verificare codice generato

Errori comuni del codice generato e come accorgersene presto

Anticipare pattern di errore ricorrenti prima che diventino bug difficili o debito tecnico.

75 min Vibe Coding

Errori comuni del codice generato e come accorgersene presto

I modelli AI sbagliano in modo sistematico, non casuale. Stessi pattern di errore ricorrono attraverso diversi modelli, diverse versioni, diversi domini. Conoscerli a memoria significa scoprirli al primo sguardo, non dopo che sono in produzione. Una checklist mentale di 15 voci basta a coprire 80% dei problemi tipici.

Perché questa lezione conta

Bug nel codice generato dall’AI non sono random. Sono prevedibili. Se sai dove guardare, li trovi in 5 minuti. Se non lo sai, li trovi in produzione 2 settimane dopo. Il valore della lezione è imparare la mappa: tre categorie di errore, 5 esempi per categoria. Quel 15 sale a vista d’occhio.

Obiettivo operativo

Alla fine devi saper:

  1. classificare un errore in tecnico / progettuale / di prodotto;
  2. identificare a colpo d’occhio i 15 pattern ricorrenti;
  3. usare una checklist personale prima del merge.

Nucleo della lezione

1. Errori tecnici

Sbagli nella sostanza del codice. Programma rotto o fragile.

Stato duplicato: stessa info in due posti che possono divergere. Esempio: total salvato accanto a items, calcolato a parte. Aggiunta a items, total non aggiornato.

Async non gestito: await mancante, promise floating, errori non catched. Esempio: fetchUser(id); useUser(id) — fetch non aspettato.

Memory leak / cleanup mancante: setInterval senza clearInterval, listener event mai rimossi, subscription RxJS non chiuse. Sintomo: app rallenta nel tempo.

Validazione solo client: lezione 26. Server fida.

Race condition: due chiamate concorrenti modificano stesso dato senza lock. Vince l’ultima, perde la prima.

2. Errori progettuali

Codice che funziona ma è strutturato male. Diventerà costoso.

Astrazioni premature: BaseManager, AbstractFactory, gerarchia di classi per 3 casi d’uso. Aggiungi complessità ora per flessibilità che non userai mai.

Nomi vaghi: data, info, helper, util. Il lettore deve aprire la funzione per capire cosa fa.

Responsabilità confuse: una funzione che valida + persiste + invia email + logga. Test difficili, modifiche pericolose.

Dati hardcoded: if (role === "admin") invece di costante o enum. Cambia il valore, devi cercare in tutto il codice.

Fallback silenziosi: try { ... } catch { return null }. Errore inghiottito, problema invisibile, debug difficile.

3. Errori di prodotto

Il codice fa, l’utente soffre. Spesso ignorati nelle review tecniche.

Stati vuoti mancanti: lista vuota mostra spazio bianco invece di “nessun elemento ancora, aggiungi il primo”.

Messaggi inutili: errore “Unknown error” o “Errore durante l’operazione”. Niente di azionabile.

Accessibilità dimenticata: niente focus state, contrasto troppo basso, click handler senza aria-label, form senza tag label.

Flussi non recuperabili: utente sbaglia a metà flusso multi-step, deve ricominciare da zero.

Microcopy approssimativo: “Click here”, “Submit”, “Error”, in app italiana. Tradotto male o non tradotto.

Caso guidato

Prototipo generato: lista task con filtro e salvataggio. A prima vista funziona. Review mirata:

const [tasks, setTasks] = useState([]);
const [filteredTasks, setFilteredTasks] = useState([]);

Errore: stato derivato duplicato. filteredTasks può divergere da tasks. Meglio calcolarlo:

const filteredTasks = tasks.filter(matchesFilter);

Altro frammento:

catch (error) {
  return [];
}

Errore: fallback silenzioso. L’utente vede lista vuota e pensa di non avere dati. Meglio distinguere vuoto da errore.

Altro frammento:

<button onClick={() => deleteTask(task.id)}>Delete</button>

Errore di prodotto/accessibilità: testo inglese in UI italiana, nessuna conferma, nessuno stato loading, nessuna gestione errore.

Metodo di lettura:

  1. cerca stato duplicato;
  2. cerca catch che nasconde;
  3. cerca any, fallback e valori hardcoded;
  4. cerca azioni distruttive senza conferma;
  5. cerca messaggi mancanti per vuoto, errore, loading.

Il codice generato spesso è “demo-completo”: buono per mostrare, fragile per usare. Il tuo lavoro è trasformarlo in comportamento affidabile.

Procedura pratica

Per ogni codice generato, scan in 5 minuti:

  1. Tecnici: cerca async/await, stato duplicato, cleanup, validazione, race.
  2. Progettuali: cerca astrazioni, nomi, responsabilità, hardcoded, fallback.
  3. Prodotto: prova flusso reale, stati vuoti, errori, tastiera, microcopy.
  4. Annota problemi prioritari (non risolvere subito tutti).
  5. Fix mirati o ticket per quelli da rimandare.

Tempo medio: 5-10 minuti per file medio. Ritorno: ore non spese in debug futuro.

Laboratorio guidato

Codice generato (esempio compatto):

// data fetcher
async function loadUser(id) {
  try {
    const data = fetch('/api/user/' + id).then(r => r.json())
    setUser(data)
    return data
  } catch (e) {
    return null
  }
}

// caller
function ProfilePage({ userId }) {
  const user = loadUser(userId)
  return <div>Welcome {user.name}</div>
}

Errori a colpo d’occhio:

Tecnici:

  • async non gestito: await manca su fetch().then(). loadUser ritorna immediatamente Promise quasi sempre.
  • Errore inghiottito: catch (e) { return null }. Diagnosi impossibile.
  • Concatenazione URL: '/api/user/' + id senza encoding di id. Se id contiene /, rotto.

Progettuali:

  • Nome vago: data invece di user.
  • Responsabilità mischiate: loadUser fa fetch + setUser. Difficile testare separatamente.
  • Side effect (setUser) dentro funzione che dovrebbe essere pura data fetcher.

Prodotto:

  • Niente loading state.
  • Niente error state per utente.
  • Niente “user not found”.
  • user.name crasha se user è null.

10 problemi in 12 righe. Scan veloce → 5 minuti per identificare. Fix → 20 minuti.

Output atteso

Una checklist personale “errori comuni post-generazione”:

TECNICI
□ async/await consistente (no promise floating)
□ stato duplicato (calcolato vs salvato)
□ cleanup di listener/interval/subscription
□ validazione server-side per input critici
□ race condition su operazioni concorrenti

PROGETTUALI
□ astrazioni giustificate da uso reale
□ nomi specifici (no data/info/helper)
□ una funzione = una responsabilità
□ valori magici → costanti con nome
□ errori non inghiottiti (almeno log)

PRODOTTO
□ stato vuoto presente
□ messaggi errore azionabili e tradotti
□ accessibilità minima (focus, aria-label, contrasto)
□ flussi recuperabili (no perdita progresso)
□ microcopy in lingua corretta

Priorità di correzione

Non tutti gli errori hanno lo stesso peso. Correggi prima ciò che può perdere dati, esporre dati, dare permessi sbagliati o rendere impossibile recuperare da un errore. Poi correggi fragilità strutturali che renderanno difficile il prossimo cambiamento. Solo dopo occupati di stile, naming minore e pulizia cosmetica.

Una buona lista di review non dice solo “c’è duplicazione”. Dice: “questa duplicazione riguarda la regola di autorizzazione, quindi è rischio alto” oppure “questa duplicazione riguarda due label UI, rischio basso”. Priorità sbagliata = molto lavoro e poca sicurezza reale.

Studio passo passo

Studia questa lezione in tre passaggi, senza correre subito al prompt o al codice.

  1. Prima lettura: chiarisci l’obiettivo: anticipare pattern di errore ricorrenti prima che diventino bug difficili o debito tecnico. Se non riesci a riscriverlo in una frase tua, rileggi il nucleo della lezione.
  2. Seconda lettura: trasforma i contenuti in domande operative:
  • Errori tecnici: stato duplicato, async non gestito, memory leak, cleanup mancante, validazione solo lato client.
  • Errori progettuali: astrazioni premature, nomi vaghi, responsabilità confuse, dati hardcoded, fallback silenziosi.
  • Errori di prodotto: stati vuoti mancanti, messaggi inutili, accessibilità dimenticata, flussi non recuperabili.
  1. Applicazione: svolgi il laboratorio: revisionare un prototipo generato e marcare gli errori secondo le tre categorie. L’output da ottenere è: un catalogo di errori ricorrenti da usare come lista di controllo prima del deploy.

Prima di passare alla lezione successiva, controlla di avere un artefatto concreto: una tabella, un prompt, una checklist, un mini report, un diff commentato o una decisione scritta. Nel vibe coding la comprensione resta astratta finché non produce una traccia verificabile.

Checklist di verifica

  • conosco a memoria le 3 categorie e ~15 pattern;
  • applico checklist a ogni feature non banale;
  • distinguo tecnico (rompe) da progettuale (rallenta) da prodotto (frustra utente);
  • ho rifiutato almeno una volta codice perché aveva troppi pattern di errore.

Errori comuni

Cercare solo errori tecnici. Solo runtime crash. Errori progettuali e prodotto invisibili in test ma costosi.

Fix tutti subito. Risultato: PR enorme, rischio nuovi errori. Priorità: blocking → consigliato → nit.

Skippare scan perché AI “ha fatto bene”. Pattern errori sono sistematici, indipendenti da quanto sembra “pulito”.

Niente checklist scritta. Affidi a memoria, dimentichi pattern. Stampa o salva.

Una sola tipologia per file. “Tecnici sì, prodotto poi”. Mescola: i tre livelli si intersecano.

Test only happy path. Errori si rivelano in casi limite. Test edge case scopre.

Prompt di lavoro

Rivedi questo codice cercando errori comuni del codice generato.

[INCOLLA CODICE]

Contesto: [tipo di codice, dove sarà usato]

Per ognuna delle 3 categorie:

1. TECNICI: stato duplicato, async non gestito, memory leak, validazione assente, race condition.
2. PROGETTUALI: astrazioni premature, nomi vaghi, responsabilità mischiate, valori magici, errori inghiottiti.
3. PRODOTTO: stati vuoti mancanti, messaggi inutili, accessibilità, flussi non recuperabili, microcopy approssimativo.

Output: per ogni problema trovato → categoria, posizione (riga), severità (blocking/consigliato/nit), motivo, suggerimento puntuale.

Concludi: top 3 problemi per priorità di fix.

NON riscrivere il codice. Solo identificare.

Esercizio finale

Apri 3 file recenti generati dall’AI e fai scan in 5 minuti ciascuno con la checklist. Conta problemi trovati per categoria. Tipicamente: 5-8 per file. Quei numeri sono la base di partenza. Iterando su scan di routine, scendi a 2-3 perché chiedi al modello di evitare i pattern noti già nel prompt iniziale. Prevenzione > correzione.