Nel precedente articolo abbiamo parlato di semplicità.

Abbiamo visto come non tutto debba essere astratto, generalizzato o affidato a una libreria.

A volte la soluzione migliore è semplicemente quella più diretta, ma c’è un’altra domanda che un developer prima o poi deve imparare a porsi:

come faccio a capire se quella soluzione semplice è anche una buona soluzione?

Perché c’è una differenza importante tra scrivere codice che funziona e progettare codice che possa continuare a funzionare bene anche quando il progetto crescerà.

Far funzionare il codice è solo il primo obiettivo

Quando ricevi una nuova funzionalità, la prima cosa che vuoi ottenere è abbastanza ovvia:

deve funzionare.

Se l’utente compila un form, i dati devono essere salvati.

Se clicca un pulsante, deve succedere qualcosa.

Se effettua un pagamento, il sistema deve elaborarlo.

Se fa una richiesta, deve ricevere una risposta.

Questo è il punto di partenza, ma non è necessariamente il punto di arrivo. Perché il software raramente rimane uguale a come lo hai scritto il primo giorno.

Arrivano nuove funzionalità.

Cambiano i requisiti.

Vengono aggiunti casi particolari.

Altri developer iniziano a lavorare sul progetto.

E improvvisamente una soluzione che sembrava perfettamente ragionevole può diventare difficile da modificare.

Il codice funzionava.

Era il modo in cui era organizzato a non funzionare più bene.

Il vero test arriva quando devi modificarlo

C’è un modo molto semplice per capire la qualità di una soluzione:

prova a cambiarla.

Non limitarti a chiederti se il codice funziona oggi.

Chiediti cosa succede quando arriva una nuova richiesta.

Supponiamo di avere una funzionalità che gestisce gli ordini.

All’inizio esistono solo ordini standard.

Scrivi una soluzione semplice.

Funziona perfettamente.

Dopo qualche mese arriva una nuova richiesta:

“Dobbiamo gestire anche gli ordini express.”

Modifichi il codice.

Ancora tutto funziona.

Poi arriva un terzo tipo di ordine.

Poi una regola diversa per alcuni clienti.

Poi un nuovo metodo di spedizione.

Ogni modifica richiede di aggiungere nuove condizioni nello stesso punto.

La soluzione iniziale era semplice, ma non era stata progettata in modo da accogliere facilmente il cambiamento.

Ed è qui che emerge la differenza.

Progettare significa pensare alle responsabilità

Una parte importante della progettazione consiste nel decidere chi deve fare cosa.

Non significa necessariamente creare decine di classi o seguire un pattern.

Significa evitare che una singola parte del codice diventi responsabile di tutto.

Immagina una funzione che:

  1. Riceve i dati di un ordine
  2. Valida i dati
  3. Calcola il prezzo
  4. Applica uno sconto
  5. Salva l’ordine
  6. Invia una mail
  7. Registra un evento

Potrebbe funzionare perfettamente, ma cosa succede quando cambia la logica dello sconto? O quando vuoi cambiare il sistema che invia le email? O quando vuoi salvare gli ordini in modo diverso?

Se tutte queste responsabilità sono mescolate, ogni modifica rischia di propagarsi in più punti.

Il problema non è che la funzione sia lunga, ma che contiene decisioni che appartengono a responsabilità diverse.

Separare non significa complicare

Qui c’è un’importante distinzione rispetto all’articolo precedente.

Abbiamo detto che non bisogna astrarre tutto. Questo rimane vero, ma evitare astrazioni inutili non significa mettere tutto nello stesso posto. Separare le responsabilità può rendere il codice più semplice, non più complesso.

Il punto è trovare il livello giusto.

Se hai due righe di codice utilizzate una sola volta, probabilmente non serve creare un nuovo livello di astrazione. Se invece hai una parte del sistema che cambia per motivi completamente diversi rispetto al resto, separarla può essere una buona scelta.

La domanda non dovrebbe essere:

“Posso creare un’astrazione?”

Ma:

“Questa parte ha una responsabilità sufficientemente distinta da meritare di essere separata?”

Una buona progettazione non prevede il futuro

Questo è un altro punto importante.

Progettare bene non significa cercare di prevedere ogni possibile cambiamento, anzi questa cosa può diventare facilmente un problema. Se pensi a tutte le funzionalità che potrebbero arrivare, finirai per progettare un sistema enorme per problemi che forse non esisteranno mai.

E torniamo esattamente al problema dell’articolo precedente.

La progettazione non consiste nel costruire tutto in anticipo, ma nel creare spazio per il cambiamento senza introdurre complessità inutile.

È una differenza sottile, ma fondamentale.

Non devi sapere cosa cambierà. Devi evitare di rendere inutilmente difficile cambiare.

Il costo del cambiamento

Questo è forse il modo più utile per ragionare sulla progettazione.

Ogni decisione che prendi nel codice ha un costo futuro.

Una struttura ben progettata rende alcune modifiche economiche, mentre una struttura progettata male può rendere costosa anche una modifica banale.

Immaginiamo di dover cambiare il sistema di autenticazione.

Se tutta l’applicazione dipende direttamente da quel sistema, potresti dover modificare decine di punti. Se invece le responsabilità sono organizzate correttamente, il cambiamento potrebbe essere confinato a una parte specifica del sistema. Non hai eliminato la complessità. Hai controllato dove si trova. Ed è una delle caratteristiche più importanti di una buona progettazione.

Il codice deve avere dei confini

Un sistema ben progettato non è necessariamente quello con più livelli.

È quello in cui è abbastanza chiaro dove finisce una responsabilità e dove ne inizia un’altra.

Un modulo dovrebbe sapere ciò che gli serve sapere. Non dovrebbe conoscere inutilmente i dettagli interni di tutto il resto dell’applicazione. Più una parte del sistema dipende dai dettagli delle altre parti, più diventa difficile modificarla senza conseguenze. Al contrario, quando i confini sono chiari, puoi cambiare una parte senza dover riscrivere tutto.

Questo non significa eliminare completamente le dipendenze, ma renderle consapevoli e controllabili.

La semplicità rimane importante

A questo punto potrebbe sembrare che stiamo tornando indietro.

Prima abbiamo detto:

“Non creare astrazioni inutili.”

Ora stiamo dicendo:

“Separa le responsabilità.”

Non c’è contraddizione.

Il vero obiettivo non è avere meno codice possibile, ma avere meno complessità necessaria possibile.

A volte questo significa eliminare un’astrazione e a volte significa introdurne una.

A volte significa lasciare una funzione esattamente com’è e a volte significa dividerla.

La decisione dipende dal problema. Ed è proprio qui che entra in gioco la progettazione.

Il developer non deve indovinare la soluzione perfetta

Un altro errore comune è pensare che progettare bene significhi trovare subito l’architettura perfetta.

Non esiste.

Quando inizi un progetto hai informazioni limitate.

Non sai ancora quali parti cambieranno maggiormente.

Non sai quali funzionalità diventeranno centrali.

Non sai quali problemi emergeranno.

Per questo una buona progettazione deve poter evolvere insieme al software.

Puoi iniziare semplice.

Poi, quando emerge un vero bisogno, puoi separare una responsabilità, introdurre un’astrazione o cambiare una parte dell’architettura.

La progettazione non è un’attività che fai una volta sola prima di scrivere codice.

È un processo continuo.

Il vero salto di qualità

All’inizio della carriera la domanda principale è:

“Come faccio a far funzionare questa cosa?”

Poi, con l’esperienza, arriva una domanda più interessante:

“Come faccio a farla funzionare senza rendere più difficile il prossimo cambiamento?”

Questa è una delle differenze più importanti tra scrivere codice e progettare software.

Non significa complicare tutto pensando al futuro.

Non significa utilizzare un design pattern per ogni problema.

Non significa costruire un’architettura enorme per un’applicazione piccola.

Significa semplicemente essere consapevoli del fatto che il codice che scrivi oggi diventerà il contesto in cui dovrai lavorare domani. E quando devi scegliere tra due soluzioni entrambe funzionanti, vale la pena chiedersi:

“Quale delle due renderà più semplice il prossimo cambiamento?”

Non sempre avrai la risposta giusta.

Ma iniziare a farti questa domanda significa già aver fatto un passo avanti.

Perché a un certo punto della carriera smetti di programmare soltanto per risolvere il problema di oggi.

Inizi a progettare pensando a come il software continuerà a vivere domani.