modulo 04 di 24 / Ore 0-20

Moduli 1-8 / Checkpoint 20 ore

Accoppiamento spiegato con UI e salvataggio dati

Capire l'accoppiamento osservando come UI, regole e localStorage diventano dipendenti tra loro nel Registro del laboratorio fragile.

25 minArchitettura del software

Accoppiamento spiegato con UI e salvataggio dati

Accoppiamento significa dipendenza. Due parti del programma sono accoppiate quando una parte conosce dettagli dell’altra e rischia di rompersi se quei dettagli cambiano.

Un po’ di accoppiamento è normale: un programma è fatto di parti che collaborano. Il problema nasce quando una parte conosce troppi dettagli, oppure dettagli che non dovrebbe conoscere. Nel Registro del laboratorio fragile questo succede spesso: la UI legge i campi HTML, modifica gli array, applica regole, salva in localStorage e decide il messaggio da mostrare.

Questa lezione serve a riconoscere quegli intrecci prima di correggerli. Non dobbiamo ancora riscrivere tutto. Dobbiamo imparare a vedere dove il codice è troppo legato.

Obiettivo della lezione

Alla fine della lezione devi saper:

  • spiegare che cosa significa accoppiamento con un esempio concreto;
  • individuare dipendenze tra UI, dati globali, regole e salvataggio;
  • distinguere un accoppiamento necessario da uno che rende fragile il progetto;
  • trasformare almeno una funzione molto accoppiata in una funzione con parametri più chiari;
  • preparare il terreno per il refactoring delle lezioni successive.

Il segnale principale: cambiare una cosa obbliga a cambiarne molte

Il modo più semplice per riconoscere l’accoppiamento è fare questa domanda:

se cambio questo dettaglio, quanti altri punti del programma devo toccare?

Nel Registro del laboratorio immaginiamo tre cambiamenti realistici:

  • passare da localStorage a un file JSON;
  • sostituire la pagina web con una piccola CLI;
  • aggiungere il nuovo stato in_manutenzione.

Se ognuno di questi cambiamenti costringe a modificare event listener, funzioni di ricerca, funzioni di render e logica dei prestiti, il codice è molto accoppiato.

L’accoppiamento non si vede sempre come errore immediato. Spesso il programma funziona, ma diventa difficile da modificare. È per questo che uno studente può dire: “funziona, però ho paura a toccarlo”.

Esempio fragile: un click che conosce tutto

Nel programma iniziale potremmo avere un codice simile dentro il click del bottone “Registra prestito”:

bottonePrestito.addEventListener("click", () => {
  const strumentoId = inputStrumentoId.value;
  const studente = inputStudente.value.trim();

  const strumento = strumenti.find((item) => item.id === strumentoId);

  if (!strumento) {
    messaggioElement.textContent = "Strumento non trovato";
    return;
  }

  if (strumento.stato === "guasto") {
    messaggioElement.textContent = "Uno strumento guasto non può essere prestato";
    return;
  }

  if (strumento.stato === "in_prestito") {
    messaggioElement.textContent = "Strumento già in prestito";
    return;
  }

  strumento.stato = "in_prestito";

  prestiti.push({
    id: crypto.randomUUID(),
    strumentoId: strumento.id,
    studente,
    aperto: true,
  });

  localStorage.setItem("strumenti", JSON.stringify(strumenti));
  localStorage.setItem("prestiti", JSON.stringify(prestiti));

  messaggioElement.textContent = "Prestito registrato";
  mostraStrumenti(strumenti);
});

Il codice è comprensibile a una prima lettura: quando clicco, registra un prestito. Il problema è che lo stesso blocco conosce troppe cose.

Conosce la UI:

  • bottonePrestito;
  • inputStrumentoId;
  • inputStudente;
  • messaggioElement;
  • mostraStrumenti.

Conosce il dominio:

  • uno strumento guasto non può essere prestato;
  • uno strumento già in prestito non può essere prestato di nuovo;
  • lo stato deve diventare in_prestito.

Conosce la persistenza:

  • le chiavi strumenti e prestiti;
  • il formato JSON;
  • il momento in cui salvare.

Conosce anche la struttura dei dati:

  • lo strumento ha id e stato;
  • il prestito ha strumentoId, studente e aperto;
  • gli strumenti e i prestiti sono array globali.

Il click è diventato il centro del sistema. Questo è un accoppiamento forte: per cambiare una regola, un formato dati o un meccanismo di salvataggio dobbiamo toccare la UI.

Accoppiamento UI e salvataggio dati

Una dipendenza molto comune nei primi progetti è questa:

localStorage.setItem("strumenti", JSON.stringify(strumenti));

Questa riga non è sbagliata in sé. È sbagliato, o almeno rischioso, trovarla sparsa in tanti event listener.

Se il salvataggio è dentro la UI, la UI deve sapere:

  • dove vengono salvati i dati;
  • con quali chiavi;
  • in quale formato;
  • quando fare il salvataggio;
  • come gestire un errore di salvataggio.

Domanda pratica:

se domani passiamo da localStorage a una API, quanti file della UI dobbiamo modificare?

In una prima versione fragile la risposta può essere: “quasi tutti”. Nel percorso cercheremo di arrivare a una risposta migliore: “solo l’infrastruttura”.

Accoppiamento UI e regole di dominio

Un’altra dipendenza delicata riguarda le regole.

Questa regola appartiene al dominio del problema:

Uno strumento guasto non può essere prestato.

Se la scriviamo dentro un event listener, la regola funziona solo in quel punto:

if (strumento.stato === "guasto") {
  messaggioElement.textContent = "Uno strumento guasto non può essere prestato";
  return;
}

Il problema appare quando la stessa operazione viene eseguita da un’altra parte del programma:

  • un pulsante diverso;
  • una schermata di amministrazione;
  • una importazione da file;
  • un test automatico;
  • una futura API.

Se la regola vive nella UI, ogni nuovo ingresso deve ricordarsi di copiarla. Prima o poi qualcuno la dimentica.

Una regola importante deve essere più vicina al dominio, non al bottone. La UI può raccogliere i dati e mostrare un messaggio, ma non dovrebbe essere l’unico posto in cui il sistema sa che cosa è permesso.

Accoppiamento con dati globali

Anche le variabili globali aumentano l’accoppiamento. Osserva questa funzione:

function aggiornaRisultati() {
  const testo = inputRicerca.value.toLowerCase();

  const risultati = strumenti.filter((strumento) => {
    return strumento.nome.toLowerCase().includes(testo);
  });

  mostraStrumenti(risultati);
}

Sembra una funzione semplice, ma dipende da molti dettagli esterni:

  • inputRicerca, cioè un elemento HTML;
  • strumenti, cioè un array globale;
  • mostraStrumenti, cioè una funzione di render;
  • la proprietà nome dello strumento;
  • il fatto che la ricerca sia case-insensitive;
  • la presenza del browser.

Queste dipendenze non compaiono nella firma della funzione. La firma è:

function aggiornaRisultati()

Da fuori sembra che non serva nulla. In realtà serve mezzo programma.

Una funzione meno accoppiata dichiara ciò di cui ha bisogno:

function cercaStrumenti(strumenti, testo) {
  const query = testo.trim().toLowerCase();

  return strumenti.filter((strumento) => {
    return (
      strumento.nome.toLowerCase().includes(query) ||
      strumento.categoria.toLowerCase().includes(query) ||
      strumento.stato.toLowerCase().includes(query)
    );
  });
}

Questa funzione non conosce:

  • input HTML;
  • bottoni;
  • messaggi;
  • localStorage;
  • render della pagina.

La UI può usarla così:

const testo = inputRicerca.value;
const risultati = cercaStrumenti(strumenti, testo);

mostraStrumenti(risultati);

La collaborazione esiste ancora, ma è più chiara. La UI legge l’input e mostra il risultato. La funzione di ricerca applica il criterio di ricerca. I due compiti non sono più fusi insieme.

Accoppiamento con stringhe ripetute

Nel Registro del laboratorio gli stati sono parole importanti:

  • disponibile;
  • in_prestito;
  • guasto;
  • in_manutenzione.

Se le stringhe sono ripetute in molti file, nasce un accoppiamento implicito:

if (strumento.stato === "guasto") {
  return "Lo strumento non può essere prestato";
}
const strumentiGuasti = strumenti.filter((strumento) => {
  return strumento.stato === "guasto";
});
if (filtroStato.value === "guasto") {
  // ...
}

Finché la parola resta identica, tutto funziona. Ma se qualcuno scrive rotto, guasti, non_funzionante o cambia il formato dello stato, il programma diventa incoerente.

Un primo miglioramento semplice è raccogliere gli stati in un punto esplicito:

export const StatoStrumento = {
  DISPONIBILE: "disponibile",
  IN_PRESTITO: "in_prestito",
  GUASTO: "guasto",
  IN_MANUTENZIONE: "in_manutenzione",
};

Poi il codice può usare il nome del concetto:

if (strumento.stato === StatoStrumento.GUASTO) {
  return "Lo strumento non può essere prestato";
}

Non è un pattern complicato. È solo un modo per rendere visibile una convenzione che prima era sparsa.

Accoppiamento accettabile e accoppiamento pericoloso

Non dobbiamo eliminare ogni relazione tra le parti. Sarebbe impossibile e renderebbe il codice artificiale.

È accettabile che:

  • la UI conosca il caso d’uso da chiamare;
  • il caso d’uso riceva un archivio attraverso un’interfaccia;
  • l’infrastruttura conosca localStorage;
  • il dominio conosca i propri stati;
  • un test conosca il comportamento che vuole verificare.

È pericoloso che:

  • il dominio legga direttamente dal DOM;
  • il dominio usi localStorage;
  • la UI sia l’unico posto in cui esistono le regole;
  • il caso d’uso costruisca HTML;
  • una funzione di ricerca legga da un input del browser;
  • una modifica al formato di salvataggio richieda di cambiare tutti i bottoni.

La domanda corretta non è:

C'è accoppiamento?

La domanda corretta è:

Questo accoppiamento rende difficile cambiare, testare o capire il programma?

Mappa delle dipendenze nel Registro fragile

Per studiare il codice non basta leggerlo dall’alto verso il basso. Serve una piccola mappa.

Puoi usare questo schema nel quaderno o nel README:

## Dipendenze individuate

### Event listener: registra prestito

- Legge da: inputStrumentoId, inputStudente
- Modifica: strumenti, prestiti
- Applica regole: stato guasto, stato in prestito
- Salva su: localStorage
- Mostra: messaggioElement, lista strumenti
- Rischio: la UI contiene regole e persistenza

### Funzione: aggiornaRisultati

- Legge da: inputRicerca
- Usa: strumenti globale
- Mostra: mostraStrumenti
- Rischio: la ricerca non è testabile senza DOM

Questa mappa non è documentazione decorativa. Serve a decidere dove intervenire. Prima di spostare codice, dobbiamo sapere quali dipendenze stiamo sciogliendo.

Esercizio guidato: rendere esplicite le dipendenze

Parti da una funzione del programma fragile che legge direttamente dal DOM:

function filtraPerStato() {
  const stato = selectStato.value;

  const risultati = strumenti.filter((strumento) => {
    return strumento.stato === stato;
  });

  mostraStrumenti(risultati);
}

Prima di modificarla, scrivi le dipendenze:

Dipendenze:
- selectStato
- strumenti globale
- struttura di strumento.stato
- mostraStrumenti

Poi separa il calcolo dal render:

function filtraStrumentiPerStato(strumenti, stato) {
  return strumenti.filter((strumento) => {
    return strumento.stato === stato;
  });
}

La UI rimane responsabile di leggere e mostrare:

function aggiornaFiltroStato() {
  const stato = selectStato.value;
  const risultati = filtraStrumentiPerStato(strumenti, stato);

  mostraStrumenti(risultati);
}

Il comportamento visibile non è cambiato. Però ora la regola di filtro può essere provata anche senza pagina HTML:

const strumenti = [
  { nome: "Microscopio", stato: "disponibile" },
  { nome: "Tablet", stato: "guasto" },
];

const risultati = filtraStrumentiPerStato(strumenti, "guasto");

console.log(risultati.length); // 1

Questo è il tipo di miglioramento che vogliamo: piccolo, comprensibile, verificabile.

Attività in laboratorio

Scegli una funzione del tuo Registro fragile e compila questa scheda:

## Scheda accoppiamento

### Funzione analizzata

Nome:

### Che cosa fa

Descrizione breve:

### Dipendenze esplicite

Parametri:

### Dipendenze nascoste

Variabili globali:
Elementi HTML:
localStorage:
Funzioni chiamate:
Stringhe condivise:

### Rischio

Che cosa diventa difficile cambiare?

### Primo miglioramento possibile

Quale parametro, funzione o confine renderebbe il codice meno accoppiato?

Lavora su un solo punto. Se provi a sistemare tutto in una volta, il rischio è perdere il comportamento funzionante. In questa fase stiamo allenando lo sguardo, non cercando la soluzione perfetta.

Micro-sfida

Trova una funzione che legge direttamente un input HTML e usa almeno una variabile globale.

Obiettivo:

  • ridurre l’accoppiamento rendendo espliciti i dati necessari.

Consegna:

  • scrivi la firma di una nuova funzione con parametri chiari;
  • indica quali dipendenze hai rimosso;
  • non riscrivere ancora tutto il flusso.

Esempio di consegna:

function cercaStrumenti(strumenti, testo, stato) {
  // qui non leggo input HTML e non uso localStorage
}

Vincolo:

  • la funzione nuova non deve leggere dal DOM;
  • la funzione nuova non deve salvare dati;
  • la funzione nuova deve restituire un risultato, non mostrarlo.

Risultato atteso:

  • una funzione candidata più facile da testare;
  • una breve nota che spiega quale accoppiamento è stato ridotto.

Prodotto da ottenere

Alla fine della lezione devi avere:

  • una mappa di almeno tre accoppiamenti presenti nel Registro fragile;
  • una scheda completa su una funzione molto accoppiata;
  • una proposta di funzione con parametri espliciti;
  • una nota su quale cambiamento futuro diventerebbe più semplice.

Esempio di nota:

Ho separato il calcolo della ricerca dalla lettura dell'input HTML.
Questo rende più semplice testare la ricerca e cambiare la UI in futuro.

Domande di comprensione

Rispondi per iscritto:

  • Perché una funzione senza parametri può essere comunque molto dipendente dal resto del programma?
  • Qual è il rischio di usare localStorage dentro molti event listener?
  • Perché una regola di dominio scritta nella UI è fragile?
  • Che differenza c’è tra collaborazione necessaria e accoppiamento pericoloso?
  • Quale dipendenza nascosta hai trovato nel tuo codice?

Le risposte non devono essere lunghe. Devono però citare pezzi concreti del Registro: strumenti, prestiti, stati, UI, localStorage, ricerca, messaggi.

Controllo finale

La lezione è completata quando puoi dire sì a queste affermazioni:

  • so spiegare l’accoppiamento con parole mie;
  • ho individuato almeno un accoppiamento tra UI e salvataggio;
  • ho individuato almeno un accoppiamento tra UI e regole;
  • so perché le dipendenze globali rendono i test più difficili;
  • ho scritto una possibile funzione con parametri più chiari;
  • ho capito che ridurre accoppiamento non significa eliminare ogni collaborazione.

Nel prossimo passo useremo queste osservazioni per lavorare sulle funzioni troppo lunghe, senza riscrivere il progetto da zero.