modulo 07 / lezione 43

Leggere e verificare codice generato

Riconoscere codice fragile: duplicazioni, accoppiamenti, magic numbers

Sviluppare occhio per problemi strutturali che il codice generato spesso nasconde dietro una forma pulita.

75 min Vibe Coding

Riconoscere codice fragile: duplicazioni, accoppiamenti, magic numbers

Codice generato dall’AI è quasi sempre formattato bene. Indentato, virgolettato, commentato. Sembra elegante. La forma pulita può nascondere fragilità strutturali: duplicazioni, accoppiamenti rigidi, valori magici, responsabilità mischiate. Vedere oltre la forma è skill che si allena. Una volta sviluppata, leggi codice in 5 minuti dove prima ne servivano 30.

Perché questa lezione conta

Le fragilità non sono bug: sono scelte che rendono ogni futura modifica più costosa. Un magic number da solo non rompe niente; tre magic number sparsi in 5 file = il prossimo cambiamento ne tocca 3 invece di 1. Riconoscere questi pattern presto, prima che si moltiplichino, è prevenzione attiva.

Obiettivo operativo

Alla fine devi saper:

  1. nominare 6 categorie di fragilità tipiche;
  2. identificare almeno una fragilità in ogni file generato che leggi;
  3. distinguere fragilità “innocua per ora” da “che renderà costoso il prossimo cambio”.

Nucleo della lezione

1. Sei categorie di fragilità

Duplicazioni di logica: la stessa regola scritta in 2+ posti. Se devi modificarla, devi ricordartene di tutti i posti. Esempi: validazione email in tre file, calcolo subtotale in client e server, formattazione data in vari componenti.

Test: cerca regex/calcoli simili in più file. Se trovi pattern, candidato ad estrazione.

Condizioni ripetute: stesso if complesso in più punti. “Se utente è admin O proprietario E non bannato”. Quando aggiungi terzo permesso, devi cambiare in tutti i posti. Estrazione: funzione canEdit(user, resource).

Costanti sparse (magic numbers/strings): if (status === 3), if (role === "admin"), setTimeout(handler, 86400000). Cosa significa 3? Cosa rappresenta 86400000? Estrazione: enum/costante con nome.

Nomi generici: data, info, helper, manager, process. Non comunicano cosa fanno. Forzano lettura del corpo per capire.

Responsabilità mischiate: una funzione che valida + persiste + invia email + logga. Difficile testare, difficile riusare, difficile modificare un pezzo.

Accoppiamento implicito: componente A importa direttamente stato globale di componente Z. Se Z cambia, A si rompe ma non lo capisci finché non runni.

2. Accoppiamento e responsabilità

Due forme principali di accoppiamento:

Accoppiamento sui dati: due moduli condividono la stessa struttura dati senza interface esplicita. Cambiare la struttura rompe entrambi. Mitigazione: tipi/interface esplicite.

Accoppiamento temporale: A deve essere chiamato prima di B prima di C. Senza spiegazione esplicita, prima o poi qualcuno (o l’AI) inverte. Mitigazione: rendere l’ordine esplicito (returntype che non lascia bypass) o documentare.

Accoppiamento sull’ambiente: funzione legge da process.env direttamente, dipende da configurazione globale. Test difficili (devi settare env). Mitigazione: passare config come parametro.

Single responsibility: una funzione fa una cosa. “Una cosa” è soggettivo, ma test concreto: la descrivi in una frase senza “e”? Se serve “e”, probabilmente fa due cose.

3. Brutto vs fragile

Distinzione importante:

Codice brutto ma innocuo: funzione lunga, formattazione strana, commento ironico. Esteticamente non bello, ma funziona, non si rompe, modifiche locali sono semplici.

Codice fragile: cambiamento richiesto in punto X richiede modifiche in 5 punti. Bug sottili. Difficile testare. Refactor obbligatorio prima di ogni nuova feature.

Brutto si pulisce a richiesta. Fragile si paga ogni giorno.

Priorità di intervento: prima fragile, poi brutto. Non viceversa.

Caso guidato

Guarda questo frammento generato:

if (user.role === "admin" || user.role === "manager") {
  canEdit = true;
}

if (task.status === "done" || task.status === "archived") {
  disabled = true;
}

if (days > 30) {
  showWarning = true;
}

Non è “brutto” in modo evidente. È fragile perché le regole sono sparse:

  • ruoli scritti come stringhe in più punti;
  • stati task controllati localmente invece che in una funzione;
  • 30 è un magic number: giorni di cosa? scadenza? retention? avviso?

Refactor minimo:

const EDITOR_ROLES = new Set(["admin", "manager"]);
const CLOSED_TASK_STATUSES = new Set(["done", "archived"]);
const STALE_TASK_DAYS = 30;

const canEditTask = (user: User) => EDITOR_ROLES.has(user.role);
const isTaskClosed = (task: Task) => CLOSED_TASK_STATUSES.has(task.status);
const isTaskStale = (ageInDays: number) => ageInDays > STALE_TASK_DAYS;

Il vantaggio non è estetico. Ora puoi testare le regole, nominarle, cambiarle in un punto solo. Se domani arriva il ruolo owner, sai dove intervenire. Se archived non deve più disabilitare, la regola ha un nome e una sede.

Quando rivedi codice AI, chiediti sempre: “se questa regola cambia, quanti posti devo toccare?” Più la risposta cresce, più il codice è fragile.

Procedura pratica

Lettura veloce di un file generato:

  1. Scorri: 30 secondi a vista d’occhio. Vedi struttura macro: quanti import? quante funzioni? quanto è lungo?
  2. Magic numbers/strings: cerca numeri letterali e stringhe non costanti. Sono ognuno un debito.
  3. Funzioni lunghe: > 30-50 righe quasi sempre fa più cose. Candidate alla scomposizione.
  4. Nomi: data, info, temp, helper → bandiera. Cosa rappresentano davvero?
  5. Condizioni complesse: if (a && b || c && !d) → estrai in funzione.
  6. Effetti collaterali nascosti: funzioni che dovrebbero essere pure ma fanno fetch/log/scrivono. Sorpresa = fragilità.

Laboratorio guidato

Esempio file fragile (volutamente, per identificare):

async function processUserData(data: any) {
  if (data.role === 'admin' || (data.role === 'owner' && data.status === 1)) {
    const result = data.permissions.filter(p => p.code !== 'X' && p.code !== 'Y');
    await fetch('/api/log', { method: 'POST', body: JSON.stringify({ user: data.id, action: 'process' }) });
    setTimeout(() => updateUI(data.id), 5000);
    return result.length > 3 ? 'high' : 'low';
  }
  return null;
}

Fragilità identificate:

  • any come tipo: tipi non garantiti, runtime crash possibili.
  • Magic numbers: 1 (status), 5000 (delay), 3 (soglia).
  • Magic strings: 'admin', 'owner', 'X', 'Y', 'process'.
  • Condizione complessa inline: estrai in canProcess(user).
  • Responsabilità mischiate: filtra permessi + logga + schedula UI + ritorna classificazione.
  • Effetto collaterale nascosto: setTimeout dentro funzione async di “processo dati” è inatteso.
  • fetch con body non tipizzato: nessun controllo response, nessuna gestione errore.
  • Nome generico: processUserData, result. Cosa “processo” davvero?

Refactor in 5-6 funzioni separate, costanti, tipi. Fragilità elevata, ma se questo codice è in produzione e funziona, valuta priorità: tutto subito o solo le 2-3 più costose.

Output atteso

Una checklist personale “fragilità da segnalare in code review”, 6 categorie con esempi:

1. Duplicazione: stessa logica in 2+ posti
2. Magic number/string: valore letterale senza nome
3. Funzione gigante: > 30-50 righe, descrizione "e ... e ..."
4. Nome generico: data, info, manager, helper
5. Accoppiamento: import di stato globale, dipendenza temporale
6. Effetto collaterale nascosto: fetch/log/setTimeout in funzioni "pure"

Studio passo passo

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

  1. Prima lettura: chiarisci l’obiettivo: sviluppare occhio per problemi strutturali che il codice generato spesso nasconde dietro una forma pulita. Se non riesci a riscriverlo in una frase tua, rileggi il nucleo della lezione.
  2. Seconda lettura: trasforma i contenuti in domande operative:
  • Duplicazioni di logica, condizioni ripetute, costanti sparse, nomi generici e responsabilità mischiate.
  • Accoppiamento: componenti che conoscono troppi dettagli, funzioni che dipendono da stato globale, moduli difficili da testare.
  • Differenza tra codice brutto ma innocuo e fragilità che renderà costoso il prossimo cambiamento.
  1. Applicazione: svolgi il laboratorio: revisionare un file volutamente fragile e segnare problemi per categoria, senza refactor immediato. L’output da ottenere è: una lista priorizzata di fragilità con impatto e proposta di correzione incrementale.

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

  • riconosco le 6 categorie a colpo d’occhio;
  • distinguo brutto da fragile;
  • almeno una review recente ha identificato fragilità nel codice generato;
  • ho un’opinione su quando refattorare subito vs accettare debito.

Errori comuni

Refactor a vista. Vedi codice brutto, riscrivi tutto. Spesso introducono nuovi bug. Refactor mirato, non onnicomprensivo.

Ignorare fragilità “perché funziona”. Funziona oggi, fragile domani. Almeno annotala (TODO/FIXME con motivo) per quando torni.

Confondere stile con sostanza. Spaziatura, virgolette, commenti = stile. Magic number, accoppiamento = sostanza.

Magic number per ogni numero. Anche i + 1 non è magic. Distingui valori arbitrari da operazioni intrinseche.

Estrarre prematuramente. Se vedi una sola occorrenza, non astrarre. Aspetta la seconda. “Tre, allora estraggo” è regola pratica buona.

Non chiedere all’AI di rivedere il proprio output. Dopo generazione, prompt review è economico e cattura molte fragilità.

Prompt di lavoro

Rivedi questo codice e identifica fragilità strutturali (NON refactor, solo segnalazioni).

[INCOLLA CODICE]

Contesto: [tipo di file, dove viene usato]

Per ogni fragilità trovata, output:

1. Categoria: duplicazione / magic number / funzione gigante / nome generico / accoppiamento / effetto nascosto / altro.
2. Posizione: riga.
3. Severità: bloccante / consigliato / nit.
4. Motivo: cosa renderà costoso il prossimo cambiamento.
5. Suggerimento: tipo di intervento (estrarre, rinominare, separare), NON codice.

Distinzione esplicita: cosa è "brutto ma innocuo" vs "fragile" (cambiare costa molto).

Concludi con priorità: quali 2-3 intervenire subito, quali rimandare.

Esercizio finale

Apri 3 file recenti generati dall’AI nel tuo progetto. Per ognuno, conta fragilità delle 6 categorie. Trova quasi sempre 3-5 per file. Decidi quali 2 intervenire subito e quali rimandare con commento // TODO: estrarre quando aggiungiamo X. Quel marker ti permette di non fixare tutto ora ma di non dimenticare. Equilibrio fra fretta e debito.