modulo 14 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Rendere esplicite le dipendenze nascoste

Trovare le dipendenze nascoste nel Registro del laboratorio e renderle visibili nella firma delle funzioni, migliorando testabilità e leggibilità senza cambiare il comportamento.

25 minArchitettura del software

Rendere esplicite le dipendenze nascoste

Questa lezione appartiene al Modulo 14 — Refactoring: migliorare senza ricominciare.

Cos’è una dipendenza nascosta

Una dipendenza nascosta è qualcosa che una funzione usa senza dichiararlo nella propria firma. Chi legge la firma non sa che quella dipendenza esiste — deve leggere il corpo per scoprirlo.

Le dipendenze nascoste più comuni nel Registro:

  • variabili globali (let archivioGlobale = [])
  • accesso diretto a localStorage dentro funzioni di dominio o application
  • accesso diretto al DOM (document.getElementById) dentro funzioni di logica
  • import di moduli concreti dentro funzioni che dovrebbero dipendere solo da contratti

Perché sono un problema

Una dipendenza nascosta rende impossibile:

  1. Testare la funzione senza il side effect (es. impossibile testare senza localStorage)
  2. Sostituire la dipendenza (es. impossibile cambiare archivio senza modificare la funzione)
  3. Capire cosa la funzione fa solo leggendo la firma

Tre esempi nel Registro — prima e dopo

Esempio 1: variabile globale

// ❌ Prima: dipendenza nascosta da variabile globale
let strumenti = [];

function registraStrumento(strumento) {
  strumenti.push(strumento); // ← usa strumenti globale — non visibile dalla firma
}

// ✅ Dopo: dipendenza esplicita nel parametro
function registraStrumento(archivioStrumenti, strumento) {
  archivioStrumenti.salva(strumento); // ← dipendenza visibile dalla firma
}

Esempio 2: localStorage diretto in una funzione di application

// ❌ Prima: dipendenza nascosta da localStorage
function trovaPerId(id) {
  const lista = JSON.parse(localStorage.getItem("registro:strumenti") || "[]");
  return lista.find(s => s.id === id) ?? null;
  // ← usa localStorage — non visibile dalla firma
}

// ✅ Dopo: dipendenza sull'archivio, localStorage nascosto nell'adapter
function creaRegistraPrestito({ archivioStrumenti, archivioPrestiti }) {
  return async function registraPrestito(comando) {
    const strumento = await archivioStrumenti.trovaPerId(comando.strumentoId);
    // ← usa archivioStrumenti — dipendenza visibile nel costruttore
  };
}

Esempio 3: DOM diretto in una funzione di logica

// ❌ Prima: dipendenza nascosta dal DOM
function mostraStatoStrumento(id) {
  const strumento = archivio.trovaPerId(id);
  document.getElementById("stato-" + id).textContent = strumento.stato;
  // ← usa document — non visibile dalla firma, impossibile testare
}

// ✅ Dopo: la funzione restituisce il dato, la UI lo usa
function getStatoStrumento(archivioStrumenti, id) {
  const strumento = archivioStrumenti.trovaPerId(id);
  return strumento ? strumento.stato : null;
  // ← nessun DOM — testabile con oggetti in memoria
}

// La Presentation usa il risultato
const stato = getStatoStrumento(archivioStrumenti, id);
document.getElementById("stato-" + id).textContent = stato ?? "non trovato";

Come trovare le dipendenze nascoste nel tuo progetto

Cerca questi pattern nel codice:

localStorage.getItem(...)      → dentro domain/ o application/ = dipendenza nascosta
localStorage.setItem(...)      → dentro domain/ o application/ = dipendenza nascosta
document.getElementById(...)   → dentro domain/ o application/ = dipendenza nascosta
let variabile = [] (globale)   → usata dentro una funzione = dipendenza nascosta
import { ... } from "../infra" → dentro domain/ = dipendenza nascosta

Strumento pratico: apri ogni file in domain/ e application/. Cerca localStorage, document, fetch. Se li trovi → dipendenza nascosta da estrarre.

Come rendere una dipendenza esplicita — tre tecniche

Tecnica 1: aggiungi il parametro

La più semplice. La dipendenza diventa un parametro della funzione.

// Prima
function salvaStrumento(strumento) {
  const lista = JSON.parse(localStorage.getItem("key") || "[]");
  lista.push(strumento);
  localStorage.setItem("key", JSON.stringify(lista));
}

// Dopo — localStorage spostato nell'adapter, archivio come parametro
function salvaStrumento(archivioStrumenti, strumento) {
  archivioStrumenti.salva(strumento);
}

Tecnica 2: usa una factory (costruttore di funzioni)

Quando più funzioni condividono la stessa dipendenza, si raggruppano con una factory.

// Prima: ogni funzione riceve archivioStrumenti come parametro → ripetitivo
function registraStrumento(archivioStrumenti, strumento) { ... }
function cercaStrumenti(archivioStrumenti, filtri) { ... }
function trovaPerId(archivioStrumenti, id) { ... }

// Dopo: factory che riceve la dipendenza una volta sola
function creaGestoreStrumenti({ archivioStrumenti }) {
  return {
    registra: (strumento) => archivioStrumenti.salva(strumento),
    cerca: (filtri) => archivioStrumenti.leggiTutti().filter(...),
    trovaPerId: (id) => archivioStrumenti.trovaPerId(id)
  };
}

Tecnica 3: sposta la dipendenza nel punto di composizione

Se la dipendenza si usa solo in un posto, la si porta a main.js dove tutto viene composto.

// main.js — unico posto dove si costruiscono le dipendenze concrete
const archivioStrumenti = creaArchivioStrumentiLocalStorage();
const archivioPrestiti = creaArchivioPrestitiLocalStorage();
const registraPrestito = creaRegistraPrestito({ archivioStrumenti, archivioPrestiti });

La procedura del refactoring

  1. Identifica la dipendenza nascosta (es. localStorage in application/).
  2. Scrivi una verifica prima di modificare (test o scenario manuale).
  3. Aggiungi la dipendenza come parametro o factory.
  4. Aggiorna il punto di chiamata per passare la dipendenza.
  5. Verifica che il comportamento sia identico.
  6. Controlla che il file ora testabile possa girare con un archivio in memoria.

Attività in classe

## Dipendenza nascosta resa esplicita

Dipendenza trovata:
  - File e riga: 
  - Tipo: localStorage / variabile globale / document / altro

Tecnica usata: parametro / factory / punto di composizione

Firma prima:

Firma dopo:

Verifica eseguita prima:

Verifica eseguita dopo (comportamento invariato):

La funzione ora è testabile senza browser (sì/no):

Spiegazione guidata per studiare

Una dipendenza nascosta è una bugia nella firma della funzione. La firma dice “ho bisogno di questi parametri” ma il corpo dice “ho bisogno anche di localStorage e di una variabile globale”. Chi chiama la funzione non lo sa — e non può sostituire quelle dipendenze.

Il punto centrale è: rendere esplicita una dipendenza non cambia cosa fa la funzione — cambia chi decide cosa usare. Con la dipendenza nascosta, è la funzione a scegliere l’archivio. Con la dipendenza esplicita, è chi costruisce il sistema a sceglierlo — e può scegliere un archivio in memoria per i test.

Il rischio tipico è pensare che aggiungere un parametro complichi il codice. In realtà lo semplifica: chi legge la firma sa esattamente di cosa la funzione ha bisogno, senza leggere il corpo.

Passo per passo nel progetto

  1. Apri i file in domain/ — cercali per localStorage. Trovato qualcosa? È una dipendenza nascosta da estrarre.
  2. Apri i file in application/ — stessa cosa.
  3. Cerca variabili dichiarate fuori dalle funzioni che le usano.
  4. Scegli la dipendenza nascosta più critica (quella che rende impossibile il test).
  5. Rendila esplicita con la tecnica più appropriata.
  6. Verifica che il comportamento sia invariato.

Esempio da leggere lentamente

// Prima: dipendenza nascosta — impossibile testare senza localStorage
function registraStrumento(strumento) {
  const lista = JSON.parse(localStorage.getItem("registro:strumenti") || "[]");
  lista.push(strumento);
  localStorage.setItem("registro:strumenti", JSON.stringify(lista));
}

// Non testabile: ogni test modifica il localStorage reale

// Dopo: dipendenza esplicita — testabile con archivio in memoria
function creaRegistraStrumento({ archivioStrumenti }) {
  return function registraStrumento(strumento) {
    archivioStrumenti.salva(strumento);
  };
}

// Test con archivio in memoria — nessun localStorage coinvolto
const archivioFinto = creaArchivioStrumentiInMemoria();
const registraStrumento = creaRegistraStrumento({ archivioStrumenti: archivioFinto });
registraStrumento({ id: "s1", nome: "Trapano", categoria: "Elettrico", stato: "disponibile" });
console.assert(archivioFinto.trovaPerId("s1") !== null, "Strumento dovrebbe esistere");

Prova di comprensione

  • Trova una dipendenza nascosta nel tuo progetto. Dove stava?
  • Dopo averla resa esplicita, puoi testare la funzione senza browser?
  • Quale tecnica hai usato (parametro, factory, punto di composizione)?

Errori da evitare

  • rendere esplicita la dipendenza ma lasciare anche la versione nascosta “per sicurezza”;
  • aggiungere parametri senza aggiornare tutti i chiamanti;
  • confondere “rendere esplicita” con “aggiungere complessità” — è il contrario;
  • fare refactoring e cambiare comportamento nello stesso passo.

Prodotto da consegnare

Può essere:

  • una funzione con dipendenza nascosta trasformata in dipendenza esplicita;
  • un test che verifica la funzione refactorizzata usando un archivio in memoria;
  • la scheda compilata con prima, dopo e verifica.

Checklist di chiusura

  • Ho trovato almeno una dipendenza nascosta nel progetto
  • La dipendenza è ora visibile nella firma della funzione
  • Ho aggiornato tutti i punti che chiamano la funzione modificata
  • Il comportamento del Registro è identico prima e dopo
  • La funzione refactorizzata può essere testata senza browser

Domande per la revisione

  • Quante dipendenze nascoste hai trovato in totale? Ne hai resa esplicita solo una?
  • La funzione è ora testabile senza localStorage? Come lo verifichi?
  • Quale dei tre tipi di dipendenza nascosta era più comune nel tuo progetto?

Risultato atteso

Alla fine lo studente deve saper dire:

Ho trovato questa dipendenza nascosta: [localStorage / variabile globale / document]
in [file:funzione].
L'ho resa esplicita aggiungendo [parametro / factory].
Il comportamento è invariato — ho verificato con [test / scenario manuale].
La funzione ora può essere testata con un archivio in memoria.

Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.