modulo 04 di 24 / Ore 0-20

Moduli 1-8 / Checkpoint 20 ore

Responsabilità: una funzione, un motivo per cambiare

Capire il concetto di responsabilità osservando funzioni del Registro del laboratorio che cambiano per motivi diversi e diventano difficili da mantenere.

25 min Architettura del software

Responsabilità: una funzione, un motivo per cambiare

Nel modulo precedente hai imparato a vedere i confini.

Ora iniziamo a lavorare su un concetto più operativo: la responsabilità.

Una responsabilità è un compito preciso assegnato a una parte del codice.

Una funzione con una responsabilità chiara è più facile da leggere, testare e modificare.

Una funzione con troppe responsabilità diventa fragile: ogni cambiamento rischia di rompere qualcosa che sembrava non c’entrare.

Obiettivo della lezione

Alla fine devi saper:

  • spiegare cosa significa responsabilità nel codice;
  • riconoscere quando una funzione ha troppi motivi per cambiare;
  • distinguere una funzione utile da una funzione contenitore;
  • compilare una diagnosi di responsabilità su un blocco del Registro;
  • scegliere una responsabilità candidata alla separazione.

In questa lezione non devi ancora fare un refactoring importante. Devi imparare a diagnosticare.

La regola pratica

Useremo questa frase:

una funzione dovrebbe avere un motivo principale per cambiare.

Non significa che una funzione debba avere sempre una sola riga.

Non significa creare decine di funzioni minuscole senza senso.

Significa che, quando leggi una funzione, dovresti riuscire a dire:

Questa funzione serve principalmente a fare questa cosa.

Se invece devi dire:

Questa funzione legge input, controlla regole, modifica dati, salva in localStorage,
mostra messaggi e ridisegna la pagina.

allora la funzione contiene troppe responsabilità.

Esempio fragile

Nel Registro fragile potresti avere una funzione come questa:

function registraPrestitoFragile() {
  const id = inputStrumentoId.value;
  const studente = inputStudente.value;
  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 può essere prestato";
    return;
  }

  if (strumento.stato === "in_prestito") {
    messaggioElement.textContent = "Lo strumento è già 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 non è lunga per caso.

È lunga perché contiene molti motivi per cambiare.

Trova i motivi per cambiare

Una buona diagnosi parte da questa domanda:

Per quali motivi dovrei modificare questa funzione?

Nel nostro esempio:

Motivo per cambiareRiga o area coinvoltaTipo di responsabilità
cambia il nome del campo HTMLinputStrumentoId.valueUI
cambia la validazione del nome studenteif (!studente)input / application
cambia la regola sugli strumenti guastistrumento.stato === "guasto"domain
cambia la regola sugli strumenti già in prestitostrumento.stato === "in_prestito"domain
cambia il formato del prestitooggetto dentro prestiti.pushapplication / domain
cambia il salvataggiolocalStorage.setIteminfrastructure
cambia il messaggio utentemessaggioElement.textContentUI
cambia il modo di renderizzare la listamostraStrumentiUI

Sono troppi motivi per una sola funzione.

Questa tabella è più utile di un commento generico tipo “la funzione è confusa”.

Ti dice perché è confusa.

Responsabilità visibili

Possiamo riassumere così:

registraPrestitoFragile
  - legge input dalla UI
  - valida input
  - cerca uno strumento
  - applica regole di dominio
  - modifica stato
  - crea un prestito
  - salva strumenti
  - salva prestiti
  - mostra messaggio
  - aggiorna lista

Questa lista non è ancora una soluzione.

È una diagnosi.

Prima di separare il codice, devi sapere cosa stai separando.

Una funzione con responsabilità chiara

Confronta con questa:

function puoEsserePrestato(strumento) {
  return strumento.stato === "disponibile";
}

Il motivo per cambiare è chiaro:

cambia se cambia la regola che stabilisce quando uno strumento può essere prestato.

Non dovrebbe cambiare se:

  • cambia il testo del bottone;
  • cambia il layout della lista;
  • cambiano le chiavi di localStorage;
  • cambia il modo di mostrare gli errori;
  • cambia il nome dell’input HTML.

Questo è un buon segnale.

La funzione appartiene al dominio perché parla di una regola del laboratorio, non della pagina o del salvataggio.

Una funzione troppo piccola può essere inutile

Non tutte le estrazioni sono utili.

Questa funzione è chiara, ma forse non serve:

function strumentoEsiste(strumento) {
  return Boolean(strumento);
}

Se viene usata una sola volta e non rende il codice più leggibile, può essere eccessiva.

L’obiettivo non è creare funzioni per ogni riga.

L’obiettivo è dare un posto chiaro alle responsabilità importanti.

Le funzioni contenitore

Attenzione alle funzioni che hanno un nome generico e nascondono il problema:

function gestisciPrestito() {
  const id = inputStrumentoId.value;
  const studente = inputStudente.value;
  const strumento = strumenti.find((item) => item.id === id);
  // poi continua a fare tutto...
}

Il nome sembra migliore di clickHandler, ma la responsabilità non è davvero separata.

Una funzione utile ha un nome che dice cosa fa:

  • leggiInputPrestito;
  • trovaStrumentoPerId;
  • puoEsserePrestato;
  • creaPrestito;
  • salvaRegistro;
  • mostraEsitoPrestito.

Non tutti questi nomi devono esistere subito.

Servono come strumenti per pensare.

Prima separazione mentale

Prima ancora di spostare codice, prova a riscrivere il flusso come elenco:

1. leggi input del prestito
2. trova lo strumento
3. valida lo studente
4. verifica se lo strumento può essere prestato
5. crea il prestito
6. salva le modifiche
7. mostra l'esito

Ogni riga può diventare una responsabilità.

Non sempre diventerà una funzione separata, ma deve essere riconoscibile.

Attività guidata

Scegli una funzione o un event listener del tuo Registro fragile.

Compila questa scheda:

## Analisi responsabilità

Nome blocco:

Che cosa fa oggi:

Motivi per cambiare:

- ...
- ...
- ...

Responsabilità presenti:

- ...
- ...
- ...

Responsabilità che separerei per prima:

Motivo:

Comportamento da non rompere:

Scrivi almeno sei motivi per cambiare.

Usa righe concrete del codice, non descrizioni vaghe.

Micro-sfida

Trova una funzione che sembra innocua ma contiene almeno tre responsabilità.

Vincoli:

  • non modificarla;
  • non rinominarla;
  • scrivi solo la diagnosi;
  • cita almeno tre righe o blocchi concreti;
  • indica quale responsabilità separeresti per prima.

Risultato atteso: una scheda che spiega perché quella funzione ha più di un motivo per cambiare.

Prodotto da ottenere

Alla fine della lezione devi avere:

  • una funzione o un event listener analizzato;
  • almeno sei motivi per cambiare;
  • una lista delle responsabilità presenti;
  • una responsabilità prioritaria da separare;
  • un comportamento da proteggere prima del refactoring.

Esempio:

Comportamento da proteggere:
se provo a prestare uno strumento già in prestito, il sistema non deve creare
un secondo prestito aperto.

Domande di comprensione

Rispondi per iscritto:

  • Che cosa significa “motivo per cambiare”?
  • Perché una funzione lunga non è sempre sbagliata?
  • Quando una funzione breve può essere inutile?
  • Quale responsabilità è più pericoloso lasciare dentro la UI?
  • Quale comportamento del tuo Registro devi proteggere prima di separare codice?

Controllo finale

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

  • so spiegare che cosa significa responsabilità;
  • so riconoscere più motivi per cambiare in una funzione;
  • ho analizzato almeno un blocco del Registro fragile;
  • so distinguere una funzione con nome utile da una funzione contenitore;
  • ho capito che prima si diagnostica e poi si refactora;
  • so indicare almeno una responsabilità da separare nei prossimi passi.

Nel prossimo passo parleremo di coesione: non basta avere funzioni separate, bisogna che le parti di una funzione o di un file lavorino per lo stesso scopo.