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.
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:
- classificare un errore in tecnico / progettuale / di prodotto;
- identificare a colpo d’occhio i 15 pattern ricorrenti;
- 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:
- cerca stato duplicato;
- cerca
catchche nasconde; - cerca
any, fallback e valori hardcoded; - cerca azioni distruttive senza conferma;
- 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:
- Tecnici: cerca async/await, stato duplicato, cleanup, validazione, race.
- Progettuali: cerca astrazioni, nomi, responsabilità, hardcoded, fallback.
- Prodotto: prova flusso reale, stati vuoti, errori, tastiera, microcopy.
- Annota problemi prioritari (non risolvere subito tutti).
- 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:
awaitmanca sufetch().then().loadUserritorna immediatamente Promisequasi sempre. - Errore inghiottito:
catch (e) { return null }. Diagnosi impossibile. - Concatenazione URL:
'/api/user/' + idsenza encoding di id. Se id contiene/, rotto.
Progettuali:
- Nome vago:
datainvece diuser. - Responsabilità mischiate:
loadUserfa 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.namecrasha 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.
- 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.
- 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.
- 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.