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.

25 minArchitettura del software

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:

  1. Quali variabili usa che non sono parametri?
  2. Quali elementi HTML legge o modifica?
  3. Usa localStorage, fetch, file, database o API?
  4. Usa tempo, numeri casuali o generatori di ID?
  5. 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.