modulo 04 di 24 / Ore 0-20
Moduli 1-8 / Checkpoint 20 ore
Responsabilità: una funzione, un motivo per cambiare
Capire il concetto di responsabilità osservando funzioni del Registro del laboratorio che cambiano per motivi diversi e diventano difficili da mantenere.
Responsabilità: una funzione, un motivo per cambiare
Nel modulo precedente hai imparato a vedere i confini.
Ora iniziamo a lavorare su un concetto più operativo: la responsabilità.
Una responsabilità è un compito preciso assegnato a una parte del codice.
Una funzione con una responsabilità chiara è più facile da leggere, testare e modificare.
Una funzione con troppe responsabilità diventa fragile: ogni cambiamento rischia di rompere qualcosa che sembrava non c’entrare.
Obiettivo della lezione
Alla fine devi saper:
- spiegare cosa significa responsabilità nel codice;
- riconoscere quando una funzione ha troppi motivi per cambiare;
- distinguere una funzione utile da una funzione contenitore;
- compilare una diagnosi di responsabilità su un blocco del Registro;
- scegliere una responsabilità candidata alla separazione.
In questa lezione non devi ancora fare un refactoring importante. Devi imparare a diagnosticare.
La regola pratica
Useremo questa frase:
una funzione dovrebbe avere un motivo principale per cambiare.
Non significa che una funzione debba avere sempre una sola riga.
Non significa creare decine di funzioni minuscole senza senso.
Significa che, quando leggi una funzione, dovresti riuscire a dire:
Questa funzione serve principalmente a fare questa cosa.
Se invece devi dire:
Questa funzione legge input, controlla regole, modifica dati, salva in localStorage,
mostra messaggi e ridisegna la pagina.
allora la funzione contiene troppe responsabilità.
Esempio fragile
Nel Registro fragile potresti avere una funzione come questa:
function registraPrestitoFragile() {
const id = inputStrumentoId.value;
const studente = inputStudente.value;
const strumento = strumenti.find((item) => item.id === id);
if (!strumento) {
messaggioElement.textContent = "Strumento non trovato";
return;
}
if (!studente) {
messaggioElement.textContent = "Inserisci il nome dello studente";
return;
}
if (strumento.stato === "guasto") {
messaggioElement.textContent = "Uno strumento guasto non può essere prestato";
return;
}
if (strumento.stato === "in_prestito") {
messaggioElement.textContent = "Lo strumento è già in prestito";
return;
}
strumento.stato = "in_prestito";
prestiti.push({
id: `pre-${Date.now()}`,
strumentoId: strumento.id,
studente,
aperto: true,
});
localStorage.setItem("strumenti", JSON.stringify(strumenti));
localStorage.setItem("prestiti", JSON.stringify(prestiti));
messaggioElement.textContent = "Prestito registrato";
mostraStrumenti(strumenti);
}
Questa funzione non è lunga per caso.
È lunga perché contiene molti motivi per cambiare.
Trova i motivi per cambiare
Una buona diagnosi parte da questa domanda:
Per quali motivi dovrei modificare questa funzione?
Nel nostro esempio:
| Motivo per cambiare | Riga o area coinvolta | Tipo di responsabilità |
|---|---|---|
| cambia il nome del campo HTML | inputStrumentoId.value | UI |
| cambia la validazione del nome studente | if (!studente) | input / application |
| cambia la regola sugli strumenti guasti | strumento.stato === "guasto" | domain |
| cambia la regola sugli strumenti già in prestito | strumento.stato === "in_prestito" | domain |
| cambia il formato del prestito | oggetto dentro prestiti.push | application / domain |
| cambia il salvataggio | localStorage.setItem | infrastructure |
| cambia il messaggio utente | messaggioElement.textContent | UI |
| cambia il modo di renderizzare la lista | mostraStrumenti | UI |
Sono troppi motivi per una sola funzione.
Questa tabella è più utile di un commento generico tipo “la funzione è confusa”.
Ti dice perché è confusa.
Responsabilità visibili
Possiamo riassumere così:
registraPrestitoFragile
- legge input dalla UI
- valida input
- cerca uno strumento
- applica regole di dominio
- modifica stato
- crea un prestito
- salva strumenti
- salva prestiti
- mostra messaggio
- aggiorna lista
Questa lista non è ancora una soluzione.
È una diagnosi.
Prima di separare il codice, devi sapere cosa stai separando.
Una funzione con responsabilità chiara
Confronta con questa:
function puoEsserePrestato(strumento) {
return strumento.stato === "disponibile";
}
Il motivo per cambiare è chiaro:
cambia se cambia la regola che stabilisce quando uno strumento può essere prestato.
Non dovrebbe cambiare se:
- cambia il testo del bottone;
- cambia il layout della lista;
- cambiano le chiavi di
localStorage; - cambia il modo di mostrare gli errori;
- cambia il nome dell’input HTML.
Questo è un buon segnale.
La funzione appartiene al dominio perché parla di una regola del laboratorio, non della pagina o del salvataggio.
Una funzione troppo piccola può essere inutile
Non tutte le estrazioni sono utili.
Questa funzione è chiara, ma forse non serve:
function strumentoEsiste(strumento) {
return Boolean(strumento);
}
Se viene usata una sola volta e non rende il codice più leggibile, può essere eccessiva.
L’obiettivo non è creare funzioni per ogni riga.
L’obiettivo è dare un posto chiaro alle responsabilità importanti.
Le funzioni contenitore
Attenzione alle funzioni che hanno un nome generico e nascondono il problema:
function gestisciPrestito() {
const id = inputStrumentoId.value;
const studente = inputStudente.value;
const strumento = strumenti.find((item) => item.id === id);
// poi continua a fare tutto...
}
Il nome sembra migliore di clickHandler, ma la responsabilità non è davvero separata.
Una funzione utile ha un nome che dice cosa fa:
leggiInputPrestito;trovaStrumentoPerId;puoEsserePrestato;creaPrestito;salvaRegistro;mostraEsitoPrestito.
Non tutti questi nomi devono esistere subito.
Servono come strumenti per pensare.
Prima separazione mentale
Prima ancora di spostare codice, prova a riscrivere il flusso come elenco:
1. leggi input del prestito
2. trova lo strumento
3. valida lo studente
4. verifica se lo strumento può essere prestato
5. crea il prestito
6. salva le modifiche
7. mostra l'esito
Ogni riga può diventare una responsabilità.
Non sempre diventerà una funzione separata, ma deve essere riconoscibile.
Attività guidata
Scegli una funzione o un event listener del tuo Registro fragile.
Compila questa scheda:
## Analisi responsabilità
Nome blocco:
Che cosa fa oggi:
Motivi per cambiare:
- ...
- ...
- ...
Responsabilità presenti:
- ...
- ...
- ...
Responsabilità che separerei per prima:
Motivo:
Comportamento da non rompere:
Scrivi almeno sei motivi per cambiare.
Usa righe concrete del codice, non descrizioni vaghe.
Micro-sfida
Trova una funzione che sembra innocua ma contiene almeno tre responsabilità.
Vincoli:
- non modificarla;
- non rinominarla;
- scrivi solo la diagnosi;
- cita almeno tre righe o blocchi concreti;
- indica quale responsabilità separeresti per prima.
Risultato atteso: una scheda che spiega perché quella funzione ha più di un motivo per cambiare.
Prodotto da ottenere
Alla fine della lezione devi avere:
- una funzione o un event listener analizzato;
- almeno sei motivi per cambiare;
- una lista delle responsabilità presenti;
- una responsabilità prioritaria da separare;
- un comportamento da proteggere prima del refactoring.
Esempio:
Comportamento da proteggere:
se provo a prestare uno strumento già in prestito, il sistema non deve creare
un secondo prestito aperto.
Domande di comprensione
Rispondi per iscritto:
- Che cosa significa “motivo per cambiare”?
- Perché una funzione lunga non è sempre sbagliata?
- Quando una funzione breve può essere inutile?
- Quale responsabilità è più pericoloso lasciare dentro la UI?
- Quale comportamento del tuo Registro devi proteggere prima di separare codice?
Controllo finale
La lezione è completata quando puoi dire sì a queste domande:
- so spiegare che cosa significa responsabilità;
- so riconoscere più motivi per cambiare in una funzione;
- ho analizzato almeno un blocco del Registro fragile;
- so distinguere una funzione con nome utile da una funzione contenitore;
- ho capito che prima si diagnostica e poi si refactora;
- so indicare almeno una responsabilità da separare nei prossimi passi.
Nel prossimo passo parleremo di coesione: non basta avere funzioni separate, bisogna che le parti di una funzione o di un file lavorino per lo stesso scopo.