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.
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
localStoragea 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
strumentieprestiti; - il formato JSON;
- il momento in cui salvare.
Conosce anche la struttura dei dati:
- lo strumento ha
idestato; - il prestito ha
strumentoId,studenteeaperto; - 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
localStoragea 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à
nomedello 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
localStoragedentro 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.