modulo 04 di 24 / Ore 0-20
Moduli 1-8 / Checkpoint 20 ore
Separare lettura input, decisione, salvataggio e messaggio
Riorganizzare il flusso di un'azione del Registro distinguendo input della UI, decisione applicativa, persistenza e messaggio all'utente.
Separare lettura input, decisione, salvataggio e messaggio
Nel Registro fragile molte azioni seguono sempre lo stesso schema:
- leggere input;
- decidere che cosa fare;
- modificare dati;
- salvare;
- mostrare un messaggio.
Quando questi passaggi sono mescolati nello stesso blocco, il codice diventa difficile da leggere e da cambiare. Quando invece li separi, anche restando nello stesso file, il flusso diventa più chiaro.
Questa lezione è un ponte: non siamo ancora alla struttura completa con presentation, application, domain e infrastructure, ma iniziamo a vedere quei confini dentro una singola azione.
Obiettivo della lezione
Alla fine della lezione devi saper:
- dividere un’azione del Registro in passaggi riconoscibili;
- capire quali passaggi appartengono alla UI e quali no;
- evitare che la UI decida regole importanti;
- evitare che il salvataggio sia sparso ovunque;
- trasformare una funzione principale in un flusso leggibile;
- spiegare quali limiti restano dopo questa prima separazione.
Il flusso da rendere visibile
Per RegistraPrestito possiamo scrivere:
UI:
leggi id strumento e nome studente
Decisione:
verifica che lo strumento esista
verifica che lo studente sia presente
verifica che lo strumento sia prestabile
Modifica:
cambia stato
crea prestito
Salvataggio:
salva strumenti e prestiti
Messaggio:
comunica successo o errore
Questo non è ancora un caso d’uso completo, ma è già un flusso più leggibile. La differenza è importante: un caso d’uso vero dovrà vivere fuori dalla UI; qui stiamo preparando il terreno.
Il punto non è creare tante funzioni per fare bella figura. Il punto è dare un nome a ogni responsabilità, così quando il codice cresce possiamo spostare i pezzi nel livello giusto.
Prima: una funzione che fa tutto
Partiamo da una versione concentrata in un solo blocco:
function registraPrestito() {
const strumentoId = inputStrumentoId.value.trim();
const studente = inputStudente.value.trim();
const strumento = strumenti.find((item) => item.id === strumentoId);
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));
mostraStrumenti(strumenti);
messaggioElement.textContent = "Prestito registrato";
}
Il comportamento è chiaro solo finché la funzione resta piccola. Appena aggiungiamo altre regole, altri stati, un salvataggio diverso o messaggi più curati, il blocco diventa una stanza piena di cavi.
Lo scomponiamo in quattro domande:
1. Da dove arrivano i dati?
2. Quale decisione deve prendere il sistema?
3. Quali dati cambiano?
4. Che cosa deve vedere l'utente?
Separare la lettura input
La UI legge input:
function leggiInputPrestito() {
return {
strumentoId: inputStrumentoId.value.trim(),
studente: inputStudente.value.trim(),
};
}
Questa funzione può dipendere dal DOM, perché il suo lavoro è proprio leggere la UI.
Il confine è chiaro:
- prende valori da elementi HTML;
- pulisce gli spazi;
- restituisce un oggetto semplice.
Non deve:
- cercare lo strumento;
- decidere se lo strumento è prestabile;
- salvare dati;
- mostrare messaggi.
Una buona lettura input produce un piccolo comando:
{
strumentoId: "s-12",
studente: "Giulia Rossi"
}
Questo oggetto è più maneggevole dei campi HTML. Può essere passato ad altre funzioni e, più avanti, a un caso d’uso.
Separare la decisione
Creiamo una funzione che decide se l’operazione puo procedere:
function validaRichiestaPrestito({ strumento, studente }) {
if (!strumento) {
return {
ok: false,
codice: "strumento_non_trovato",
messaggio: "Strumento non trovato",
};
}
if (!studente) {
return {
ok: false,
codice: "studente_mancante",
messaggio: "Inserisci il nome dello studente",
};
}
if (strumento.stato === "guasto") {
return {
ok: false,
codice: "strumento_guasto",
messaggio: "Uno strumento guasto non può essere prestato",
};
}
if (strumento.stato === "in_prestito") {
return {
ok: false,
codice: "strumento_gia_in_prestito",
messaggio: "Lo strumento è già in prestito",
};
}
return { ok: true };
}
Questa funzione è ancora didatticamente intermedia: restituisce anche messaggi utente. Più avanti separeremo errore e messaggio in modo più netto.
Per ora abbiamo già ottenuto un miglioramento: la decisione non è più nascosta in mezzo a lettura input, salvataggio e render.
La funzione risponde a una domanda precisa:
questa richiesta di prestito può procedere?
Se la risposta è no, restituisce un motivo.
Separare ricerca e decisione
Attenzione: cercare lo strumento e decidere se è prestabile non sono la stessa cosa.
La ricerca recupera un dato:
function trovaStrumentoPerId(strumenti, id) {
return strumenti.find((strumento) => strumento.id === id);
}
La decisione interpreta quel dato:
const validazione = validaRichiestaPrestito({
strumento,
studente: input.studente,
});
Questa separazione sembra piccola, ma aiuta a leggere il flusso:
- prima recupero ciò che mi serve;
- poi valuto se posso procedere.
Se un giorno lo strumento verrà letto da un database, la ricerca cambierà. La regola “uno strumento guasto non può essere prestato” non dovrebbe cambiare.
Separare la modifica dati
La modifica puo essere isolata:
function applicaPrestito(strumento, studente) {
strumento.stato = "in_prestito";
const prestito = {
id: `pre-${Date.now()}`,
strumentoId: strumento.id,
studente,
aperto: true,
};
prestiti.push(prestito);
return {
strumento,
prestito,
};
}
Anche questa funzione non è definitiva: usa prestiti globale. Ma isola il momento in cui cambiano i dati.
Possiamo renderla più esplicita passando anche la lista dei prestiti:
function applicaPrestito(strumento, prestiti, studente) {
strumento.stato = "in_prestito";
const prestito = {
id: `pre-${Date.now()}`,
strumentoId: strumento.id,
studente,
aperto: true,
};
prestiti.push(prestito);
return prestito;
}
Non è ancora perfetta, perché modifica oggetti e array esistenti. Però adesso è più chiaro quale passaggio cambia lo stato del sistema.
Regola pratica:
una funzione che modifica dati deve avere un nome che lo dichiara.
applicaPrestito è più chiaro di gestisci, submit, ok o fai.
Separare salvataggio
function salvaRegistro() {
localStorage.setItem("strumenti", JSON.stringify(strumenti));
localStorage.setItem("prestiti", JSON.stringify(prestiti));
}
La funzione è ancora accoppiata a localStorage, ma almeno il salvataggio ha un nome.
Meglio ancora:
function salvaRegistro(strumentiDaSalvare, prestitiDaSalvare) {
localStorage.setItem("strumenti", JSON.stringify(strumentiDaSalvare));
localStorage.setItem("prestiti", JSON.stringify(prestitiDaSalvare));
}
Questa funzione appartiene concettualmente all’infrastruttura, anche se per ora resta nello stesso file. Il suo lavoro non è decidere regole, ma trasformare dati in JSON e salvarli.
Più avanti potremo sostituirla con:
salvaRegistroSuJson(strumenti, prestiti);
oppure:
await salvaRegistroSuApi(strumenti, prestiti);
Se il resto del flusso non dipende dai dettagli di localStorage, il cambiamento sarà molto meno doloroso.
Separare messaggio
function mostraMessaggio(testo) {
messaggioElement.textContent = testo;
}
Questa funzione sembra piccola, ma rende esplicito che comunicare con l’utente è una responsabilità della UI.
La UI può mostrare:
- messaggi di successo;
- errori comprensibili;
- indicazioni su cosa fare dopo;
- stati vuoti, come “Nessuno strumento trovato”.
La UI non dovrebbe essere l’unico luogo in cui esiste una regola. Può tradurre un errore in messaggio, ma non dovrebbe decidere da sola se uno strumento guasto può essere prestato.
Un passaggio intermedio può essere una piccola funzione di traduzione:
function messaggioPerErrorePrestito(codice) {
if (codice === "strumento_non_trovato") {
return "Strumento non trovato";
}
if (codice === "studente_mancante") {
return "Inserisci il nome dello studente";
}
if (codice === "strumento_guasto") {
return "Uno strumento guasto non può essere prestato";
}
if (codice === "strumento_gia_in_prestito") {
return "Lo strumento è già in prestito";
}
return "Operazione non riuscita";
}
Questa versione è un po’ più lunga, ma prepara una separazione utile: il sistema può restituire un codice, la UI sceglie il testo da mostrare.
Il flusso completo
Ora registraPrestito puo diventare:
function registraPrestito() {
const input = leggiInputPrestito();
const strumento = trovaStrumentoPerId(strumenti, input.strumentoId);
const validazione = validaRichiestaPrestito({
strumento,
studente: input.studente,
});
if (!validazione.ok) {
mostraMessaggio(validazione.messaggio);
return;
}
applicaPrestito(strumento, input.studente);
salvaRegistro(strumenti, prestiti);
mostraStrumenti(strumenti);
mostraMessaggio("Prestito registrato");
}
Questa funzione è ancora nello stesso file, ma ora racconta una storia:
leggi input
trova dati
valida
applica
salva
mostra
La funzione principale non contiene tutti i dettagli. Coordina i passaggi. Questo è un primo assaggio di ciò che farà il livello Application: non disegna la pagina, non salva direttamente, non contiene tutti i dettagli del dominio, ma coordina un’operazione.
Perche questo e utile
Non abbiamo ancora introdotto cartelle, classi o pattern.
Abbiamo solo dato nomi alle responsabilita.
Questo porta vantaggi immediati:
- il flusso principale e piu leggibile;
- alcune funzioni sono piu facili da testare manualmente;
- le regole iniziano ad avere un punto;
- il salvataggio e localizzato;
- i messaggi sono meno sparsi;
- i prossimi refactoring saranno piu semplici.
Limiti da riconoscere
Non fingiamo che sia tutto risolto.
Restano limiti:
validaRichiestaPrestitomescola regole e messaggi;applicaPrestitomodifica dati globali;salvaRegistrodipende direttamente dalocalStorage;registraPrestitoconosce ancora troppe funzioni concrete;- non ci sono test automatici.
Ma ora sappiamo meglio dove intervenire.
Schema mentale da usare in ogni azione
Ogni volta che lavori su un’azione del Registro, prova a compilare questa tabella:
| Passaggio | Domanda | Esempio nel prestito | Dove dovrebbe stare in futuro |
| --- | --- | --- | --- |
| Lettura input | Che cosa ha inserito l'utente? | id strumento, nome studente | Presentation |
| Decisione | L'operazione è permessa? | strumento esiste, non è guasto | Application + Domain |
| Modifica | Quali dati cambiano? | stato in_prestito, nuovo prestito | Domain/Application |
| Salvataggio | Dove salvo? | localStorage | Infrastructure |
| Messaggio | Che cosa vede l'utente? | "Prestito registrato" | Presentation |
Questa tabella serve a non perdersi. Se non sai dove mettere una riga di codice, chiediti quale passaggio rappresenta.
Esempio rapido: aprire una segnalazione di guasto
Lo stesso schema vale per un’altra azione.
Flusso:
leggi input:
id strumento
descrizione guasto
decisione:
lo strumento esiste?
la descrizione è presente?
lo strumento è già guasto?
modifica:
cambia stato in guasto
crea segnalazione
salvataggio:
salva strumenti e segnalazioni
messaggio:
mostra esito
Possibile funzione principale:
function apriSegnalazioneGuasto() {
const input = leggiInputGuasto();
const strumento = trovaStrumentoPerId(strumenti, input.strumentoId);
const validazione = validaSegnalazioneGuasto({ strumento, descrizione: input.descrizione });
if (!validazione.ok) {
mostraMessaggio(validazione.messaggio);
return;
}
applicaSegnalazioneGuasto(strumento, segnalazioni, input.descrizione);
salvaRegistro(strumenti, prestiti, segnalazioni);
mostraStrumenti(strumenti);
mostraMessaggio("Segnalazione registrata");
}
Non è il codice finale del progetto. È una forma didattica: rende visibile il percorso dell’azione.
Attivita guidata
Riorganizza registraPrestito seguendo questa sequenza:
leggi input
trova strumento
valida
applica modifica
salva
mostra messaggio
renderizza
Per ogni passaggio crea una funzione con un nome esplicito.
Poi rispondi:
- quale funzione dipende dalla UI?
- quale funzione contiene regole?
- quale funzione salva?
- quale funzione modifica dati?
- quale funzione sarebbe piu facile testare?
Micro-sfida
Applica lo stesso schema al cambio stato.
Obiettivo:
- trasformare una funzione confusa in un flusso leggibile.
Consegna:
- scegli l’azione “cambia stato”;
- separa lettura input, decisione, modifica, salvataggio e messaggio;
- lascia tutto nello stesso file, se serve;
- aggiungi una nota che spiega quale passaggio sarà spostato in futuro.
Vincoli:
- separa lettura input;
- separa validazione;
- separa modifica;
- separa salvataggio;
- separa messaggio;
- non creare ancora cartelle nuove.
Risultato atteso:
function cambiaStatoStrumento() {
const input = leggiInputCambioStato();
const strumento = trovaStrumentoPerId(strumenti, input.strumentoId);
const validazione = validaCambioStato({ strumento, nuovoStato: input.nuovoStato });
if (!validazione.ok) {
mostraMessaggio(validazione.messaggio);
return;
}
applicaCambioStato(strumento, input.nuovoStato);
salvaRegistro(strumenti, prestiti);
mostraStrumenti(strumenti);
mostraMessaggio("Stato aggiornato");
}
Il codice può essere diverso, ma deve raccontare lo stesso flusso.
Prodotto da ottenere
Alla fine della lezione devi avere:
registraPrestitoriorganizzata in passaggi leggibili;- almeno cinque funzioni con responsabilità nominate;
- una tabella che collega passaggio e livello futuro;
- la micro-sfida sul cambio stato;
- una verifica manuale del comportamento prima e dopo.
Esempio di verifica:
| Caso | Prima | Dopo |
| --- | --- | --- |
| Prestito valido | ok | ok |
| Strumento non trovato | ok | ok |
| Studente mancante | ok | ok |
| Strumento guasto | ok | ok |
| Strumento già in prestito | ok | ok |
Domande di comprensione
Rispondi per iscritto:
- Perché leggere input e decidere una regola sono responsabilità diverse?
- Quale funzione può conoscere il DOM?
- Quale funzione può conoscere
localStorage? - Perché è utile restituire un oggetto
{ ok: false, codice, messaggio }invece di scrivere subito nella pagina? - Quale passaggio assomiglia di più a un futuro caso d’uso?
- Quale passaggio assomiglia di più a un futuro adapter di infrastruttura?
Controllo finale
La lezione è completata quando puoi dire sì a queste domande:
- ho separato almeno quattro responsabilità nel flusso di prestito;
- il comportamento del programma non è cambiato;
- so indicare quale funzione legge la UI;
- so indicare quale funzione decide;
- so indicare quale funzione salva;
- so indicare quale funzione comunica con l’utente;
- ho riconosciuto i limiti ancora presenti.
Nel prossimo passo cercheremo le dipendenze nascoste: variabili globali, DOM implicito e funzioni che sembrano semplici ma dipendono da mezzo programma.