Ho visto decine di progetti affondare sotto il peso di astrazioni inutili prima ancora di raggiungere la fase di produzione. Ricordo un caso specifico in una banca d'affari a Milano: un intero team di sviluppo ha passato tre mesi a costruire un sistema di gestione dei pagamenti che sembrava un'opera d'arte accademica. Avevano implementato catene di responsabilità per ogni minima validazione e Bridge per componenti che non avrebbero mai cambiato fornitore. Risultato? Il codice era così frammentato che risolvere un bug stupido nel calcolo delle commissioni richiedeva di saltare tra dodici file diversi. Hanno mancato la deadline di rilascio, bruciando circa duecentomila euro in stipendi e costi operativi, solo per poi scoprire che la logica di business era sepolta sotto troppi Gang Of 4 Design Patterns usati a sproposito. Non commettere lo stesso errore pensando che più schemi usi, più sei bravo come programmatore. La realtà è che ogni astrazione è un debito che dovrai pagare con gli interessi in termini di manutenzione e comprensibilità.
L'ossessione per il Singleton che uccide la testabilità
Il primo errore che vedo ripetere ossessivamente è l'abuso del Singleton. Molti sviluppatori lo vedono come un modo comodo per gestire variabili globali mascherandole da oggetti eleganti. Ho lavorato su un sistema di e-commerce dove ogni configurazione, ogni connessione al database e persino il carrello della spesa erano Singleton. Sulla carta sembrava pulito, ma quando è arrivato il momento di scrivere test unitari, è scoppiato l'inferno. Poiché lo stato persisteva tra un test e l'altro, i risultati erano imprevedibili. Abbiamo perso settimane a cercare di resettare manualmente le istanze globali solo per far passare i test.
La soluzione non è eliminare il concetto di istanza unica, ma smettere di forzarla nel codice. Se hai bisogno che esista solo un oggetto di un certo tipo, gestisci questa logica nel punto in cui crei gli oggetti, magari tramite un contenitore per l'iniezione delle dipendenze, non blindando la classe stessa. Un Singleton rende il tuo codice rigido e difficile da evolvere. Se domani devi gestire due database invece di uno, dovrai riscrivere metà dell'applicazione perché hai cablato ovunque l'idea che l'istanza debba essere solo una. È un vincolo tecnico che ti esploderà in faccia non appena il business chiederà un briciolo di flessibilità in più.
Quando i Gang Of 4 Design Patterns diventano un labirinto di astrazioni
C'è questa idea sbagliata che per essere un vero senior tu debba applicare i Gang Of 4 Design Patterns ovunque, come se stessi completando un album di figurine. Ho visto architetture dove ogni singola operazione era avvolta in un Command, che a sua volta veniva gestito da un Mediator, che inviava notifiche tramite un Observer. È il classico caso di "over-engineering". Quando chiedevo al lead developer dove fosse la logica che salvava i dati, doveva aprire sei pacchetti diversi per trovarla. Questo non è codice pulito, è un labirinto che spaventa i nuovi arrivati e rallenta i veterani.
Il costo di questa complessità è misurabile. In un progetto per una startup di logistica, il tempo medio per inserire una nuova funzionalità era passato da tre giorni a due settimane. Il motivo? Ogni modifica richiedeva di aggiornare interfacce, implementazioni concrete e registrazioni nel sistema di messaggistica interna. Quando abbiamo deciso di semplificare, eliminando i pattern che non servivano e tornando a chiamate dirette tra classi dove la logica era lineare, la velocità del team è raddoppiata nel giro di un mese. Non usare un pattern se non hai un problema specifico che quel pattern risolve. L'astrazione deve servire a semplificare il cambiamento futuro, non a complicare il presente.
Il mito dell'estendibilità infinita
Spesso si giustifica l'uso eccessivo di schemi complessi dicendo che "così il codice è pronto per il futuro". È una bugia che ci raccontiamo per sentirci più sicuri. Nella maggior parte dei casi, quel futuro non arriva mai. Ho visto programmatori implementare lo Strategy pattern per algoritmi di ordinamento che sono rimasti identici per cinque anni. Hanno creato tre classi e un'interfaccia per qualcosa che poteva essere una singola funzione. Se non hai almeno tre varianti diverse di una logica che devono cambiare a runtime, non ti serve una Strategy. Ti serve una funzione ben scritta.
Confondere l'Abstract Factory con la flessibilità reale
L'Abstract Factory è forse il pattern più frainteso dopo il Singleton. Molti pensano che serva a rendere il software "indipendente dalla piattaforma", ma finiscono per creare gerarchie di classi così profonde che nessuno riesce più a seguirle. In un progetto di gestione documentale, il team aveva creato factory per ogni tipo di output: PDF, Word, HTML. Il problema è nato quando il cliente ha chiesto una modifica specifica solo per i PDF che non aveva senso per gli altri formati. Poiché tutto era incastrato nell'interfaccia della Factory comune, hanno dovuto aggiungere metodi vuoti o lanciare eccezioni nelle altre classi, sporcando tutto il sistema.
La realtà è che raramente hai bisogno di cambiare intere famiglie di oggetti a caldo. Se il tuo obiettivo è solo poter cambiare il database da MySQL a PostgreSQL una volta ogni tre anni, non ti serve un'architettura Factory ultra-complessa che appesantisce ogni singola allocazione di memoria. Ti basta un buon livello di astrazione sui dati e una configurazione fatta bene. Spendere tempo a costruire fabbriche di fabbriche è un lusso che poche aziende possono permettersi, specialmente quando il ritorno sull'investimento è vicino allo zero.
Trasformare la logica di business in uno stato confusionale
L'uso del pattern State è un altro campo minato. L'idea è ottima: eliminare i lunghi blocchi di if-else o switch gestendo il comportamento dell'oggetto in classi separate. Ma ecco cosa succede nella pratica: la logica di transizione tra gli stati finisce per essere dispersa. Se guardi la classe dello stato "OrdineInAttesa", trovi riferimenti che la spingono verso "OrdineSpedito" o "OrdineAnnullato". Finisci con un grafo di dipendenze circolari che rende impossibile capire il flusso del programma senza avere una mappa stampata sulla scrivania.
Ho visto un sistema di prenotazioni ferroviarie che usava lo Stato per gestire il ciclo di vita del biglietto. Ogni volta che si verificava un errore in una transizione, era un incubo capire quale oggetto avesse fallito il passaggio. Abbiamo dovuto riscrivere quella parte usando una semplice macchina a stati finita centralizzata, dove tutte le regole di transizione erano scritte in un unico posto. Il codice è diventato più corto di circa 500 righe e i bug legati a stati incoerenti sono spariti del tutto. Se la tua logica di stato supera i tre o quattro passaggi semplici, centralizzala. Non nasconderla dentro decine di piccole classi.
Il confronto reale tra teoria accademica e pragmatismo tecnico
Per capire quanto possa essere dannoso un approccio dogmatico, guardiamo come viene gestita l'aggiunta di una nuova funzionalità in due scenari diversi. Immaginiamo di dover aggiungere un sistema di sconti variabile a un carrello della spesa.
- Approccio con eccesso di schemi: Lo sviluppatore crea un'interfaccia
DiscountStrategy. Poi creaBlackFridayDiscount,ChristmasDiscounteNoDiscount. Dato che gli sconti dipendono dal cliente, crea unaDiscountFactoryche legge le preferenze dal database. Per gestire la notifica dello sconto applicato, aggiunge unDiscountObserver. Quando il business chiede di applicare due sconti contemporaneamente (es. sconto Natale + sconto fedeltà), l'intera struttura crolla. La Factory non sa gestire combinazioni, la Strategy ne accetta solo una alla volta. Lo sviluppatore deve introdurre unDecoratorper avvolgere le strategie. Tempo di sviluppo: 8 giorni. Righe di codice aggiunte: 450. - Approccio pragmatico: Lo sviluppatore nota che gli sconti sono solo regole di calcolo. Crea una lista di funzioni o una classe semplice che riceve il totale e restituisce lo sconto. Per gestire sconti multipli, usa un semplice ciclo che itera su una lista di sconti attivi. Non ci sono interfacce inutili perché non c'è previsione di cambiare radicalmente il modo in cui si calcola uno sconto a runtime oltre a queste varianti. Se serve una notifica, si aggiunge una riga di log o una chiamata a un servizio esistente. Tempo di sviluppo: 2 giorni. Righe di codice aggiunte: 60.
La differenza non è solo nel tempo iniziale. Il secondo approccio è molto più facile da debuggare. Se il calcolo dello sconto è sbagliato, sai esattamente dove guardare. Nel primo approccio, potresti passare ore a capire se l'errore è nella Factory che ha scelto la strategia sbagliata o nel Decoratore che ha applicato gli sconti nell'ordine errato.
Semplificare il sistema senza perdere il controllo
Non sto dicendo che non devi usare i Gang Of 4 Design Patterns. Sono strumenti incredibili, ma come ogni strumento, se lo usi male ti fai male. Il segreto sta nel posticipare l'astrazione il più possibile. Inizia scrivendo il codice più semplice e diretto che risolve il problema. Solo quando vedi che quel codice sta diventando difficile da gestire, o quando hai davvero una duplicazione che urla vendetta, allora introduci un pattern.
Un buon modo per decidere se introdurre una struttura complessa è porsi tre domande:
- Questa parte di codice cambierà davvero più di una volta all'anno?
- La complessità aggiunta dal pattern è minore della complessità causata dal problema originale?
- Un nuovo programmatore riuscirebbe a capire cosa succede senza leggere la documentazione del pattern?
Se la risposta a una di queste domande è "no", allora fermati. Non stai migliorando il sistema, lo stai solo rendendo più pesante. Spesso un semplice metodo privato o una piccola classe di supporto sono tutto ciò di cui hai bisogno. Non c'è alcun premio per chi usa il pattern più oscuro del libro. Il premio lo vince chi consegna un software che funziona, è veloce e non richiede un team di geni per essere mantenuto.
Il controllo della realtà sul campo
Smettila di pensare che i pattern siano una bacchetta magica. Se il tuo dominio di business è confuso o se i tuoi requisiti cambiano ogni due giorni, nessun pattern ti salverà dal caos. Anzi, un'architettura rigida basata su schemi predefiniti renderà ogni cambiamento ancora più doloroso. Molti sviluppatori usano i pattern per sentirsi protetti, come se seguendo una ricetta famosa potessero evitare di pensare criticamente alla struttura dei dati e al flusso delle informazioni. Non funziona così.
L'esperienza mi ha insegnato che il codice migliore è quello che puoi cancellare e riscrivere in un pomeriggio. Se hai implementato una struttura così complessa che averne paura ti impedisce di toccarla, hai fallito come architetto. Sii brutale con il tuo codice. Se un'astrazione non si ripaga da sola riducendo il carico mentale, eliminala. Il successo non arriva da quanto sei bravo a seguire i manuali degli anni novanta, ma da quanto sei capace di adattare quei concetti alle esigenze reali, sporche e mutevoli del mercato attuale. Meno teoria, più codice che gira.