modulo 14 di 24 / Ore 21-40
Moduli 9-16 / Checkpoint 40 ore
Refactoring: cambiare struttura senza cambiare comportamento
Capire cosa significa refactoring, perché è diverso da riscrivere, e applicarlo al Registro del laboratorio con piccoli passi verificabili.
Refactoring: cambiare struttura senza cambiare comportamento
Questa lezione appartiene al Modulo 14 — Refactoring: migliorare senza ricominciare.
Definizione operativa
Refactoring = cambiare la struttura interna del codice senza cambiare il comportamento osservabile dall’esterno.
Non è refactoring:
- aggiungere una funzionalità nuova
- correggere un bug
- riscrivere tutto da capo perché “il codice fa schifo”
È refactoring:
- estrarre una funzione lunga in funzioni più piccole con nomi precisi
- spostare una regola dal posto sbagliato al posto giusto
- rinominare una variabile o funzione per comunicare meglio l’intenzione
- separare responsabilità mischiate in un solo posto
Esempio concreto nel Registro — prima e dopo
Prima: tutto in un event listener
// presentation/ui.js — PRIMA del refactoring
// Un event listener che fa troppe cose
document.getElementById("btn-presta").addEventListener("click", () => {
// 1. Legge input
const strumentoId = document.getElementById("strumento-id").value.trim();
const assegnatario = document.getElementById("assegnatario").value.trim();
// 2. Valida
if (!strumentoId) { alert("ID strumento obbligatorio"); return; }
// 3. Cerca lo strumento nell'archivio (dipendenza diretta!)
const strumenti = JSON.parse(localStorage.getItem("registro:strumenti") || "[]");
const strumento = strumenti.find(s => s.id === strumentoId);
if (!strumento) { alert("Strumento non trovato"); return; }
// 4. Applica la regola di dominio (nel posto sbagliato!)
if (strumento.stato !== "disponibile") { alert("Non disponibile"); return; }
// 5. Modifica e salva
strumento.stato = "in_prestito";
const i = strumenti.findIndex(s => s.id === strumentoId);
strumenti[i] = strumento;
localStorage.setItem("registro:strumenti", JSON.stringify(strumenti));
alert("Prestito registrato!");
});
Dopo: ogni responsabilità al suo posto
// domain/strumento.js
function puoEsserePrestato(strumento) {
if (strumento.stato !== "disponibile") throw new Error("STRUMENTO_NON_DISPONIBILE");
}
// application/registraPrestito.js
function creaRegistraPrestito({ archivioStrumenti, archivioPrestiti }) {
return async function registraPrestito(comando) {
const strumento = await archivioStrumenti.trovaPerId(comando.strumentoId);
if (!strumento) throw new Error("STRUMENTO_NON_TROVATO");
puoEsserePrestato(strumento);
const prestito = apriPrestito(strumento.id, new Date().toISOString());
await archivioPrestiti.salva({ ...prestito, assegnatario: comando.assegnatario });
await archivioStrumenti.salva({ ...strumento, stato: "in_prestito" });
};
}
// presentation/ui.js
document.getElementById("btn-presta").addEventListener("click", async () => {
const comando = {
strumentoId: document.getElementById("strumento-id").value.trim(),
assegnatario: document.getElementById("assegnatario").value.trim()
};
try {
await registraPrestito(comando);
mostraSuccesso("Prestito registrato.");
} catch (errore) {
mostraErrore(messaggi[errore.message] ?? "Operazione non riuscita.");
}
});
Il comportamento per l’utente è identico. La struttura è completamente diversa — e molto più manutenibile.
I passi del refactoring
Il refactoring non si fa tutto in una volta. Si fa in passi piccoli, ognuno verificabile:
Passo 1: scrivi un test (o uno scenario manuale) che descrive il comportamento attuale
Passo 2: identifica una sola responsabilità da spostare
Passo 3: estrai quella responsabilità in una funzione separata
Passo 4: verifica che il comportamento sia invariato
Passo 5: (opzionale) commit — il progetto funziona, il codice è migliorato
Passo 6: ripeti per la prossima responsabilità
Regola fondamentale: dopo ogni passo, il Registro deve funzionare. Non si accumula debito tecnico “temporaneo” tra un passo e l’altro.
Tre tipi di refactoring nel Registro
Tipo 1: estrazione di funzione
Quando una funzione fa troppo, si estrae una parte in una funzione con nome preciso.
// Prima: logica di filtraggio inline nell'event listener
const risultati = strumenti.filter(s =>
s.nome.toLowerCase().includes(query) &&
(categoria === "" || s.categoria === categoria)
);
// Dopo: funzione estratta con nome che comunica l'intenzione
function filtraStrumenti(strumenti, { query, categoria }) {
return strumenti.filter(s =>
s.nome.toLowerCase().includes(query.toLowerCase()) &&
(categoria === "" || s.categoria === categoria)
);
}
Tipo 2: spostamento di responsabilità
Quando una responsabilità è nel posto sbagliato, si sposta nel livello corretto.
// Prima: regola del dominio nell'event listener
if (strumento.stato === "guasto") { alert("Guasto"); return; }
// Dopo: regola nel dominio
// domain/strumento.js
function puoEsserePrestato(strumento) {
if (strumento.stato !== "disponibile") throw new Error("STRUMENTO_NON_DISPONIBILE");
}
Tipo 3: rendere esplicita una dipendenza
Quando una funzione usa qualcosa di nascosto, si rende visibile nella firma.
// Prima: dipendenza nascosta da localStorage
function salvaStrumento(strumento) {
const lista = JSON.parse(localStorage.getItem("registro:strumenti") || "[]");
// ...
}
// Dopo: dipendenza esplicita — l'archivio viene passato
function salvaStrumento(archivio, strumento) {
const lista = archivio.leggiTutti();
// ...
}
Come verificare che il comportamento non sia cambiato
Tre metodi, in ordine di affidabilità:
- Test automatico — scrivi il test prima del refactoring, eseguilo dopo
- Scenario manuale documentato — esegui esattamente gli stessi passi prima e dopo
- Confronto del diff — leggi le righe cambiate e verifica che nessuna modifica la logica
Il metodo 1 è il più affidabile. Il metodo 3 è il meno affidabile ma spesso sufficiente per refactoring piccoli (es. rinomino).
Procedura guidata
- Scegli un punto del Registro dove il codice fa troppo (evento listener, funzione lunga).
- Scrivi in una frase il comportamento che deve restare invariato dopo il refactoring.
- Esegui una verifica prima di iniziare (test o scenario manuale).
- Estrai una responsabilità — solo una, non tutte insieme.
- Verifica che il comportamento sia invariato.
- Ripeti per la prossima responsabilità.
Attività in classe
## Refactoring eseguito
Punto del codice scelto (file e funzione):
Problema identificato:
Tipo di refactoring: estrazione / spostamento / dipendenza esplicita
Verifica eseguita prima:
Descrizione della modifica:
Verifica eseguita dopo:
Comportamento cambiato (sì/no — deve essere no):
Prossima responsabilità da estrarre (se esiste):
Spiegazione guidata per studiare
Il refactoring è un’abilità disciplinata — non è riscrivere perché non piace il codice. La disciplina sta nei passi: uno alla volta, verifica in mezzo, mai rompere il comportamento osservabile.
Il punto centrale è: dopo un refactoring, l’utente non deve accorgersi di nulla. Se la UI mostra un comportamento diverso, non è un refactoring — è una modifica funzionale. Le due cose vanno separate.
Il rischio tipico è fare refactoring e aggiungere una funzionalità nello stesso passo. Questo rende impossibile capire se un eventuale problema viene dal refactoring o dalla nuova funzionalità. Separali sempre in commit distinti.
Passo per passo nel progetto
- Apri l’event listener del form di registrazione prestito.
- Quante responsabilità ci sono? (Leggi input, valida, chiama archivio, applica regola, salva, mostra messaggio)
- Scegli una sola da estrarre — la più isolata.
- Estrai la funzione. Verifica che il Registro funzioni identicamente.
- Documenta in una nota: “estratto [X] da [Y] — comportamento invariato”.
Esempio da leggere lentamente
// Refactoring in tre passi — ogni passo lascia il programma funzionante
// PASSO 1: estrai la lettura dell'input
function leggiComandoPrestito() {
return {
strumentoId: document.getElementById("strumento-id").value.trim(),
assegnatario: document.getElementById("assegnatario").value.trim()
};
}
// Verifica: il form funziona ancora ✓
// PASSO 2: estrai la gestione dell'errore
function gestisciErrorePrestito(errore) {
const messaggi = {
STRUMENTO_NON_TROVATO: "Strumento non trovato.",
STRUMENTO_NON_DISPONIBILE: "Strumento non disponibile."
};
mostraErrore(messaggi[errore.message] ?? "Operazione non riuscita.");
}
// Verifica: gli errori funzionano ancora ✓
// PASSO 3: l'event listener diventa leggibile
document.getElementById("btn-presta").addEventListener("click", async () => {
const comando = leggiComandoPrestito();
try {
await registraPrestito(comando);
mostraSuccesso("Prestito registrato.");
} catch (errore) {
gestisciErrorePrestito(errore);
}
});
// Verifica: tutto funziona ancora ✓
Tre passi, tre verifiche, nessuna modifica al comportamento.
Prova di comprensione
- Qual è la differenza tra refactoring e riscrittura?
- Perché si fa un passo alla volta invece di cambiare tutto insieme?
- Come verifichi che il comportamento non è cambiato dopo il refactoring?
Errori da evitare
- fare refactoring e aggiungere funzionalità nello stesso passo;
- non verificare dopo ogni passo — il bug si accumula senza saperlo;
- riscrivere una funzione da zero invece di modificarla incrementalmente;
- considerare concluso il refactoring quando “sembra giusto” invece che quando i test passano.
Prodotto da consegnare
Può essere:
- un diff del codice prima e dopo il refactoring, con descrizione della responsabilità estratta;
- la scheda compilata con verifica prima e dopo;
- un commit che contiene solo refactoring (nessuna funzionalità aggiunta).
Checklist di chiusura
- Ho identificato una responsabilità da estrarre
- Ho verificato il comportamento prima di iniziare
- Ho estratto la responsabilità in una funzione con nome preciso
- Ho verificato che il comportamento sia identico dopo
- Il Registro funziona correttamente dalla UI
Domande per la revisione
- Quante responsabilità ha ancora la funzione che hai refactorizzato?
- Il comportamento per l’utente è identico a prima?
- Hai aggiunto funzionalità durante il refactoring? (Se sì, separa in un altro commit.)
Risultato atteso
Alla fine lo studente deve saper dire:
Ho estratto [responsabilità] da [funzione/file].
Prima: [descrizione dello stato originale].
Dopo: [descrizione del nuovo stato].
Il comportamento per l'utente è identico — ho verificato con [metodo].
Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.