modulo 04 di 24 / Ore 0-20
Moduli 1-8 / Checkpoint 20 ore
Riconoscere dipendenze nascoste
Individuare variabili globali, DOM implicito, localStorage e funzioni esterne usate senza essere dichiarate chiaramente nel Registro del laboratorio.
Riconoscere dipendenze nascoste
Una dipendenza è qualcosa di cui una funzione ha bisogno per lavorare.
Alcune dipendenze sono visibili:
function trovaStrumentoPerId(strumenti, id) {
return strumenti.find((strumento) => strumento.id === id);
}
La funzione dipende da strumenti e id. Lo vedi subito dai parametri.
Altre dipendenze sono nascoste:
function aggiornaRisultati() {
const testo = inputRicerca.value.toLowerCase();
const risultati = strumenti.filter((strumento) => {
return strumento.nome.toLowerCase().includes(testo);
});
mostraStrumenti(risultati);
}
Questa funzione dipende da:
inputRicerca;strumenti;mostraStrumenti;- struttura degli oggetti strumento;
- DOM.
Ma nei parametri non appare nulla.
Questa lezione allena una capacità molto pratica: leggere una funzione e chiedersi “da quali cose dipende davvero?”. È una capacità più importante della definizione a memoria, perché ti permette di capire perché un pezzo di codice è difficile da testare, spostare o modificare.
Obiettivo della lezione
Alla fine della lezione devi saper:
- distinguere dipendenze visibili e dipendenze nascoste;
- trovare dipendenze da DOM, variabili globali,
localStorage, tempo e funzioni esterne; - capire quando una dipendenza è coerente con la responsabilità della funzione;
- trasformare almeno una dipendenza nascosta in parametro;
- compilare una piccola scheda di analisi per tre funzioni del Registro.
Perche sono pericolose
Le dipendenze nascoste rendono difficile:
- capire la funzione;
- spostarla in un altro file;
- testarla;
- riusarla;
- modificare il contesto;
- capire perche si rompe.
Se una funzione usa variabili globali, sembra piu semplice di quanto sia davvero.
Esempio:
function contaStrumentiDisponibili() {
return strumenti.filter((strumento) => strumento.stato === "disponibile").length;
}
La funzione sembra comodissima: non prende parametri. In realtà dipende da strumenti, una variabile globale. Questo significa che:
- non puoi provarla facilmente con una lista diversa;
- non sai da dove arrivi il valore di
strumenti; - se un altro pezzo del programma modifica
strumenti, il risultato cambia; - spostare la funzione in un altro file richiede di portarsi dietro anche la variabile globale.
Una versione più esplicita è:
function contaStrumentiDisponibili(strumenti) {
return strumenti.filter((strumento) => strumento.stato === "disponibile").length;
}
Ora la funzione dichiara ciò che le serve.
Tipi di dipendenze nascoste nel registro
Nel registro fragile puoi trovare:
| Dipendenza nascosta | Esempio |
|---|---|
| DOM | inputRicerca.value |
| Stato globale | strumenti, prestiti |
| Persistenza | localStorage |
| Funzioni esterne | mostraStrumenti(...) |
| Tempo | Date.now() |
| Convenzioni stringa | "in_prestito" |
Non tutte sono sbagliate. Ma devi saperle vedere.
Questa tabella va letta così:
- se una funzione di UI usa il DOM, probabilmente va bene;
- se una funzione di dominio usa il DOM, c’è un problema;
- se una funzione di infrastruttura usa
localStorage, probabilmente va bene; - se una funzione di dominio usa
localStorage, c’è un problema; - se una funzione di test dipende da
Date.now(), potresti avere risultati difficili da controllare.
Il punto non è eliminare ogni dipendenza. Il punto è mettere ogni dipendenza nel posto giusto.
Esempio: funzione con DOM nascosto
function leggiInputPrestito() {
return {
strumentoId: inputStrumentoId.value.trim(),
studente: inputStudente.value.trim(),
};
}
Questa funzione dipende dal DOM. Va bene, perché il suo nome dice “leggi input”.
La dipendenza non è un problema se è coerente con la responsabilità.
Diventa un problema se una funzione di dominio fa questo:
function puoEsserePrestato() {
const id = inputStrumentoId.value;
const strumento = strumenti.find((item) => item.id === id);
return strumento.stato === "disponibile";
}
Qui una regola dipende da input HTML e stato globale.
Questa funzione risponde alla domanda:
lo strumento può essere prestato?
Ma per rispondere legge:
inputStrumentoId, cioè la UI;strumenti, cioè uno stato globale;stato, cioè il modello dati.
Il nome sembra una regola di dominio, ma il contenuto dipende dalla pagina. Questo è un segnale forte: la funzione è nel posto sbagliato o fa troppe cose.
Una versione più chiara:
function puoEsserePrestato(strumento) {
return strumento.stato === "disponibile";
}
Ora la funzione non sa come lo strumento è stato trovato. Non sa se arriva da un form, da un file, da localStorage o da un test. Sa solo valutare uno strumento.
La UI può fare il suo pezzo:
const id = inputStrumentoId.value;
const strumento = trovaStrumentoPerId(strumenti, id);
if (!puoEsserePrestato(strumento)) {
mostraMessaggio("Lo strumento non può essere prestato");
}
La separazione non è ancora perfetta, ma le dipendenze sono più leggibili.
Esempio: stato globale nascosto
Osserva questa funzione:
function chiudiPrestito(prestitoId) {
const prestito = prestiti.find((item) => item.id === prestitoId);
const strumento = strumenti.find((item) => item.id === prestito.strumentoId);
prestito.aperto = false;
strumento.stato = "disponibile";
}
La firma dice:
function chiudiPrestito(prestitoId)
Sembra che serva solo prestitoId. In realtà servono:
prestiti;strumenti;- struttura del prestito;
- struttura dello strumento;
- convenzione dello stato
disponibile; - possibilità di modificare gli oggetti globali.
Una versione più esplicita:
function chiudiPrestito(prestiti, strumenti, prestitoId) {
const prestito = prestiti.find((item) => item.id === prestitoId);
if (!prestito) {
return { ok: false, errore: "prestito_non_trovato" };
}
const strumento = strumenti.find((item) => item.id === prestito.strumentoId);
if (!strumento) {
return { ok: false, errore: "strumento_non_trovato" };
}
prestito.aperto = false;
strumento.stato = "disponibile";
return { ok: true };
}
Non è ancora la versione finale del corso. Però ora è più facile capire da che cosa dipende l’operazione.
Esempio: localStorage nascosto
Questa funzione sembra registrare uno strumento:
function registraStrumento(nome, categoria) {
strumenti.push({
id: `str-${Date.now()}`,
nome,
categoria,
stato: "disponibile",
});
localStorage.setItem("strumenti", JSON.stringify(strumenti));
}
In realtà fa almeno tre cose:
- crea un oggetto strumento;
- modifica la lista globale;
- salva in
localStorage.
Se domani vogliamo salvare su file JSON, dobbiamo toccare questa funzione. Se vogliamo testare la creazione dello strumento, ci portiamo dietro localStorage.
Un primo passo può essere:
function creaStrumento(nome, categoria, id) {
return {
id,
nome,
categoria,
stato: "disponibile",
};
}
Poi la parte esterna decide dove mettere e salvare lo strumento:
const strumento = creaStrumento(nome, categoria, `str-${Date.now()}`);
strumenti.push(strumento);
salvaStrumenti(strumenti);
Non abbiamo risolto tutto, ma abbiamo separato creazione e salvataggio.
Esempio: tempo nascosto
Questa funzione crea un prestito:
function creaPrestito(strumentoId, studente) {
return {
id: `pre-${Date.now()}`,
strumentoId,
studente,
aperto: true,
};
}
Dipende da Date.now().
Per l’applicazione va bene. Per un test automatico può essere scomodo, perché l’id cambia ogni volta.
Più avanti potremmo passare l’id o una funzione generatrice come dipendenza. Per ora basta riconoscere il punto.
Versione più controllabile:
function creaPrestito(strumentoId, studente, id) {
return {
id,
strumentoId,
studente,
aperto: true,
};
}
Nel programma:
const prestito = creaPrestito(strumento.id, studente, `pre-${Date.now()}`);
Nel test:
const prestito = creaPrestito("s-1", "Giulia Rossi", "pre-test-1");
La funzione è più facile da verificare perché il valore variabile viene deciso fuori.
Esempio: funzione esterna nascosta
Guarda questa funzione:
function mostraRisultatiRicerca(risultati) {
listaElement.innerHTML = "";
risultati.forEach((strumento) => {
const elemento = creaElementoStrumento(strumento);
listaElement.appendChild(elemento);
});
}
Dipende da:
listaElement;creaElementoStrumento;- struttura di
strumento; - API del DOM.
Va bene, perché è una funzione di render. La dipendenza è coerente con il suo compito.
Il problema sarebbe usare la stessa funzione per decidere quali strumenti mostrare:
function mostraRisultatiRicerca() {
const testo = inputRicerca.value.toLowerCase();
const risultati = strumenti.filter((strumento) => {
return strumento.nome.toLowerCase().includes(testo);
});
listaElement.innerHTML = "";
// render...
}
Qui render e ricerca sono mescolati. La ricerca diventa difficile da testare senza DOM.
Rendere visibili le dipendenze
Una funzione piu chiara riceve quello che le serve:
function cercaStrumenti(strumenti, filtri) {
const testo = filtri.testo.toLowerCase();
return strumenti.filter((strumento) => {
return (
strumento.nome.toLowerCase().includes(testo) ||
strumento.categoria.toLowerCase().includes(testo)
);
});
}
La UI prepara i filtri:
const filtri = {
testo: inputRicerca.value,
stato: filtroStato.value,
};
Poi chiama:
const risultati = cercaStrumenti(strumenti, filtri);
Abbiamo separato un pezzo di logica dalla UI.
Il criterio pratico è:
se una funzione usa qualcosa che non è nei parametri e non è dichiarato dentro la funzione, chiediti se quella dipendenza dovrebbe essere esplicita.
Non devi trasformare ogni cosa in parametro. Se una funzione si chiama mostraMessaggio e usa messaggioElement, la dipendenza dal DOM può essere accettabile. Ma se una funzione si chiama calcolaPrestabilità e usa inputStrumentoId, la dipendenza è sospetta.
Dipendenze accettabili
Non devi eliminare tutte le dipendenze.
E accettabile che:
- una funzione UI dipenda dal DOM;
- una funzione infrastrutturale dipenda da
localStorage; - una funzione di render dipenda dal formato HTML;
- una funzione applicativa dipenda da funzioni di dominio.
Il problema è quando una funzione dipende da dettagli che non appartengono alla sua responsabilità.
Esempi:
Presentation -> DOM: accettabile
Presentation -> regole importanti: sospetto
Domain -> stati del dominio: accettabile
Domain -> localStorage: sbagliato
Infrastructure -> localStorage: accettabile
Application -> markup HTML: sospetto
Test di dominio -> browser: troppo costoso
Questa non è burocrazia. Serve a proteggere il progetto quando cresce.
Metodo di ispezione in cinque domande
Quando analizzi una funzione, chiediti:
- Quali variabili usa che non sono parametri?
- Quali elementi HTML legge o modifica?
- Usa
localStorage,fetch, file, database o API? - Usa tempo, numeri casuali o generatori di ID?
- Chiama funzioni che fanno cose importanti fuori dal suo nome?
Esempio:
function registraGuasto() {
const id = inputGuastoStrumento.value;
const descrizione = inputDescrizioneGuasto.value;
const strumento = strumenti.find((item) => item.id === id);
strumento.stato = "guasto";
segnalazioni.push({ id: Date.now(), strumentoId: id, descrizione });
localStorage.setItem("strumenti", JSON.stringify(strumenti));
localStorage.setItem("segnalazioni", JSON.stringify(segnalazioni));
mostraMessaggio("Guasto registrato");
}
Scheda:
Funzione: registraGuasto
Dipendenze nascoste:
- inputGuastoStrumento
- inputDescrizioneGuasto
- strumenti
- segnalazioni
- Date.now()
- localStorage
- mostraMessaggio
Responsabilità mescolate:
- lettura UI
- ricerca strumento
- modifica stato
- creazione segnalazione
- salvataggio
- messaggio utente
Primo miglioramento:
- estrarre leggiInputGuasto()
- estrarre creaSegnalazioneGuasto(...)
Questa scheda ti impedisce di dire solo “è scritto male”. Ti costringe a indicare che cosa è nascosto e quale passo concreto puoi fare.
Attivita guidata
Scegli tre funzioni del tuo registro.
Per ciascuna compila:
## Dipendenze funzione
Funzione:
Dipendenze visibili:
- ...
Dipendenze nascoste:
- ...
Sono coerenti con la responsabilita?
Quale dipendenza potrei rendere esplicita?
Esempio:
Funzione: aggiornaRisultati
Dipendenze nascoste:
- inputRicerca
- filtroStato
- strumenti
- mostraStrumenti
Possibile miglioramento:
estrarre cercaStrumenti(strumenti, filtri)
Micro-sfida
Trasforma una dipendenza nascosta in parametro.
Esempio:
Prima:
function trovaStrumento() {
return strumenti.find((strumento) => strumento.id === inputStrumentoId.value);
}
Dopo:
function trovaStrumentoPerId(strumenti, id) {
return strumenti.find((strumento) => strumento.id === id);
}
Vincoli:
- cambia una sola funzione;
- verifica che il comportamento resti uguale;
- annota quale dipendenza hai reso visibile.
Risultato atteso:
Funzione modificata: trovaStrumento
Dipendenza nascosta prima: strumenti globale, inputStrumentoId
Dipendenza resa esplicita: strumenti, id
Verifica: prestito con strumento esistente ancora funzionante
Nota: la funzione ora può essere provata senza DOM
Prodotto da ottenere
Alla fine della lezione devi avere:
- tre schede di analisi delle dipendenze;
- almeno una dipendenza nascosta trasformata in parametro;
- una nota su una dipendenza accettabile;
- una nota su una dipendenza sospetta;
- una verifica manuale del comportamento dopo la modifica.
Domande di comprensione
Rispondi per iscritto:
- Che cos’è una dipendenza nascosta?
- Perché una funzione senza parametri può essere meno semplice di quanto sembri?
- Quando una dipendenza dal DOM è accettabile?
- Perché
Date.now()può creare problemi nei test? - Quale dipendenza nascosta hai trovato nel tuo Registro?
- Quale dipendenza hai reso esplicita?
Controllo finale
La lezione è completata quando puoi dire sì a queste domande:
- so spiegare che cos’è una dipendenza;
- so distinguere dipendenze visibili e nascoste;
- ho analizzato almeno tre funzioni;
- ho trovato dipendenze da DOM, stato globale o localStorage;
- ho trasformato almeno una dipendenza nascosta in parametro;
- so dire quando una dipendenza è coerente con la responsabilità della funzione.
Nel prossimo passo chiudiamo il modulo con una micro-sfida dedicata: trovare la responsabilità nascosta in un blocco di codice.