---
title: "Codice leggibile quando una parte lo scrive l’AI"
source: "https://www.matteovincenti.com/blog/codice-leggibile-ai"
language: it
author: "Matteo Vincenti"
published: 2026-10-28
category: "Codice"
tags: ["Qualità software", "JavaScript", "Agenti AI"]
---

# Codice leggibile quando una parte lo scrive l’AI

Quando condividere una regola nel codice scritto con l’AI: notifiche, anteprima e invio, controesempi e controlli per una consegna verificabile.

![Un selettore centrale collegato a uno schermo con una campanella e a un contenitore con una busta.](https://cdn.sanity.io/images/xrqkwry7/production/62945e17c576d723aeb58928f837727ab9187952-1672x941.png?w=1400&fit=max&auto=format)

Categoria: Codice · Tag: Qualità software, JavaScript, Agenti AI

Chi torna su una funzione scritta con un agente AI deve poter capire quale regola cambiare anche senza la chat originale. Il requisito di oggi può essere soddisfatto; fra un mese arriverà un’eccezione, e qualcuno dovrà stabilire dove inserirla e quali comportamenti preservare.

Per codice leggibile intendo qui codice che permette di trovare una decisione, ricostruirne le precedenze e riconoscere gli effetti che produce. È un criterio utile quando il sorgente viene scritto a più mani, comprese quelle di un agente. Il numero di righe e la quantità di commenti, da soli, non dicono se quel lavoro sarà semplice.

La [review di una PR](https://www.matteovincenti.com/blog/code-review-agenti-ai-pr-pronta) riguarda l’accettazione di una modifica. Qui il punto è ciò che resta nel repository dopo il merge: la conoscenza necessaria a fare la modifica successiva.

## Una regola da ritrovare

Consideriamo un esempio costruito per questa analisi: il routing delle notifiche di un servizio. Un evento ha severità `critical`, `warning` oppure `info`. L’impostazione `enabled` abilita gli invii; `quietHours` indica le ore silenziose.

La policy scelta per il caso è precisa: disabilitare gli invii sopprime anche gli eventi critici. Quando gli invii sono abilitati, un evento critico raggiunge il reperibile tramite il canale `page`, anche nelle ore silenziose. Un warning produce un’email solo fuori da quelle ore. Gli eventi informativi non producono notifiche.

Questa funzione esprime già la regola in modo locale:

```javascript
function notify(event, settings, deliver) {
  if (!settings.enabled) return;

  if (event.severity === 'critical') {
    deliver('page', event);
    return;
  }

  if (event.severity === 'warning' && !settings.quietHours) {
    deliver('email', event);
  }
}
```

Gli input sono già validati dal chiamante: le impostazioni sono booleani e la severità appartiene ai tre valori indicati. `deliver` è la funzione di consegna fornita dall’applicazione; qui interessa quale canale riceve quale evento. Il frammento non implementa il recapito, i retry o la gestione delle notifiche duplicate.

L’ordine dei controlli conserva una decisione del prodotto. Spostare `quietHours` all’inizio sopprimerebbe anche gli allarmi critici. Spostare `critical` prima di `enabled` farebbe partire allarmi con gli invii disabilitati. Non sono due preferenze di formattazione.

Un commento può conservare il motivo della prima regola: per esempio, che `enabled` è un interruttore globale e deve prevalere su tutte le severità. Ripetere «se disabilitato, ritorna» non aggiunge quella conoscenza.

## Quando separare la decisione dall’invio

Si può estrarre la scelta del canale in una funzione pura: restituisce un valore a partire dagli argomenti, senza inviare messaggi né leggere altre impostazioni.

```javascript
function notificationChannel(severity, enabled, quietHours) {
  if (!enabled) return 'none';
  if (severity === 'critical') return 'page';
  if (severity === 'warning' && !quietHours) return 'email';
  return 'none';
}

function notify(event, settings, deliver) {
  const channel = notificationChannel(
    event.severity, settings.enabled, settings.quietHours
  );
  if (channel !== 'none') deliver(channel, event);
}
```

Adesso si può chiedere quale canale verrebbe scelto senza effettuare un invio. Questo confine serve se, per esempio, una schermata di configurazione deve mostrare un’anteprima della stessa policy. Usare la medesima funzione per anteprima e invio evita di riscrivere quella decisione in due posti.

Se esiste soltanto il piccolo handler iniziale, l’estrazione è una scelta da giustificare. La regola era già visibile e i test potevano registrare le chiamate a `deliver`. La seconda forma aggiunge un nome, una chiamata e il valore convenzionale `none`: chi legge deve seguirli. Non occorre creare una funzione per ogni condizione, né un motore configurabile per questa regola.

Le [pratiche di Google sulla code review](https://google.github.io/eng-practices/review/reviewer/looking-for.html) collegano la complessità alla difficoltà di comprendere e modificare il codice, e chiedono di evitare genericità per esigenze ancora ipotetiche. Il criterio si applica anche a un refactor proposto dall’agente: quale decisione rende più riconoscibile, e a quale costo?

## La modifica successiva

Arriva una nuova richiesta: nelle ore silenziose i warning devono entrare in un riepilogo differito, chiamato `digest`. Per isolare il problema di routing, assumiamo che l’applicazione sappia già raccogliere e spedire quel riepilogo.

Nella funzione estratta cambia questo ramo:

```diff
-  if (severity === 'warning' && !quietHours) return 'email';
+  if (severity === 'warning') return quietHours ? 'digest' : 'email';
```

Nella versione inline la modifica è altrettanto circoscritta:

```diff
-  if (event.severity === 'warning' && !settings.quietHours) {
-    deliver('email', event);
+  if (event.severity === 'warning') {
+    deliver(settings.quietHours ? 'digest' : 'email', event);
   }
```

Entrambe permettono di trovare il punto da cambiare. La funzione estratta diventa preferibile quando quella decisione deve vivere autonomamente rispetto all’invio, come nell’anteprima.

Le precedenze da conservare si possono leggere anche senza JavaScript:

_La modifica successiva_

| Invii | Severità | Ore silenziose | Prima | Dopo |
| --- | --- | --- | --- | --- |
| Disabilitati | Qualsiasi | Sì o no | Nessun invio | Nessun invio |
| Abilitati | Critical | Sì o no | Page | Page |
| Abilitati | Warning | No | Email | Email |
| Abilitati | Warning | Sì | Nessun invio | Digest |
| Abilitati | Info | Sì o no | Nessun invio | Nessun invio |

La tabella copre le dodici combinazioni del dominio dichiarato: tre severità per due impostazioni booleane. Nella verifica con Node 22.23.4, l’handler inline e quello con la funzione estratta hanno prodotto le stesse chiamate attese in tutti i casi. Applicando la nuova policy a entrambe le forme cambia un solo esito: il warning abilitato durante le ore silenziose. Due varianti con le precedenze invertite vengono respinte dai controlli.

La prova usa una funzione che registra le chiamate al posto della consegna reale. Verifica il routing e protegge il confronto fra le forme del codice; non misura il tempo necessario a una persona per capirle.

## Mettere alla prova il confine con un secondo utilizzo

L’anteprima rende concreta la ragione per estrarre la decisione. Dopo avere applicato il diff per il digest alla funzione `notificationChannel`, la schermata di configurazione può usarla così:

```javascript
function preview(event, proposedSettings) {
  return notificationChannel(
    event.severity,
    proposedSettings.enabled,
    proposedSettings.quietHours
  );
}
```

Questa funzione restituisce un canale; il codice dell’interfaccia decide come mostrarlo. Non riceve `deliver`, perché l’anteprima non deve inviare nulla. L’handler `notify` già mostrato continua invece a usare il risultato per effettuare la consegna.

Se la schermata contenesse una seconda copia della vecchia regola, un warning abilitato nelle ore silenziose mostrerebbe «nessun invio» mentre il servizio lo inserirebbe nel digest. Nell’estensione della prova, anteprima e invio sono stati controllati separatamente contro gli esiti attesi delle dodici combinazioni. La variante con anteprima rimasta alla vecchia policy fallisce proprio in quel caso.

Condividerei quindi il valutatore quando i chiamanti devono applicare la stessa decisione. Due funzioni simili non bastano: una simulazione di impostazioni proposte e un invio con impostazioni effettive possono avere risultati diversi. Nella prova, il warning con invii abilitati produce `digest`; con invii disabilitati produce `none`, anche usando il medesimo valutatore. Il chiamante deve rendere visibile quali impostazioni passa.

Questo confine introduce una dipendenza comune e un contratto: entrambi i chiamanti devono interpretare gli stessi valori. Quando cambia il significato di un canale, vanno controllati anche i consumatori. Se l’anteprima deve deliberatamente applicare una policy diversa, una sola funzione piena di flag per soddisfare entrambi può diventare più difficile da mantenere di due decisioni esplicite.

## Chiedere all’agente la modifica successiva

Prima di accettare un refactor, userei una richiesta concreta del prodotto per confrontare il codice corrente e quello proposto. Nel caso delle notifiche è il digest, con il vincolo che l’interruttore globale resti prioritario.

All’agente chiederei prima una mappa della modifica: dove cambia la decisione, quali chiamanti la usano e quali comportamenti devono restare identici. Poi chiederei il diff minimo per ciascuna forma. Così si vede se una nuova astrazione raccoglie davvero una regola oppure sposta soltanto il codice dietro un nome.

Senza anteprima, i due diff precedenti modificano un solo ramo: questo caso non giustifica l’estrazione. Con l’anteprima duplicata, invece, la nuova policy va aggiornata in due implementazioni; con il valutatore condiviso la regola cambia in un punto e si verificano entrambi gli usi. Il vantaggio riguarda la manutenzione di quella decisione.

Sceglierei una modifica già richiesta o plausibile nel contesto del prodotto. Chiedere all’agente di preparare il codice a ogni futuro possibile incoraggia proprio la genericità anticipata che rende difficile seguirlo. Se la richiesta futura è ancora ignota, manterrei locale il codice e rinvierei l’astrazione.

## Che cosa deve restare nel repository

Martin Fowler descrive la [qualità interna](https://martinfowler.com/articles/is-quality-worth-cost.html) attraverso il lavoro necessario a trovare e comprendere il codice da modificare. Nel caso delle notifiche, quella conoscenza ha una collocazione concreta: il nome della policy, l’ordine delle precedenze, la tabella dei comportamenti e il motivo per cui l’interruttore globale prevale anche sugli allarmi.

La chat con l’agente può aiutare a ricostruirla, ma non dovrebbe essere l’unico posto in cui cercarla. Il test conserva gli esiti attesi; un commento vicino alla regola o una breve decisione tecnica conserva il motivo, quando non è ricavabile dal codice. La spiegazione va aggiornata insieme al comportamento.

Non serve partire dall’idea che l’AI produca sempre codice peggiore. Nel proprio [studio sul lavoro con gli agenti](https://www.anthropic.com/research/employment-at-anthropic), Anthropic non rileva differenze nei tassi di righe successivamente rimosse o di commit annullati, confrontando codice umano e dell’agente per lo stesso dipendente, mese e directory. Sono indicatori di qualità del codice osservato in quell’azienda; non misurano direttamente la facilità con cui un altro sviluppatore ne comprende una regola.

Peter Mattis, nell’[intervista a The Pragmatic Engineer](https://newsletter.pragmaticengineer.com/p/distributed-databases-with-peter), prevede invece un futuro in cui leggeremo molto meno sorgente, come già accade con l’assembly. È un’obiezione pertinente alla centralità della lettura manuale. Per il team che deve ancora diagnosticare e modificare il sistema, però, una regola recuperabile rimane un requisito operativo.

Per questo caso, lascerei una consegna breve accanto al codice o nella descrizione della modifica, con riferimenti ai test effettivi del repository:

- **Decisione:** `notificationChannel` sceglie il canale; `notify` esegue la consegna e `preview` restituisce la scelta senza inviare.
- **Precedenza:** `enabled = false` sopprime tutte le severità. È la policy assunta in questo esempio; una ragione di prodotto diversa richiede una decisione esplicita.
- **Modifica:** il warning abilitato nelle ore silenziose passa da `none` a `digest`. Restano invariati gli altri undici casi.
- **Scelta progettuale:** condividere il valutatore evita di duplicare la stessa policy nell’anteprima. Con il solo handler, la versione inline rimane sufficiente.
- **Verifica:** controllare anteprima e consegna contro gli esiti attesi, includendo invii disabilitati, critical nelle ore silenziose e il nuovo digest. La consegna reale del riepilogo resta responsabilità dell’applicazione.

La nota conserva ciò che serve a orientare la modifica; la tabella completa degli esiti appartiene ai test. Eviterei di duplicarla in un documento separato che nessuno aggiorna. Se una frase ripete soltanto un’istruzione del codice, la eliminerei; se spiega un vincolo non ricavabile dal sorgente, la terrei vicino al punto che protegge.

Infine farei una lettura senza la chat originale: un collega deve poter indicare dove cambiare la policy, quale precedenza preservare e quale test controllare. Se deve chiedere all’agente che cosa intendesse, manca ancora un pezzo della consegna.
