AI per lo sviluppo software

L'AI ti ha promosso a reviewer. Hai mai testato i tuoi controlli?

Un ingegnere ha contato 204 controlli automatici nei suoi repository — test, guardie CI, script di validazione — e ha scoperto che solo il 11% sa dimostrare di essere in grado di fallire quando dovrebbe. Il resto è verde, ma nessuno sa perché.

Il tuo vero codice, oggi, sono i controlli

Quando gran parte del codice applicativo lo scrive un assistente AI, il lavoro che resta a chi programma si sposta: prima si passava la maggior parte del tempo a produrre codice e una piccola parte a verificarlo, oggi il rapporto si inverte. Il codice che scrivi davvero tu, quello su cui si basa il tuo giudizio professionale, diventa l'insieme dei controlli che decidono se accettare o rifiutare quello che l'assistente propone: test, check di CI/CD, script che validano una configurazione, guardie che bloccano un deploy.

Il problema è che questi controlli vengono quasi sempre trattati con uno standard più basso di quello che applicheremmo al codice di produzione. Un test applicativo che nessuno ha mai visto fallire con un input scorretto non lo definiremmo "collaudato" — eppure è esattamente lo stato in cui vive la maggior parte dei controlli che oggi fanno da filtro tra un assistente AI e il codice che finisce in produzione.

Tre controlli verdi, tre modi diversi di essere inutili

Il punto non è teorico. Tre episodi reali, capitati nella stessa settimana, mostrano lo stesso schema sotto forma diversa:

  • Il controllo di deploy che è morto della sua stessa medicina. Uno step di pipeline doveva intercettare un fallimento silenzioso — uno strumento mancante che faceva ripiegare il sistema su un risultato vuoto invece di segnalare errore. Chiamava node -e per leggere una risposta di health check. Il runner di deploy non aveva Node installato. Sei deploy consecutivi sono falliti con "comando non trovato", e per sei ore non è partito nulla: il controllo pensato per intercettare uno strumento mancante è fallito proprio per uno strumento mancante, e nessuno se n'è accorto perché non era mai stato eseguito nell'ambiente reale in cui gira.
  • Il job che buttava via il proprio lavoro. Un processo automatico raccoglieva dati e giudicava ogni esecuzione solo dal codice di uscita. Un'esecuzione ha scritto sette record perfettamente validi, poi ha incontrato un avviso non bloccante e si è chiusa con codice diverso da zero. Il sistema ha archiviato il proprio lavoro come "fallito, da ripetere" — perché nella sua logica esistevano solo successo e fallimento, non "interrotto con risultati parziali validi".
  • Il pattern che intercettava il numero sbagliato. Un classificatore di errori cercava pattern come 50[024] in output di log per riconoscere errori server. Ha trovato "500" dentro la stringa "4258 di 5000 punti quota rimanenti" e ha classificato come fallita un'esecuzione perfettamente riuscita: ogni dato letto era vero, ma il controllo rispondeva a una domanda diversa da quella che credeva di porsi.

Il test negativo: dare in pasto un input che deve far fallire

La differenza tra un controllo verde per caso e un controllo verde perché funziona davvero sta in una sola domanda: l'hai mai visto fallire nel modo giusto? Un controllo che afferma qualcosa sullo stato del sistema ("nessun workflow scarica la cache dalla rete", "questa feature flag corrisponde alla configurazione distribuita") va sottoposto almeno una volta a un caso costruito apposta per essere sbagliato, verificando che venga rifiutato per il motivo corretto — non semplicemente che il risultato sia diverso.

// Prima: un check "verde" di cui non sai nulla
function checkNoNetworkCache(workflowFile) {
  return !workflowFile.includes("actions/cache@");
}
// Non sai se rileva davvero il caso vietato, o se
// non lo troverà mai perché la stringa cercata è sbagliata.

// Dopo: lo stesso check, con un caso noto-sbagliato accanto
test("rileva un workflow che scarica la cache dalla rete", () => {
  const bad = "steps:\n  - uses: actions/cache@v4\n";
  expect(checkNoNetworkCache(bad)).toBe(false); // deve fallire qui
});

test("accetta un workflow senza cache di rete", () => {
  const good = "steps:\n  - run: npm ci\n";
  expect(checkNoNetworkCache(good)).toBe(true);
});

Non serve applicare questa disciplina a ogni singolo controllo che possiedi: ha senso concentrarsi su quelli che decidono qualcosa di costoso da sbagliare — un gate di deploy, una validazione di sicurezza, un classificatore che decide se ripetere un job costoso. Per quei controlli, un caso di test negativo — un input costruito apposta per essere quello sbagliato — costa poco da scrivere e ti dice, una volta per tutte, se il tuo revisore automatico sa davvero riconoscere il problema che dovrebbe intercettare.

Fonte: AI promoted every developer to reviewer. Nobody tested the reviewer, su dev.to