Code Smell è un indicatore superficiale nel codice che segnala un potenziale problema nel design o nell'architettura di un'applicazione. Il termine è stato coniato da Kent Beck e reso popolare da Martin Fowler nel libro “Refactoring: Improving the Design of Existing Code”. Secondo Martin Fowler, un odore del codice non significa necessariamente un bug, ma indica quasi sempre la necessità di refactoring per migliorare la manutenibilità.
Punti Chiave
Code Smell è una metafora per i sintomi nel codice sorgente che con alta probabilità indicano problemi più profondi. Il termine stesso non ha una definizione formale — è un'euristica basata sull'esperienza degli sviluppatori. Martin Fowler e Kent Beck hanno sistematizzato per la prima volta 22 odori nel 1999 nel libro “Refactoring”, e la maggior parte di essi rimane rilevante decenni dopo.
È importante capire la differenza tra Code Smell e un bug. Un odore non è un errore: il codice compila, funziona e produce risultati corretti. Il problema è che tale codice è difficile da leggere, modificare e testare. Con il tempo, il costo di ogni cambiamento aumenta e la fiducia nella correttezza del refactoring diminuisce. Gli strumenti di analisi statica (SonarQube, Detekt, SwiftLint) rilevano automaticamente molti odori.
La natura euristica di Code Smell significa che non tutti i metodi lunghi devono essere suddivisi e non tutte le classi grandi richiedono refactoring. La decisione viene presa dallo sviluppatore valutando il contesto: frequenza dei cambiamenti, criticità del modulo, piani di sviluppo. Gli ingegneri esperti percepiscono un odore intuitivamente — il codice “puzza” anche se tutte le regole formali sono rispettate.
Fowler ha identificato 22 odori raggruppati in diverse categorie. Per lo sviluppo mobile, i più rilevanti sono gli odori strutturali, gli odori di design orientato agli oggetti e i problemi specifici legati ai vincoli della piattaforma. Esaminiamo ogni gruppo con esempi reali.
Long Method è l'odore più comune nelle applicazioni mobili. Uno schermo di modulo di registrazione contiene spesso un unico metodo setupUI di 200+ righe che crea tutte le View, configura i constraint, si iscrive agli eventi e gestisce gli errori. Soluzione: suddividere in metodi per blocchi logici — configureEmailField, configurePasswordField, setupConstraints, bindViewModel.
Large Class — un'Activity o ViewController responsabile di visualizzazione, navigazione, logica di business e interazioni di rete tutti insieme. Tale classe viola il Principio di Responsabilità Unica e contiene decine di campi e metodi. In Android, è spesso un Fragment con 1000+ righe contenente logica di schermate diverse. Soluzione: estrarre un presenter/ViewModel, spostare il codice di rete in un repository e la navigazione in un coordinatore.
Duplicate Code — copiare blocchi identici in diverse parti dell'applicazione. Un esempio tipico: due schermate che mostrano una scheda prodotto — nel catalogo e nei preferiti. Se la logica di visualizzazione è copiata, correggere un bug in un punto non lo correggerà nell'altro. Soluzione: estrarre la logica comune in un componente riutilizzabile o estensione.
Feature Envy — un metodo di una classe usa intensamente i dati di un'altra classe. In Android, ciò si manifesta quando una ViewModel accede direttamente ai campi di un modello User invece di chiamare un metodo del modello. Segnale: se un metodo può essere spostato nella classe i cui dati usa — spostatelo. Switch Statements (catene di condizioni) — una costruzione switch o catena if-else che controlla il tipo di oggetto. Invece, usate il polimorfismo o il pattern strategy.
Data Class — una classe che memorizza solo dati ma non contiene comportamento. Le data class in Kotlin o le strutture in Swift non sono intrinsecamente un odore. Il problema sorge quando la logica di business che lavora con quei dati è sparsa in tutta la base di codice invece di essere incapsulata. Refused Bequest — una sottoclasse non usa la maggior parte dei metodi del genitore e li sovrascrive con stub vuoti. Segno di ereditarietà errata: sostituite l'ereditarietà con la composizione.
God Activity / God Fragment — un'Activity o Fragment che sa tutto: ciclo di vita, dati, navigazione, permessi, DI. È la classe più costosa da mantenere in un'applicazione. Soluzione: i pattern architetturali MVVM, MVI o Clean Architecture separano le responsabilità. Giant ViewController — l'equivalente per iOS, dove un UIViewController contiene tutta la logica dello schermo e spesso supera 500 righe.
Hardcoded Resources — stringhe, colori, dimensioni, URL API incorporati direttamente nel codice. In Android, ciò viola il sistema di risorse R; in iOS, NSLocalizedString e Asset Catalog. Correzione: spostare tutte le stringhe in strings.xml o Localizable.strings, gli URL in un file di configurazione, le dimensioni in dimens. Leaking Context — mantenere un riferimento a un'Activity o ViewController più a lungo della vita del componente stesso. Porta a perdite di memoria e crash. Soluzione: riferimenti deboli, Jetpack Lifecycle, RxSwift DisposeBag.
| Odore | Dove si presenta | Soluzione |
|---|---|---|
| Long Method | Android/iOS | Extract Method, suddivisione |
| Large Class | Activity, ViewController | MVVM, VIPER, Clean Arch |
| Duplicate Code | Qualsiasi schermo | Componente condiviso, DRY |
| Feature Envy | ViewModel, Presenter | Move Method |
| Leaking Context | Android | Componenti Lifecycle-aware |
Code review è il modo più affidabile per rilevare gli odori. L'occhio umano nota costruzioni innaturali che gli analizzatori automatici perdono. L'efficacia del code review migliora quando il team usa una checklist di odori tipici. Si raccomanda di non revisionare più di 200–400 righe di codice per sessione — dopo questa soglia, l'attenzione diminuisce e gli odori iniziano a sfuggire.
Analisi statica automatizza la ricerca degli odori strutturali. Per Android, gli strumenti standard sono Detekt (Kotlin) e Android Lint; per iOS, SwiftLint e SonarQube. Questi strumenti trovano metodi lunghi, classi grandi, codice duplicato e molti altri problemi. È importante adattare le regole al progetto — le configurazioni predefinite sono spesso troppo severe o, al contrario, perdono odori critici.
Metriche del codice forniscono criteri oggettivi: Complessità Ciclomatica (soglia >10 richiede attenzione), Righe di Codice per Metodo (soglia >30), Profondità di Ereditarietà (>3 — motivo per riflettere). Strumenti come CodeMetrics (Xcode) e il plugin Gradle Metrics costruiscono grafici dei cambiamenti delle metriche nel tempo. Se la complessità di un metodo è cresciuta da 5 a 15 dopo l'ultimo commit — è un segnale per refactoring.
// Esempio: metodo con Complessità Ciclomatica = 7 (sopra la soglia 5)
fun processOrder(order: Order) {
if (order.status == Status.NEW) { /* 10 righe */ }
else if (order.status == Status.PAID) { /* 15 righe */ }
else if (order.status == Status.SHIPPED) { /* 20 righe */ }
else if (order.status == Status.DELIVERED) { /* 8 righe */ }
else if (order.status == Status.CANCELLED) { /* 5 righe */ }
else { throw IllegalStateException() }
}
// Correzione: polimorfismo invece di switch
interface OrderHandler {
fun handle(order: Order)
}
Il rilevamento automatizzato degli odori non sostituisce il code review: gli analizzatori statici trovano solo problemi strutturali ma non catturano odori semantici (Feature Envy, Inappropriate Intimacy). La combinazione di strumenti automatici e revisione umana dà i migliori risultati. Configurate la pipeline CI/CD in modo che le build falliscano quando vengono superate le soglie di complessità o lunghezza del metodo.
Refactoring è il metodo principale per eliminare gli odori del codice. Fowler descrive dozzine di tecniche di refactoring, ciascuna applicabile a un odore specifico. Extract Method — per metodi lunghi, Extract Class — per classi grandi, Move Method — per Feature Envy. È importante eseguire il refactoring a piccoli passi, mantenendo il codice funzionante dopo ogni modifica.
Test prima del refactoring sono obbligatori. Se il codice non è coperto da test unitari, il refactoring si trasforma in riscrittura con risultati sconosciuti. Per codice legacy senza test, usate Characterization Tests — scrivete test che catturano il comportamento attuale, poi rifattorizzate. I test danno la certezza che la logica di business non si sia rotta dopo il refactoring.
Gradualità è la chiave per risolvere con successo gli odori nello sviluppo mobile. Non cercate di riscrivere una God Activity completamente. Prima estraete il livello di navigazione, poi il livello dati, poi la logica di visualizzazione. Ogni passo dovrebbe essere accompagnato da un commit e dall'esecuzione dei test. Usate feature toggle per abilitare il refactoring per un sottoinsieme di utenti e tornare indietro in caso di problemi.
Gli strumenti IDE automatizzano molte tecniche di refactoring. Android Studio e IntelliJ IDEA offrono refactoring integrati: Extract Method, Extract Interface, Pull Members Up, Encapsulate Fields. Xcode (a partire dalla versione 14) ha migliorato il supporto al refactoring per Swift. L'uso di refactoring automatici riduce il rischio di errori rispetto alla copia manuale del codice.
Lo sviluppo mobile aggiunge i propri odori specifici legati ai vincoli della piattaforma. In Android, ciò include perdite di Context, Cursor non chiusi e uso improprio del Lifecycle. In iOS, cicli di retain attraverso le closure, gestione errata di Auto Layout e ViewController giganti. Questi odori non solo compromettono la manutenibilità ma influiscono direttamente sulle prestazioni e sulla stabilità dell'applicazione.
Callback Hell è un odore caratteristico per il codice che lavora con operazioni asincrone. Callback annidati rendono il codice illeggibile e difficile da debuggare. Soluzione: coroutine (Kotlin), async/await (Swift 5.5+), RxJava/RxSwift o Combine. Secondo Google I/O 2023, i progetti passati dallo stile callback alle coroutine hanno ridotto il numero di bug del 30% e accelerato l'aggiunta di nuove funzionalità.
Platform Coupling — forte accoppiamento della logica di business ai componenti della piattaforma. Testare tale logica richiede l'avvio di un emulatore, rallentando il ciclo di feedback. Correzione: Clean Architecture separa il codice in livelli Domain (Kotlin/Swift puro senza dipendenze dalla piattaforma) e Data/UI (con dipendenze dalla piattaforma). La logica di business viene testata sulla JVM senza emulatore.
Domande Frequenti
No — Code Smell non è un errore. Il codice con odore funziona correttamente, ma è difficile da mantenere, modificare e testare. Un bug è un comportamento errato; un odore è un avviso di potenziali problemi futuri.
22 odori nella seconda edizione di “Refactoring” (2019). Tra questi Long Method, Large Class, Primitive Obsession, Data Clumps, Switch Statements, Speculative Generality e altri. La comunità ha aggiunto dozzine di nuovi odori per paradigmi e piattaforme moderne.
Una combinazione dà il miglior risultato: Detekt (Android/Kotlin), SwiftLint (iOS), SonarQube (entrambi) per l'analisi automatica e code review per gli odori semantici. Nessun singolo strumento trova il 100% dei problemi — l'esperienza umana rimane decisiva.
Sì, se il codice cambia raramente o sarà completamente riscritto a breve. Tuttavia, l'accumulo di odori si trasforma in debito tecnico: ogni nuovo cambiamento diventa più difficile e il costo della correzione cresce esponenzialmente.
Sì — i framework dichiarativi hanno generato nuovi odori: blocchi @State giganti, gestione errata dei rendering ripetuti, ricomposizione eccessiva e mancanza di estrazione in View separate. Per SwiftUI, un odore tipico è Massive View con decine di variabili @State.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche