modulo 04 di 24 / Ore 0-20

Moduli 1-8 / Checkpoint 20 ore

Smontare una funzione lunga passo per passo

Refactorare una funzione lunga del Registro del laboratorio in piccoli passi, mantenendo il programma funzionante e rendendo visibili le responsabilità.

35 min Architettura del software

Smontare una funzione lunga passo per passo

Ora iniziamo un refactoring controllato.

Refactoring significa cambiare la struttura del codice senza cambiare il comportamento osservabile. L’utente deve poter fare le stesse cose di prima, ma il codice deve diventare più leggibile, più separato e meno rischioso da modificare.

In questa lezione smontiamo una funzione lunga del Registro del laboratorio. Non cerchiamo ancora l’architettura perfetta. Facciamo una cosa più importante per uno studente che sta imparando: prendiamo codice reale, lo miglioriamo a piccoli passi e controlliamo che continui a funzionare.

Obiettivo della lezione

Alla fine della lezione devi saper:

  • riconoscere una funzione lunga con troppe responsabilità;
  • estrarre funzioni piccole senza cambiare il comportamento;
  • dare nomi utili alle responsabilità estratte;
  • verificare il programma dopo ogni passo;
  • distinguere un refactoring controllato da una riscrittura impulsiva;
  • spiegare che cosa è migliorato e che cosa resta ancora fragile.

Punto di partenza

Usiamo una funzione fragile:

function registraPrestitoFragile() {
  const id = inputStrumentoId.value;
  const studente = inputStudente.value.trim();
  const strumento = strumenti.find((item) => item.id === id);

  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 puo essere prestato";
    return;
  }

  if (strumento.stato === "in_prestito") {
    messaggioElement.textContent = "Lo strumento e gia 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));

  messaggioElement.textContent = "Prestito registrato";
  mostraStrumenti(strumenti);
}

Questa funzione funziona, ma fa troppe cose insieme:

  • legge campi della UI;
  • cerca uno strumento;
  • valida l’input;
  • applica regole di dominio;
  • modifica lo stato dello strumento;
  • crea un prestito;
  • salva dati in localStorage;
  • mostra messaggi;
  • aggiorna la lista a schermo.

Una funzione così non è lunga solo per numero di righe. È lunga perché contiene molte responsabilità. Il problema non è estetico: se una regola cambia, se cambia la UI o se cambia il salvataggio, dobbiamo toccare lo stesso blocco.

Prima regola: un passo alla volta

Un refactoring sicuro procede così:

  1. scegli un pezzo piccolo;
  2. estrailo;
  3. verifica che il programma funzioni ancora;
  4. fai il passo successivo.

Non fare cinque cambiamenti insieme. Se qualcosa si rompe, non saprai quale modifica ha causato il problema.

Prima di toccare il codice, prepara tre prove manuali:

## Prove prima del refactoring

1. Prestito valido
   - Strumento disponibile
   - Studente compilato
   - Risultato atteso: prestito registrato e strumento in prestito

2. Strumento inesistente
   - ID non presente
   - Risultato atteso: messaggio "Strumento non trovato"

3. Strumento guasto
   - Stato: guasto
   - Risultato atteso: il prestito non viene registrato

Queste prove non sostituiscono i test automatici, che arriveranno più avanti. Servono però a non lavorare alla cieca.

Passo 1: estrarre la lettura input

Prima:

const id = inputStrumentoId.value;
const studente = inputStudente.value;

Dopo:

function leggiInputPrestito() {
  return {
    strumentoId: inputStrumentoId.value,
    studente: inputStudente.value.trim(),
  };
}

Uso:

function registraPrestitoFragile() {
  const input = leggiInputPrestito();
  const strumento = strumenti.find((item) => item.id === input.strumentoId);
  // ...
}

Questa funzione dipende ancora dalla UI. Va bene: il suo nome lo dice. L’obiettivo del primo passo non è rendere tutto puro, ma separare una responsabilità: leggere i dati inseriti dall’utente.

Verifica dopo il passo:

  • registra un prestito valido;
  • prova uno studente vuoto;
  • controlla che il messaggio sia uguale a prima.

Se il comportamento è cambiato, fermati. Non passare al passo successivo finché non hai capito cosa è successo.

Passo 2: estrarre la ricerca dello strumento

Prima:

const strumento = strumenti.find((item) => item.id === input.strumentoId);

Dopo:

function trovaStrumentoPerId(strumenti, id) {
  return strumenti.find((strumento) => strumento.id === id);
}

Uso:

const strumento = trovaStrumentoPerId(strumenti, input.strumentoId);

Questa funzione e piu testabile: riceve lista e id, non legge il DOM.

Possiamo già provarla senza browser:

const strumentiDiEsempio = [
  { id: "s-1", nome: "Microscopio" },
  { id: "s-2", nome: "Tablet" },
];

console.log(trovaStrumentoPerId(strumentiDiEsempio, "s-2"));

Non è ancora un test automatico, ma mostra un fatto importante: quando una funzione riceve i dati come parametri, diventa più facile verificarla.

Passo 3: estrarre la regola di prestabilita

Prima:

if (strumento.stato === "guasto") {
  messaggioElement.textContent = "Uno strumento guasto non puo essere prestato";
  return;
}

if (strumento.stato === "in_prestito") {
  messaggioElement.textContent = "Lo strumento e gia in prestito";
  return;
}

Dopo:

function motivoNonPrestabile(strumento) {
  if (strumento.stato === "guasto") {
    return "Uno strumento guasto non può essere prestato";
  }

  if (strumento.stato === "in_prestito") {
    return "Lo strumento è già in prestito";
  }

  return null;
}

Uso:

const errorePrestito = motivoNonPrestabile(strumento);

if (errorePrestito) {
  messaggioElement.textContent = errorePrestito;
  return;
}

Questa funzione non è ancora perfetta: restituisce messaggi utente, quindi mescola ancora un po’ dominio e presentazione. Però è già un miglioramento perché la regola ha un nome.

Più avanti separeremo errore tecnico, errore di dominio e messaggio utente. In questo momento ci basta aver isolato la domanda:

questo strumento può essere prestato?

Verifica dopo il passo:

  • strumento guasto: prestito bloccato;
  • strumento già in prestito: prestito bloccato;
  • strumento disponibile: prestito consentito.

Se uno di questi casi cambia comportamento, il refactoring non è ancora corretto.

Passo 4: estrarre la creazione del prestito

Prima:

prestiti.push({
  id: `pre-${Date.now()}`,
  strumentoId: strumento.id,
  studente,
  aperto: true,
});

Dopo:

function creaPrestito(strumentoId, studente) {
  return {
    id: `pre-${Date.now()}`,
    strumentoId,
    studente,
    aperto: true,
  };
}

Uso:

const prestito = creaPrestito(strumento.id, input.studente);
prestiti.push(prestito);

Ora la forma del prestito è in un punto solo. Questo è utile perché un prestito ha una struttura precisa:

  • id;
  • strumentoId;
  • studente;
  • aperto.

Se in futuro aggiungiamo dataApertura, docenteResponsabile o note, sappiamo dove guardare.

Un miglioramento possibile, da non fare per forza adesso, è passare anche il generatore di ID:

function creaPrestito(strumentoId, studente, generaId) {
  return {
    id: generaId(),
    strumentoId,
    studente,
    aperto: true,
  };
}

Per ora possiamo restare alla versione semplice. L’importante è capire il confine: creare un prestito non è la stessa responsabilità di leggere un input o salvare dati.

Passo 5: estrarre il salvataggio

Prima:

localStorage.setItem("strumenti", JSON.stringify(strumenti));
localStorage.setItem("prestiti", JSON.stringify(prestiti));

Dopo:

function salvaRegistro() {
  localStorage.setItem("strumenti", JSON.stringify(strumenti));
  localStorage.setItem("prestiti", JSON.stringify(prestiti));
}

Uso:

salvaRegistro();

Questa funzione dipende ancora da variabili globali. Non e definitiva. Ma rende visibile la responsabilita “salvare”.

Potremmo anche renderla un po’ più esplicita:

function salvaRegistro(strumentiDaSalvare, prestitiDaSalvare) {
  localStorage.setItem("strumenti", JSON.stringify(strumentiDaSalvare));
  localStorage.setItem("prestiti", JSON.stringify(prestitiDaSalvare));
}

Uso:

salvaRegistro(strumenti, prestiti);

Questa seconda versione è migliore perché non dipende da due variabili globali nascoste. Non siamo ancora arrivati a un adapter di infrastruttura, ma abbiamo fatto un passo nella direzione giusta.

Funzione dopo i primi passi

Dopo questi passaggi, la funzione puo diventare:

function registraPrestitoFragile() {
  const input = leggiInputPrestito();
  const strumento = trovaStrumentoPerId(strumenti, input.strumentoId);

  if (!strumento) {
    messaggioElement.textContent = "Strumento non trovato";
    return;
  }

  if (!input.studente) {
    messaggioElement.textContent = "Inserisci il nome dello studente";
    return;
  }

  const errorePrestito = motivoNonPrestabile(strumento);

  if (errorePrestito) {
    messaggioElement.textContent = errorePrestito;
    return;
  }

  strumento.stato = "in_prestito";
  prestiti.push(creaPrestito(strumento.id, input.studente));

  salvaRegistro(strumenti, prestiti);
  messaggioElement.textContent = "Prestito registrato";
  mostraStrumenti(strumenti);
}

Non è ancora architettura completa, ma è più leggibile. La funzione principale ora racconta il flusso:

leggi input
cerca strumento
controlla errori
modifica dati
salva
mostra esito

Questo è un buon risultato per una lezione di refactoring iniziale.

Che cosa e migliorato

Ora puoi vedere nomi utili:

  • leggiInputPrestito;
  • trovaStrumentoPerId;
  • motivoNonPrestabile;
  • creaPrestito;
  • salvaRegistro.

La funzione principale racconta meglio il flusso.

Il refactoring ha trasformato righe anonime in responsabilita nominate.

In più, alcune parti sono più facili da spostare in futuro:

  • trovaStrumentoPerId può finire nel dominio o in un servizio di ricerca;
  • motivoNonPrestabile può diventare una vera regola di dominio;
  • creaPrestito può diventare una funzione del dominio;
  • salvaRegistro può diventare un adapter di infrastruttura;
  • leggiInputPrestito può restare nella Presentation.

Non spostiamo ancora tutto. Ma i pezzi ora hanno un nome, quindi possiamo ragionarci.

Che cosa resta fragile

Restano problemi:

  • messaggi utente ancora mescolati a regole;
  • salvaRegistro usa variabili globali;
  • localStorage e ancora nello stesso file;
  • strumento.stato = "in_prestito" e una modifica diretta;
  • non abbiamo ancora test automatici;
  • non abbiamo ancora separato livelli.

Questo e normale. Un buon refactoring non deve risolvere tutto in un colpo.

Questa è una frase importante: refactoring non significa “far sparire ogni problema”. Significa migliorare il codice mantenendo controllo sul comportamento.

Errori tipici durante questo refactoring

Estrarre funzioni con nomi vaghi

Evita nomi come:

function gestisciCose() {}
function controlla() {}
function faiPrestito() {}

Sono nomi troppo generici. Non aiutano a capire la responsabilità.

Meglio:

function leggiInputPrestito() {}
function trovaStrumentoPerId(strumenti, id) {}
function creaPrestito(strumentoId, studente) {}

Un buon nome deve far capire perché la funzione esiste.

Cambiare comportamento mentre stai refactorando

Durante un refactoring non dovresti aggiungere nuove funzionalità. Per esempio, se stai estraendo creaPrestito, non aggiungere nello stesso momento la data di apertura, la categoria dello strumento o il docente responsabile.

Prima sistemi la struttura. Poi, in un passaggio separato, aggiungi comportamento.

Estrarre una funzione che dipende ancora da tutto

Questo è un refactoring solo apparente:

function controllaPrestito() {
  const strumento = strumenti.find((item) => item.id === inputStrumentoId.value);

  if (strumento.stato === "guasto") {
    messaggioElement.textContent = "Non puoi prestarlo";
  }
}

La funzione ha un nome nuovo, ma dipende ancora da variabili globali, input HTML e messaggio. Non è molto meglio di prima.

Una direzione più utile è passare i dati necessari:

function motivoNonPrestabile(strumento) {
  if (strumento.stato === "guasto") {
    return "guasto";
  }

  if (strumento.stato === "in_prestito") {
    return "gia_in_prestito";
  }

  return null;
}

Questa versione non mostra messaggi e non legge il DOM. È più vicina a una regola.

Attivita guidata

Applica i passi in ordine:

  1. estrai leggiInputPrestito;
  2. verifica;
  3. estrai trovaStrumentoPerId;
  4. verifica;
  5. estrai motivoNonPrestabile;
  6. verifica;
  7. estrai creaPrestito;
  8. verifica;
  9. estrai salvaRegistro;
  10. verifica.

Dopo ogni passo, prova almeno un prestito valido e un prestito non valido.

Usa questa tabella di controllo:

| Passo | Modifica fatta | Prestito valido | Strumento guasto | Strumento inesistente | Note |
| --- | --- | --- | --- | --- | --- |
| 0 | codice iniziale | ok | ok | ok | baseline |
| 1 | leggiInputPrestito |  |  |  |  |
| 2 | trovaStrumentoPerId |  |  |  |  |
| 3 | motivoNonPrestabile |  |  |  |  |
| 4 | creaPrestito |  |  |  |  |
| 5 | salvaRegistro |  |  |  |  |

Scrivi ok solo dopo aver provato davvero il caso.

Variante per studenti più rapidi

Se hai completato i passi principali, prova a rendere più esplicita la modifica dello stato:

Prima:

strumento.stato = "in_prestito";

Dopo:

function segnaStrumentoInPrestito(strumento) {
  return {
    ...strumento,
    stato: "in_prestito",
  };
}

Questa versione non modifica direttamente l’oggetto ricevuto, ma restituisce una copia aggiornata. Non è obbligatoria in questa fase. Serve però a vedere una scelta diversa: invece di cambiare un oggetto esistente, posso produrne uno nuovo.

Se usi questa variante, devi anche aggiornare l’array:

const strumentoAggiornato = segnaStrumentoInPrestito(strumento);

strumenti = strumenti.map((item) => {
  if (item.id === strumentoAggiornato.id) {
    return strumentoAggiornato;
  }

  return item;
});

Questa strada è più pulita in alcuni contesti, ma richiede più attenzione. Se la classe è all’inizio, resta pure alla modifica diretta dello stato e concentrati sulle responsabilità.

Micro-sfida

Scegli una seconda funzione lunga del Registro fragile. Può essere:

  • registrazione di uno strumento;
  • cambio stato;
  • ricerca con filtri;
  • apertura segnalazione di guasto;
  • chiusura prestito.

Obiettivo:

  • fare solo il primo passo di refactoring.

Consegna:

  • individua le responsabilità presenti nella funzione;
  • estrai una sola funzione con nome chiaro;
  • verifica che il comportamento sia invariato;
  • scrivi due righe su cosa è migliorato.

Vincoli:

  • non cambiare la funzionalità;
  • non introdurre nuove librerie;
  • non spostare file;
  • non fare più di una estrazione nella micro-sfida.

Risultato atteso:

Funzione scelta: cambiaStatoStrumento
Responsabilità estratta: lettura dello stato selezionato dalla UI
Nuova funzione: leggiInputCambioStato
Verifica: cambio disponibile -> guasto ancora funzionante
Nota: la funzione principale è più leggibile, ma le regole sono ancora nella UI

Prodotto da ottenere

Alla fine della lezione devi avere:

  • la funzione registraPrestitoFragile refactorata in più funzioni piccole;
  • una tabella di verifica compilata;
  • almeno una nota su ciò che resta fragile;
  • una seconda funzione analizzata per la micro-sfida;
  • il programma ancora funzionante.

Non serve che il codice sia perfetto. Serve che il miglioramento sia comprensibile e verificato.

Domande di comprensione

Rispondi per iscritto:

  • Perché una funzione lunga è più rischiosa da modificare?
  • Qual è la differenza tra estrarre una funzione e cambiare comportamento?
  • Quale funzione estratta è ancora legata alla UI?
  • Quale funzione estratta è più facile da testare senza browser?
  • Perché è utile verificare dopo ogni passo?
  • Che cosa resta ancora da separare dopo questa lezione?

Una buona risposta non ripete definizioni. Usa il caso del Registro: prestito, strumento, stato, messaggio, localStorage.

Controllo finale

La lezione è completata quando puoi dire sì a queste affermazioni:

  • ho refactorato a passi piccoli;
  • dopo ogni passo il programma funzionava;
  • ho estratto almeno tre funzioni con nome chiaro;
  • so dire che cosa è migliorato;
  • so dire che cosa resta fragile;
  • non ho riscritto tutto da zero.

Nel prossimo passo separeremo in modo più esplicito lettura input, decisione, salvataggio e messaggio.