Anti-pattern di design che costano più dei bug 🚨
I bug fanno danni, ma gli anti-pattern di design hanno un talento speciale: fanno danni lentamente, in modo credibile, e spesso con l’aria di essere stati introdotti per “mettere ordine”. Il problema è che alzano il costo del cambiamento fino a rendere ogni intervento un’operazione delicata.
Questa guida non serve a collezionare etichette. Serve a riconoscere odori strutturali prima che diventino l’arredamento stabile della codebase.
God Object: tutto passa da lui 👑
Il God Object concentra troppe responsabilità in un solo componente. Conosce tutto, decide tutto, coordina tutto e probabilmente avrebbe opinioni anche sulla tua roadmap personale.
Segnali tipici:
- file enormi e ingestibili
- dipendenze ovunque
- test lenti e fragili
- paura diffusa di toccare quel componente
Correzione tipica:
- separare responsabilità per motivo di cambiamento
- estrarre servizi o moduli coesi
- ridurre le dipendenze ai bordi
Spaghetti Code: flusso senza geometria 🍝
Lo Spaghetti Code non è solo codice brutto. È codice in cui il flusso è difficile da seguire, le dipendenze non sono leggibili e la modifica di un comportamento genera effetti collaterali poco prevedibili.
Segnali tipici:
- condizionali annidati ovunque
- logica distribuita in punti lontani
- stato mutato da troppi attori
- debugging che somiglia a un inseguimento in città senza mappa
Correzione tipica:
- chiarire il flusso con funzioni più piccole e responsabilità nette
- ridurre stato condiviso e coupling implicito
- isolare I/O, orchestrazione e logica di dominio
Premature Abstraction: quando astrarre troppo presto costa caro 🏗️
La Premature Abstraction nasce da un’intenzione rispettabile: evitare duplicazione e prepararsi al futuro. Il risultato, spesso, è un layer generico che non rappresenta bene nessun caso reale.
Segnali tipici:
- nomi vaghi come
BaseManager,GenericService,AbstractProcessor - parametri e flag che cambiano il comportamento in dieci modi diversi
- codice difficile da capire perché l’astrazione è più complicata dei casi concreti
Correzione tipica:
- riportare il design più vicino ai casi reali
- accettare una duplicazione temporanea quando chiarisce il dominio
- astrarre solo dopo aver visto un pattern stabile
Shotgun Surgery: un cambiamento, venti file 💥
La Shotgun Surgery è l’anti-pattern in cui una singola modifica costringe a ritoccare molti punti diversi della codebase.
Segnali tipici:
- ogni feature banale richiede una lista infinita di micro-modifiche
- logica correlata sparsa in troppi moduli
- alta probabilità di dimenticare un pezzo
Correzione tipica:
- ricomporre comportamento e dati che cambiano insieme
- aumentare la coesione
- rendere espliciti i confini tra responsabilità
Golden Hammer: un pattern per dominarli tutti 🔨
Il Golden Hammer è l’abitudine di applicare sempre la stessa soluzione, indipendentemente dal problema. Succede con i microservizi, con DDD, con i workflow engine, con gli event bus e, sì, anche con la dependency injection.
Segnali tipici:
- la soluzione arriva prima dell’analisi
- ogni problema assomiglia al tool preferito del team
- il contesto concreto conta meno della moda tecnica in corso
Correzione tipica:
- ripartire dal problema e dai vincoli
- confrontare più opzioni con trade-off espliciti
- evitare architetture prestazionali per requisiti immaginari
Due anti-pattern moderni molto comuni 🧪
Interfacce premature
Creare interfacce ovunque “così siamo flessibili” parte da un’intenzione rispettabile. Il risultato, spesso, è una codebase che simula varietà senza averne il bisogno reale: contratti artificiali, boilerplate abbondante e un’illusione di testabilità che non regge alla prima pressione.
Segnali tipici:
- interfacce con un unico implementatore, destinato probabilmente a restare tale
- naming ridondante del tipo
UserRepository,UserRepositoryInterface,UserRepositoryImpl - mock che vengono copiati spesso e aggiornati raramente quando il contratto cambia
- wiring elaborato per sostituire qualcosa che in produzione non si sostituirà mai
Correzione tipica:
- introdurre l’interfaccia solo quando esiste o è concretamente attesa una seconda implementazione
- tenerla vicina al chiamante, non come ornamento architetturale condiviso
- ricordare che uno struct concreto con dipendenze esplicite è già testabile senza interfacce aggiuntive
DI ovunque
La dependency injection è utile, ma applicarla indiscriminatamente trasforma una codebase ordinata in una sfilata di costruttori con parametri inutili, mock che non si sostituiscono mai e wiring che nessuno riesce più a tracciare. È il Golden Hammer applicato alla gestione delle dipendenze.
Segnali tipici:
- costruttori con molti parametri, metà dei quali non vengono mai variati in test o runtime
- mock di dipendenze che non cambiano mai tra un test e l’altro e che costano più che usare la versione reale
- container o registry che nascondono il grafo reale dell’applicazione dietro configurazione implicita
- code review dedicate a discutere “dove va iniettato X” invece che la logica effettiva
Correzione tipica:
- applicare DI dove c’è vera variabilità: repository, clock, gateway, notifier
- lasciare concrete le dipendenze stabili che non cambieranno mai
- tenere il wiring nel main o in entry point chiari, non distribuito ovunque
Come usare questa guida in review 🔍
Domande utili:
- questo componente sta facendo troppo?
- questa astrazione è nata da un caso reale o da ansia preventiva?
- un cambiamento locale resta locale?
- il team sta applicando uno schema perché utile o perché familiare?
In sintesi 🧾
Gli anti-pattern di design non sono peccati morali. Sono segnali di un sistema che sta diventando più costoso da cambiare, capire e testare. Riconoscerli presto vale più di tante retrospettive drammatiche fatte quando il danno è già diventato struttura.
Per i principi che aiutano a prevenirli, vedi principi di software design, Clean code e Dependency Injection e Inversion of Control.