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.

30 min Architettura del software

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

ScenarioPrima (disperso)Dopo (centralizzato)
Aggiungere stato "dismesso"Modifica N fileModifica solo TRANSIZIONI_VALIDE
Vietare transizione guasto→disponibileCerca in tutta la codebaseRimuove dalla mappa
Testare le regole di transizioneRichiede mock del caso d’usoTesta transizionaStato in isolamento
Capire quali transizioni sono valideLegge N file diversiLegge 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

  1. Disegna le transizioni di stato valide nel tuo laboratorio.
  2. Implementa TRANSIZIONI_VALIDE e transizionaStato in domain/strumento.js.
  3. Scrivi test per almeno: una transizione valida, due transizioni non valide.
  4. Sostituisci le assegnazioni dirette di stato con chiamate a transizionaStato.
  5. 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

  1. Esegui grep per le assegnazioni di stato in tutto il progetto.
  2. Crea TRANSIZIONI_VALIDE basandoti sulle regole del laboratorio.
  3. Implementa transizionaStato con validazione.
  4. Scrivi test per le transizioni più critiche.
  5. 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 transizionaStato ma lasciare alcune assegnazioni dirette;
  • mettere TRANSIZIONI_VALIDE in configurazione invece di domain/ — è una regola del dominio;
  • non scrivere test per le transizioni non valide.

Prodotto da consegnare

Può essere:

  • TRANSIZIONI_VALIDE e transizionaStato implementati in domain/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_VALIDE copre tutti gli stati esistenti
  • transizionaStato lancia 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.