Quando inizi a programmare, è facile pensare che un buon developer sia quello capace di scrivere codice sofisticato.
Più astrazioni.
Più pattern.
Più livelli.
Più configurazioni.
Più codice.
Con l’esperienza, però, inizi a capire qualcosa di diverso:
molto spesso il codice migliore è quello che fa il suo lavoro nel modo più semplice possibile.
Non perché sia più elegante o perché abbia meno righe, ma perché ha meno cose che possono andare storte.
Semplice non significa banale
Quando parliamo di codice semplice, non intendiamo codice scritto male o eccessivamente semplificato. Un codice può essere semplice da leggere e allo stesso tempo risolvere un problema complesso. La semplicità riguarda soprattutto quanto è facile capire cosa sta succedendo.
Immagina una funzione che deve recuperare un utente.
Puoi avere una funzione che fa esattamente quello:
const user = await getUser(id);
Oppure puoi costruire cinque livelli di astrazione, tre servizi e una factory soltanto per arrivare allo stesso risultato. Entrambe le soluzioni possono funzionare, ma la seconda introduce più elementi da comprendere. E ogni elemento aggiuntivo ha un costo.
Ogni riga di codice è una responsabilità
Quando aggiungiamo codice a un progetto, non stiamo aggiungendo soltanto funzionalità.
Stiamo aggiungendo qualcosa che dovremo:
- Leggere
- Capire
- Testare
- Mantenere
- Modificare
- Eventualmente correggere
Questo significa che il costo del codice non termina quando viene scritto, anzi, spesso è proprio lì che inizia. Una soluzione apparentemente sofisticata può richiedere dieci minuti per essere implementata e ore per essere capita da chi dovrà modificarla qualche mese dopo.
La semplicità riduce questo costo.
La complessità arriva molto facilmente
Il problema è che complicare il codice è facilissimo.
Basta aggiungere un’astrazione “per il futuro”.
Una configurazione “per quando servirà”.
Un pattern “per rendere il codice più scalabile”.
Una funzione generica che dovrebbe funzionare in dieci casi diversi, quando in realtà oggi ne serve soltanto uno.
Singolarmente sembrano tutte decisioni ragionevoli.
Il problema è che si sommano. E alla fine ti ritrovi con un sistema che funziona, ma che richiede molta più energia del necessario per essere compreso.
Il futuro non è una buona ragione per complicare il presente
Uno degli errori più comuni è progettare codice pensando continuamente a problemi che forse arriveranno.
“Un giorno potremmo avere più implementazioni.”
“Potremmo dover supportare altri tipi di dati.”
“Potremmo cambiare questo componente.”
“Potremmo avere milioni di utenti.”
A volte queste preoccupazioni sono fondate, altre volte invece stiamo semplicemente costruendo una soluzione per un problema che ancora non esiste.
Il risultato è codice più astratto, più generico e più difficile da modificare.
Una buona regola è:
non aggiungere complessità per problemi ipotetici quando non hai ancora bisogno di risolverli.
Quando quel problema arriverà, avrai più informazioni per progettare una soluzione realmente adatta.
La semplicità richiede esperienza
Ed ecco il paradosso.
Il codice semplice sembra facile da scrivere.
In realtà, spesso è più difficile.
Scrivere una soluzione complicata è relativamente semplice: puoi continuare ad aggiungere livelli fino a quando il problema sembra risolto.
Trovare la soluzione più semplice che soddisfa davvero i requisiti richiede invece di capire bene il problema.
Devi sapere cosa può essere eliminato.
Devi sapere cosa è necessario.
Devi distinguere una vera necessità da una possibilità futura.
Devi conoscere abbastanza alternative da poter scegliere.
Per questo la semplicità è spesso il risultato dell’esperienza. Non perché il developer esperto scriva meno codice per principio, ma perché sa riconoscere quale codice non serve.
Non tutto deve essere astratto
L’astrazione è uno degli strumenti più potenti della programmazione, ma proprio per questo può essere utilizzata troppo.
Immagina di avere due funzioni molto simili.
È naturale pensare:
“Sono quasi uguali. Devo creare una funzione generica.”
Forse sì.
Ma non necessariamente.
Se l’astrazione rende più difficile capire il comportamento delle due funzioni, potrebbe essere peggiore della duplicazione che stavi cercando di eliminare.
Una piccola duplicazione può essere economica, un’astrazione sbagliata può diventare un vincolo architetturale.
Quindi non chiederti soltanto:
“Posso riutilizzare questo codice?”
Chiediti anche:
“Questa astrazione rende il codice più facile o più difficile da capire?”
Il codice deve essere comprensibile prima che elegante
Un altro errore comune è cercare di scrivere codice “bello”.
Il problema è che la bellezza del codice è soggettiva, la comprensibilità molto meno.
Se un altro developer può aprire una funzione e capire rapidamente:
- Cosa riceve
- Cosa fa
- Cosa restituisce
- Cosa può andare storto
hai già ottenuto qualcosa di estremamente importante.
Non serve che il codice sia impressionante.
Deve essere prevedibile.
Un buon codice non dovrebbe costringerti a indovinare cosa succederà.
La complessità ha un costo
Ogni volta che introduciamo complessità, dovremmo avere una ragione, perché quella complessità comporta un costo.
Più codice significa più possibilità di errore.
Più astrazioni significano più livelli da attraversare.
Più dipendenze significano più cose da mantenere.
Più configurazione significa più possibilità di configurazione errata.
Più comportamenti impliciti significano più difficoltà nel capire cosa succede.
Questo non significa eliminare tutta la complessità. Alcuni problemi sono realmente complessi. Significa invece non aggiungere complessità dove il problema non la richiede.
Semplice non significa “sempre la soluzione più corta”
Questo punto è importante.
Una funzione di cinquanta righe non è necessariamente peggiore di una funzione di dieci.
Un’architettura con più componenti non è necessariamente troppo complessa.
Una soluzione più lunga può essere più semplice da comprendere se separa correttamente responsabilità diverse.
La semplicità non si misura con il numero di righe.
Si misura anche con il numero di concetti che devi tenere a mente per capire il codice.
A volte dieci righe possono essere più complesse di cinquanta.
Per questo bisogna stare attenti alla tentazione di trasformare “codice semplice” in “codice corto”.
Non sono la stessa cosa.
Il codice semplice è più facile da cambiare
Il vero vantaggio della semplicità emerge soprattutto nel tempo.
Oggi scrivi una funzionalità, tra sei mesi qualcuno deve modificarla, se il codice è semplice, quella persona può entrare nel progetto, capire cosa succede e intervenire, se il codice è pieno di dipendenze, astrazioni e comportamenti nascosti, anche una modifica banale può diventare rischiosa.
Ed è qui che la semplicità diventa una caratteristica tecnica, non soltanto estetica.
Il codice semplice costa meno da mantenere.
E il mantenimento rappresenta una parte enorme della vita di un software.
Prima risolvi il problema. Poi ottimizza la soluzione
C’è anche un altro motivo per cui tendiamo a complicare il codice.
Confondiamo troppo presto correttezza, performance e perfezione.
Se una soluzione semplice funziona correttamente, spesso è un ottimo punto di partenza.
Successivamente puoi misurare.
Se esiste un problema reale di performance, puoi intervenire.
Se emerge un problema di scalabilità, puoi progettare una soluzione adeguata, ma ottimizzare prima di avere un problema concreto può portarti a introdurre complessità senza ottenere un beneficio reale.
La domanda dovrebbe essere:
“Quale problema sto cercando di risolvere?”
E non:
“Quale soluzione tecnicamente più sofisticata posso costruire?”
La semplicità non significa smettere di migliorare il codice
Scrivere codice semplice non significa lasciare tutto com’è. Al contrario. A volte la soluzione più semplice che puoi scrivere oggi non è quella più semplice che potresti avere domani.
Con nuove informazioni puoi eliminare codice.
Puoi semplificare un’astrazione.
Puoi rimuovere una dipendenza.
Puoi accorpare due componenti.
Puoi eliminare una configurazione che non serve più.
La semplicità è quindi anche un processo di rimozione, non soltanto di costruzione.
Il vero segno di maturità
All’inizio vuoi dimostrare di saper scrivere codice.
Poi vuoi dimostrare di conoscere pattern, architetture e tecnologie.
Con l’esperienza inizi invece a chiederti:
“Posso risolvere questo problema con meno complessità?”
Ed è una domanda molto più importante.
Perché ogni pezzo di codice che aggiungi rimarrà nel progetto.
Qualcuno dovrà leggerlo.
Qualcuno dovrà mantenerlo.
Qualcuno dovrà modificarlo.
Forse quel qualcuno sarai tu tra un anno.
Per questo, prima di aggiungere qualcosa, vale la pena chiedersi:
“Mi serve davvero?”
Se la risposta è sì, aggiungilo.
Se la risposta è “forse”, probabilmente puoi aspettare.
Perché nel software la complessità è facile da aggiungere.
La parte difficile è sapere cosa non aggiungere.