modulo 22 di 24 / Ore 41-60
Moduli 17-24 / Checkpoint 60 ore
Ridurre condizioni duplicate sugli stati
Eliminare la duplicazione avanzata nei controlli sugli stati del Registro: validare le transizioni di stato, centralizzare la logica di transizione nel dominio e proteggere gli invarianti.
Ridurre condizioni duplicate sugli stati
Questa lezione appartiene al Modulo 22 — Refactoring avanzato e varianti.
Da predicati semplici a transizioni di stato
La lezione precedente (Modulo 14) ha centralizzato i controlli semplici (isDisponibile, puoEsserePrestato). Questa lezione affronta il livello successivo: le transizioni di stato.
Una transizione di stato è un cambiamento da un valore a un altro: da "disponibile" a "in_prestito", da "in_prestito" a "disponibile". Non tutte le transizioni sono valide — uno strumento "guasto" non può andare direttamente a "disponibile" senza passare da "in_manutenzione".
Se questa logica di transizione è dispersa nel codice, ogni cambio di regola richiede di cercare e aggiornare più file.
Mappare le transizioni valide
Prima di centralizzare, bisogna capire quali transizioni sono permesse nel laboratorio:
Transizioni valide:
disponibile → in_prestito (apriPrestito)
in_prestito → disponibile (chiudiPrestito)
disponibile → guasto (segnalaGuasto)
disponibile → in_manutenzione (portaInManutenzione)
guasto → in_manutenzione (portaInManutenzione)
in_manutenzione → disponibile (completaManutenzione)
Transizioni NON valide:
guasto → disponibile (deve passare da in_manutenzione)
guasto → in_prestito (non prestabile)
in_prestito → guasto (non si può segnalare guasto se in prestito)
in_manutenzione → in_prestito (non prestabile)
Centralizzare le transizioni nel dominio
// domain/strumento.js — tutte le transizioni in un posto
const TRANSIZIONI_VALIDE = {
"disponibile": ["in_prestito", "guasto", "in_manutenzione"],
"in_prestito": ["disponibile"],
"guasto": ["in_manutenzione"],
"in_manutenzione": ["disponibile"]
};
function isTransizioneValida(daStato, aStato) {
const permesse = TRANSIZIONI_VALIDE[daStato] ?? [];
return permesse.includes(aStato);
}
function transizionaStato(strumento, nuovoStato) {
if (!isTransizioneValida(strumento.stato, nuovoStato)) {
throw new Error(`TRANSIZIONE_NON_VALIDA`);
}
return { ...strumento, stato: nuovoStato };
}
Ora ogni caso d’uso usa transizionaStato invece di impostare stato direttamente:
// application/registraPrestito.js
async function registraPrestito(comando) {
const strumento = await archivioStrumenti.trovaPerId(comando.strumentoId);
if (!strumento) throw new Error("STRUMENTO_NON_TROVATO");
const strumentoAggiornato = transizionaStato(strumento, "in_prestito");
// ← valida la transizione e lancia se non permessa
const prestito = apriPrestito(strumento.id, new Date().toISOString());
await archivioPrestiti.salva({ ...prestito, assegnatario: comando.assegnatario });
await archivioStrumenti.salva(strumentoAggiornato);
}
Il vantaggio: aggiungere un nuovo stato
Se si aggiunge lo stato "dismesso":
// Prima: cerca in tutta la codebase dove aggiornare le condizioni
// → registraPrestito, chiudiPrestito, segnalaGuasto, renderUI, ...
// Dopo: aggiorna solo TRANSIZIONI_VALIDE
const TRANSIZIONI_VALIDE = {
"disponibile": ["in_prestito", "guasto", "in_manutenzione", "dismesso"],
"in_prestito": ["disponibile"],
"guasto": ["in_manutenzione", "dismesso"],
"in_manutenzione": ["disponibile", "dismesso"],
"dismesso": [] // nessuna transizione possibile — stato finale
};
Un solo punto da aggiornare. Tutti i casi d’uso ereditano automaticamente le nuove regole.
Come trovare le transizioni duplicate nel codice
Cerca nel codice tutti i punti dove lo stato viene impostato direttamente:
grep -rn "stato.*=.*\"" src/
grep -rn "stato:.*\"" src/
Per ogni occorrenza fuori da domain/strumento.js: questa assegnazione dovrebbe passare da transizionaStato?
Confronto: prima e dopo
| Scenario | Prima (disperso) | Dopo (centralizzato) |
|---|---|---|
Aggiungere stato "dismesso" | Modifica N file | Modifica solo TRANSIZIONI_VALIDE |
Vietare transizione guasto→disponibile | Cerca in tutta la codebase | Rimuove dalla mappa |
| Testare le regole di transizione | Richiede mock del caso d’uso | Testa transizionaStato in isolamento |
| Capire quali transizioni sono valide | Legge N file diversi | Legge TRANSIZIONI_VALIDE |
Test delle transizioni
test("transizioni valide", () => {
const disponibile = { id: "s1", stato: "disponibile" };
const inPrestito = transizionaStato(disponibile, "in_prestito");
expect(inPrestito.stato).toBe("in_prestito");
const guasto = { id: "s2", stato: "guasto" };
expect(() => transizionaStato(guasto, "in_prestito")).toThrow("TRANSIZIONE_NON_VALIDA");
expect(() => transizionaStato(guasto, "disponibile")).toThrow("TRANSIZIONE_NON_VALIDA");
});
Procedura guidata
- Disegna le transizioni di stato valide nel tuo laboratorio.
- Implementa
TRANSIZIONI_VALIDEetransizionaStatoindomain/strumento.js. - Scrivi test per almeno: una transizione valida, due transizioni non valide.
- Sostituisci le assegnazioni dirette di stato con chiamate a
transizionaStato. - Verifica che il comportamento del Registro sia identico.
Attività in classe
## Centralizzazione delle transizioni di stato
Transizioni valide identificate nel mio laboratorio:
- disponibile → [lista]
- in_prestito → [lista]
- guasto → [lista]
- in_manutenzione → [lista]
TRANSIZIONI_VALIDE implementato (sì/no):
Test scritti:
- transizione valida:
- transizione non valida 1:
- transizione non valida 2:
Occorrenze di stato assegnato direttamente fuori da domain/:
- [file:riga] → sostituito (sì/no)
Verifica comportamento identico (sì/no):
Spiegazione guidata per studiare
La centralizzazione delle transizioni di stato è il refactoring avanzato più utile per la manutenibilità a lungo termine. Non cambia il comportamento — cambia chi “conosce” le regole di transizione. Prima le conosce tutta la codebase. Dopo le conosce solo il dominio.
Il punto centrale è: la mappa delle transizioni è una regola del laboratorio. “Uno strumento guasto non può andare direttamente a disponibile” è una regola scolastica, non tecnica. Deve stare nel dominio.
Il rischio tipico è non aggiornare tutte le occorrenze. La grep sistematica prima di iniziare previene questo.
Passo per passo nel progetto
- Esegui grep per le assegnazioni di stato in tutto il progetto.
- Crea
TRANSIZIONI_VALIDEbasandoti sulle regole del laboratorio. - Implementa
transizionaStatocon validazione. - Scrivi test per le transizioni più critiche.
- Sostituisci le assegnazioni dirette una alla volta con verifica intermedia.
Esempio da leggere lentamente
// Prima — transizioni sparse senza validazione
// registraPrestito.js: strumento.stato = "in_prestito"
// chiudiPrestito.js: strumento.stato = "disponibile"
// segnalaGuasto.js: strumento.stato = "guasto"
// → nessuna regola impedisce guasto → disponibile direttamente
// Dopo — transizioni centralizzate con validazione
// Tutte usano: transizionaStato(strumento, nuovoStato)
// → TRANSIZIONI_VALIDE impedisce guasto → disponibile automaticamente
Prova di comprensione
- Quante transizioni valide ha il tuo Registro? Elencarle tutte.
- Quale transizione non valida potrebbe causare più problemi se fosse possibile?
- Dopo il refactoring, aggiungere lo stato
"dismesso"richiede di modificare quanti file?
Errori da evitare
- implementare
transizionaStatoma lasciare alcune assegnazioni dirette; - mettere
TRANSIZIONI_VALIDEin configurazione invece didomain/— è una regola del dominio; - non scrivere test per le transizioni non valide.
Prodotto da consegnare
Può essere:
TRANSIZIONI_VALIDEetransizionaStatoimplementati indomain/strumento.js;- test che coprono almeno tre transizioni valide e tre non valide;
- il diff delle sostituzioni con verifica del comportamento.
Checklist di chiusura
- Ho disegnato le transizioni valide del mio laboratorio
-
TRANSIZIONI_VALIDEcopre tutti gli stati esistenti -
transizionaStatolancia un errore per transizioni non valide - Ho scritto test per transizioni valide e non valide
- Non ci sono assegnazioni dirette di stato fuori da
domain/strumento.js - Il comportamento del Registro è identico prima e dopo
Domande per la revisione
- Quali transizioni non valide hai scoperto durante il mapping?
- I test per le transizioni non valide falliscono prima del refactoring?
- Il codice è più o meno leggibile dopo il refactoring?
Risultato atteso
Alla fine lo studente deve saper dire:
Ho identificato [N] transizioni valide nel mio Registro.
Ho implementato TRANSIZIONI_VALIDE in domain/strumento.js.
Ho scritto test per: [transizioni valide] e [transizioni non valide].
Dopo il refactoring, aggiungere uno stato richiede solo di modificare TRANSIZIONI_VALIDE.
Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.