modulo 19 di 24 / Ore 41-60

Moduli 17-24 / Checkpoint 60 ore

Caricamento e salvataggio: costi e frequenza

Analisi del costo di caricamento e salvataggio in localStorage nel Registro del laboratorio scolastico: quando costa di più, con quale frequenza avviene, quando ottimizzare.

25 min Architettura del software

Caricamento e salvataggio: costi e frequenza

Questa lezione appartiene al Modulo 19 — Performance e ottimizzazione pratica.

Il lavoro continua il progetto guida, il Registro del laboratorio scolastico. In questa lezione analizzi quanto costano le operazioni di lettura e scrittura su localStorage e decidi quando vale la pena ottimizzare.

Obiettivo della lezione

Capire il costo reale di caricaTutti e salva nel Registro. Misurare con console.time. Identificare le situazioni in cui si legge e si scrive troppo spesso e proporre una soluzione misurata.

Alla fine devi avere: un’analisi documentata con almeno tre misurazioni e una decisione motivata su cosa ottimizzare (o non ottimizzare).

Come funzionano caricaTutti e salva

L’adapter localStorage del Registro fa operazioni semplici ma costose al crescere dei dati:

// infrastructure/archivioStrumentiLocalStorage.js

export function caricaTutti() {
  const raw = localStorage.getItem("registro_strumenti");
  if (!raw) return [];
  return JSON.parse(raw).map(dtoToStrumento);
}

export function salva(strumenti) {
  const dtos = strumenti.map(strumentoToDTO);
  localStorage.setItem("registro_strumenti", JSON.stringify(dtos));
}

Ogni caricaTutti legge l’intera stringa da localStorage e fa il parse di tutto l’array. Ogni salva serializza l’intero array e riscrive la stringa in localStorage. Non c’è scrittura parziale, non c’è aggiornamento incrementale.

Il costo in pratica

Per il Registro scolastico, con pochi decine di strumenti, il costo è trascurabile — pochi millisecondi. Ma capire il costo serve a ragionare su quando ottimizzare e quando la semplicità è preferibile alla performance.

I fattori che influenzano il costo sono:

  • dimensione dell’array (numero di strumenti o prestiti)
  • dimensione di ogni oggetto (quanti campi, quante stringhe lunghe)
  • frequenza con cui si chiama caricaTutti o salva in una singola sessione utente

Il problema tipico del Registro non è un singolo salvataggio lento — è chiamare caricaTutti molte volte consecutive senza che i dati siano cambiati.

Nel Registro del laboratorio

Un pattern problematico comune è questo:

// PROBLEMA: caricaTutti chiamato tre volte per la stessa operazione
function mostraStatistiche(archivioStrumenti, archivioPrestiti) {
  const tutti = archivioStrumenti.caricaTutti();
  const disponibili = archivioStrumenti.caricaTutti().filter(s => s.stato === "disponibile");
  const inPrestito = archivioStrumenti.caricaTutti().filter(s => s.stato === "in-prestito");
  return { totale: tutti.length, disponibili: disponibili.length, inPrestito: inPrestito.length };
}

Si legge da localStorage tre volte quando basterebbe una. La soluzione non richiede cache o ottimizzazioni complesse — basta chiamare caricaTutti una sola volta:

// CORRETTO: una sola lettura
function mostraStatistiche(archivioStrumenti) {
  const tutti = archivioStrumenti.caricaTutti();
  const disponibili = tutti.filter(s => s.stato === "disponibile");
  const inPrestito = tutti.filter(s => s.stato === "in-prestito");
  return { totale: tutti.length, disponibili: disponibili.length, inPrestito: inPrestito.length };
}

Procedura guidata

  1. Identifica i punti del Registro dove si chiama caricaTutti o salva.
  2. Conta quante volte vengono chiamati in sequenza nella stessa operazione.
  3. Aggiungi misurazioni con console.time e console.timeEnd.
  4. Documenta i tempi misurati.
  5. Identifica le chiamate ridondanti e rimuovile.
  6. Misura di nuovo dopo la modifica e confronta.

Attività in classe

Compila questa scheda di analisi:

## Analisi costi localStorage

Operazione 1: caricaTutti in CercaStrumenti
Tempo misurato (console.time): __ ms
Chiamate ridondanti trovate: __

Operazione 2: salva in RegistraStrumento
Tempo misurato: __ ms
Necessaria ogni volta? sì / no — motivo:

Operazione 3: caricaTutti + salva in ApriPrestito
Tempo totale: __ ms
Cosa si legge, cosa si scrive:

Caso problematico trovato nel mio codice:
Soluzione applicata:

Spiegazione guidata per studiare

localStorage è sincrono e bloccante: mentre legge o scrive, il browser non fa altro. Per quantità piccole questo non si nota. Per array grandi (centinaia di record con campi lunghi) può causare un ritardo percepibile.

La regola pratica per il Registro è: non ottimizzare prima di misurare. Tante ottimizzazioni premature rendono il codice più complesso senza beneficio reale. Prima misura con console.time, poi decidi se il costo giustifica la complessità aggiunta.

La seconda regola: carica una volta, usa più volte. Se hai bisogno dei dati in più punti della stessa funzione, chiamala una volta e passa l’array come parametro. Non serve una cache — basta non chiamare caricaTutti più volte nella stessa esecuzione.

Passo per passo nel progetto

  1. Apri src/application/ e cerca tutti i punti dove si chiama archivio.caricaTutti().
  2. Per ogni caso d’uso, aggiungi misurazioni temporanee:
// application/registraStrumento.js — con misurazione temporanea
export function registraStrumento({ nome, categoria }, archivio) {
  console.time("caricaTutti-registraStrumento");
  const strumenti = archivio.caricaTutti();
  console.timeEnd("caricaTutti-registraStrumento");

  const nuovoStrumento = {
    id: crypto.randomUUID(),
    nome: nome.trim(),
    categoria,
    stato: "disponibile"
  };
  strumenti.push(nuovoStrumento);

  console.time("salva-registraStrumento");
  archivio.salva(strumenti);
  console.timeEnd("salva-registraStrumento");

  return nuovoStrumento;
}
  1. Apri la console del browser, esegui l’operazione e leggi i tempi.
  2. Cerca i pattern di lettura ripetuta. Un caso tipico nel Registro:
// PROBLEMA: apriPrestito legge gli strumenti due volte
export function apriPrestito({ strumentoId }, archivioStrumenti, archivioPrestiti) {
  const strumenti = archivioStrumenti.caricaTutti();
  const strumento = strumenti.find(s => s.id === strumentoId);
  if (!strumento) throw new Error("STRUMENTO_NON_TROVATO");

  // secondo caricaTutti — ridondante!
  const tuttiGliStrumenti = archivioStrumenti.caricaTutti();
  tuttiGliStrumenti.find(s => s.id === strumentoId).stato = "in-prestito";
  archivioStrumenti.salva(tuttiGliStrumenti);
}

Il secondo caricaTutti è ridondante — hai già l’array nella variabile strumenti. Correzione:

export function apriPrestito({ strumentoId }, archivioStrumenti, archivioPrestiti) {
  const strumenti = archivioStrumenti.caricaTutti(); // una sola lettura
  const strumento = strumenti.find(s => s.id === strumentoId);
  if (!strumento) throw new Error("STRUMENTO_NON_TROVATO");
  if (strumento.stato !== "disponibile") throw new Error("STRUMENTO_NON_DISPONIBILE");

  strumento.stato = "in-prestito"; // modifica sull'array già caricato
  archivioStrumenti.salva(strumenti); // una sola scrittura
  // ...
}
  1. Dopo le correzioni, misura di nuovo e documenta il risultato.

Esempio JS — frequenza tipica nel Registro

// Scenario tipico in una sessione di 10 minuti nel laboratorio:
// - 5 ricerche di strumenti → 5 caricaTutti (sola lettura)
// - 3 prestiti aperti → 3 coppie caricaTutti + salva
// - 2 restituzioni → 2 coppie caricaTutti + salva
// - 1 segnalazione guasto → 1 coppia caricaTutti + salva
// Totale: ~11 caricaTutti, ~6 salva

// Per 50 strumenti, ogni operazione dura < 1 ms → costo totale < 20 ms
// Non c'è nulla da ottimizzare in questo scenario

La conclusione pratica: per un registro scolastico con meno di 500 strumenti e pochi utenti contemporanei, non serve ottimizzare. Serve evitare le chiamate ridondanti evidenti.

Prova di comprensione

Rispondi per iscritto:

  • Qual è la differenza tra “caricaTutti è lento” e “caricaTutti viene chiamato troppo spesso”?
  • Perché non conviene fare una cache in memoria dei dati di localStorage nel Registro?
  • Cosa misura console.time/console.timeEnd e cosa non misura?
  • Quando sarebbe ragionevole aggiungere una cache in memoria per gli strumenti?

Errori da evitare

  • ottimizzare prima di misurare — il codice diventa complesso senza beneficio reale;
  • chiamare caricaTutti più volte nella stessa funzione senza necessità;
  • fare salva dopo ogni singola modifica quando puoi raccogliere le modifiche e salvare una volta sola;
  • confondere la lentezza percepita con un problema di localStorage — spesso è la UI che non risponde durante operazioni sincrone.

Prodotto da consegnare

Una scheda di analisi compilata con i tempi misurati e almeno una correzione applicata al codice. Come verifica: mostra console.time in azione nel browser con i dati reali del Registro.

Checklist di chiusura

  • ho misurato almeno tre operazioni con console.time;
  • ho identificato almeno una chiamata ridondante a caricaTutti;
  • ho corretto la chiamata ridondante e rimisurare;
  • ho documentato il risultato nella scheda;
  • le misurazioni temporanee sono state rimosse dal codice finale.

Risultato atteso

Ho misurato caricaTutti e salva nel mio Registro.
I tempi sono: [valori].
Ho trovato una chiamata ridondante in: [caso d'uso].
Dopo la correzione il costo è sceso da X a Y chiamate a localStorage.
Conclusione: con i dati attuali non serve ottimizzare ulteriormente.