"Code smell" è un termine informale usato dai programmatori per descrivere i modelli di progettazione del software che sono comuni al codice di scarsa qualità. I code smell sono meglio intesi non come bug o difetti a sé stanti, ma come segnali di avvertimento di scarsa qualità del codice.
Il termine "code smell" è stato coniato da Kent Beck e reso popolare nel libro fondamentale del 1999 che ha scritto insieme a Martin Fowler, Refactoring: Improving the Design of Existing Code e in particolare nel capitolo intitolato "Bad Smells in Code." Fowler stesso definisce sinteticamente un "code smell" come "un'indicazione superficiale che di solito corrisponde a un problema più profondo nel sistema".1
A differenza di un vero bug software, un code smell non impedisce al codice sorgente di compilare, eseguire e svolgere la funzione desiderata, né la sua presenza indica sempre un problema reale. Immagina di entrare in una casa e sentire l'odore di qualcosa di strano o di muffa. Quell'odore potrebbe provenire dal cibo scaduto nel frigorifero o dai rifiuti che devono essere portati via; più seriamente, potrebbe essere dovuto alla muffa o a qualcosa che marcisce nelle pareti, ma potrebbe anche provenire solo da un formaggio molto pungente ma perfettamente sicuro da mangiare. In ogni caso, l'odore merita un'ulteriore ispezione.
Per un ingegnere del software esperto, alcuni modelli di codice, semplicemente, non "hanno l'odore" giusto. Nel tempo, si potrebbe istintivamente associare la loro presenza a determinate complicazioni, inefficienze o problemi futuri. Dare un nome e rendere popolari "code smell" (cattive pratiche di programmazione) specifici facilita la familiarizzazione con questi modelli controproducenti (o "anti-pattern") e con i principi di progettazione che violano. Questa familiarità, a sua volta, rende più probabile che questi anti-pattern vengano notati e risolti durante le revisioni del codice. Come afferma Fowler, "un odore è per definizione qualcosa che si può individuare rapidamente, o annusare".
I code smell, se non affrontati, sono le cause principali del debito tecnico. Come afferma uno studio, il 75% dei difetti di revisione del codice non influisce sull'esecuzione del programma ma "influisce sulla sua capacità di evolvere".2 Anche se un certo anti-pattern o una cattiva abitudine non causa un bug oggi, aumenta significativamente il rischio che bug, crash o vulnerabilità di sicurezza nella base di codice emergano in seguito. Come minimo, la presenza di code smell noti riduce la leggibilità e la manutenibilità del codice, rendendolo più difficile da comprendere, aggiornare e migliorare.
Resta al passo con le tendenze più importanti e interessanti del settore relative ad AI, automazione, dati e oltre con la newsletter Think. Leggi l' Informativa sulla privacy IBM.
Sebbene molti code smell comuni continuino a essere conosciuti con i nomi coniati da Beck e Fowler in refactoring, non ne esiste una lista canonica e universalmente accettata. Ma se un determinato code smell debba essere indicato con un nome o con un altro, o essere classificato come il proprio anti-pattern piuttosto che come sottotipo di un altro, è in gran parte irrilevante. L'aspetto importante è se il nome e la descrizione di un codice siano abbastanza chiari e intuitivi da aiutare i team di sviluppo software a condividere e implementare principi di progettazione produttivi.
Non esiste, allo stesso modo, una tassonomia universalmente accettata per le diverse categorie di code smell. Questo articolo si basa principalmente sulla tassonomia proposta da Jerzyk e Madeyski nel 2023.3 I risultati sono stati pubblicati nell'utile catalogo disponibile sul sito codesmells.org, che fornisce ulteriore contesto, esempi di codice e tecniche di refactoring appropriate per ciascuno di essi.
Jerzyk e Madeyski osservano che la tassonomia più comunemente citata dei code smell segue i cinque raggruppamenti proposti da Mäntylä e Lassenius nel 2006, che hanno classificato i 22 odori di codice introdotti da Fowler e Beck (e un altro coniato da loro) in cinque gruppi distinti: Bloaters, Object-Oriented Abusers, Change Preventers, Dispensables e Couplers.4 Dopo aver tentato una revisione esaustiva sia della letteratura pubblicata ufficialmente sia del "materiale grigio" (come blog, forum e wiki), Jerzyk e Madeyski hanno catalogato 56 diversi "code smell" in 9 gruppi (che includono i 5 menzionati in precedenza in questo paragrafo).
Sebbene questa sezione segua per lo più la struttura a 9 categorie che hanno proposto, è importante notare che tali raggruppamenti sono informali e soggettivi: la tassonomia più utile è quella che si trova più intuitiva. Comprendere quali inefficienze e complicazioni possano derivare dai code smell è molto più utile che memorizzarli meccanicamente uno per uno. Come afferma Mäntylä, "con un lungo elenco di code smell è facile perdere di vista il quadro generale".5
I Bloater sono code smell che spesso portano a metodi, classi o blocchi di codice a diventare così grandi da diventare ingombranti, riducendone la leggibilità e rendendo il codice più difficile da mantenere o modificare.
Esempi di bloater includono:
Aggregati di dati: gruppi di variabili che spesso appaiono insieme ovunque.
Classe grande: una classe che cerca di fare troppo e contiene troppe variabili, mancando di coesione.
Funzione lunga (metodo lungo): un metodo contenente troppe righe di codice.
Elenco di parametri lungo: funzioni che richiedono troppi argomenti per funzionare correttamente.
Ossessione primitiva: utilizzare tipi di dati primitivi invece di piccoli oggetti specializzati.
I change preventer, come suggerisce il nome, sono code smell che ostacolano la possibilità di modificare, aggiungere o sviluppare ulteriormente software. Un esempio tipico di un change preventer è una struttura di codice che ti costringe a fare una serie di modifiche in più punti solo per implementare una singola, semplice modifica.
Questo tipo di code smell viola il principio di responsabilità unica (SRP) di Robert C. Martin, secondo il quale le modifiche a un singolo modulo di codice dovrebbero avere origine da un solo punto. Come suggerisce Martin in un post sul blog sull'SRP, "Raccogli le cose che cambiano per gli stessi motivi. Separa le cose che cambiano per motivi diversi."6
Gli esempi di change preventer includono:
Shotgun surgery: l'implementazione di una singola modifica richiede modifiche simultanee su molti moduli sparsi (essenzialmente l'opposto di una classe grande).
Divergent change: come l'inverso della shotgun surgery, l'implementazione di una singola modifica richiede molte modifiche all'interno di una singola classe.
Callback hell: struttura del codice in cui molti metodi sono annidati in profondità l'uno dentro l'altro, attraverso numerose schede di indentazione e parentesi graffe, offuscando la causa-effetto e rendendo il codice difficile da leggere e mantenere.
I coupler sono code smell che violano il principio fondamentale di progettazione del low coupling creando dipendenze eccessive tra classi diverse. Questo fatto riduce la riusabilità del codice e complica (o addirittura impedisce) i test unitari indipendenti.
Ecco alcuni esempi:
Feature envy: un metodo accede ai dati di un altro oggetto in modo eccessivo.
Insider trading (noto anche come inappropriate intimacy): una classe utilizza i campi o i metodi interni di un'altra.
Message chain: una lunga sequenza di chiamate di metodo concatenate tra oggetti.
I data dealer fanno passare i dati attraverso molte più classi o funzioni di quanto sia necessario. Questo fatto crea dipendenze e complessità non necessarie, spesso con conseguenti elementi di codice che si limitano a contenere o distribuire dati senza fornire alcun comportamento significativo.
Gli esempi di data dealer includono:
Middle man: una classe il cui unico compito è delegare ad altre.
Tramp data: quando i dati "viaggiano" attraverso una catena di metodi che non ne fanno uso.
Global data: struttura in cui le variabili possono essere modificate da qualsiasi punto della base di codice, rendendone sospetta ogni funzione in caso di guasto.
I dispensable sono, intuitivamente, elementi di codice superflui: la loro rimozione renderebbe la base di codice più pulita e facile da leggere senza alcun impatto significativo sulla funzionalità complessiva.
Gli esempi di dispensable includono:
Commenti: sebbene i commenti siano generalmente una cosa positiva, a volte vengono usati come un "deodorante" per i code smell, spiegando invece di migliorare. Ad esempio, se un commento descrive cosa accade in una particolare sezione di codice, viene utilizzato per mascherare codice che non è sufficientemente intuitivo o leggibile di per sé. I singoli commenti possono anche diventare superflui o obsoleti col tempo. Comportamenti più specifici relativi ai commenti compaiono in altri tipi di code smell.
Data class: classi contenenti solo campi e funzioni di accesso, prive di un comportamento significativo.
Dead code: elementi di codice che non vengono più eseguiti perché, nel tempo, il refactoring e altre modifiche li hanno resi obsoleti.
Duplicate code: strutture di codice identiche o molto simili presenti in più punti.
Lazy class (o anche lazy element): classi o funzioni che fanno troppo poco per esistere.
Speculative generality: codice non necessario aggiunto per supportare ipotetiche caratteristiche future.
I functional abuser sono code smell che evitano i principi di object-oriented programming forzando i modelli funzionali in una object-oriented codebase.
Esempi di functional abuser includono:
Loop: utilizzo di cicli tradizionali al posto delle moderne operazioni pipeline. Sebbene Fowler considerasse obsoleti quasi tutti i loop, Jerzyk sosteneva che gli imperative loop, in particolare, rappresentassero il problema principale.
Mutable data: variabili il cui stato cambia inaspettatamente, causando effetti collaterali imprevedibili.
Side effect (o anche impure functions): metodi che svolgono funzioni che vanno oltre quanto il loro nome e il loro scopo primario lascerebbero intendere.
I lexical abuser sono code smell derivanti da convenzioni di denominazione inadeguate, formattazione incoerente o sintassi criptica. In parole semplici, si tratta di casi in cui la formulazione del codice non corrisponde intuitivamente al comportamento del codice stesso, ostacolandone la leggibilità.
Esempi di lexical abuser includono:
Magic number: numeri senza nome inseriti in codice senza adeguata spiegazione o contesto.
Fallacious comment: commenti che non sono più accurati perché il codice circostante è cambiato. Poiché i commenti non vengono effettivamente eseguiti, spesso sfuggono ai linter e ad altri controlli automatici.
Mysterious name: funzioni o variabili nominate in modo inadeguato, che nascondono il vero intento.
Fallacious method name: funzioni i cui nomi sono attivamente fuorvianti, in base a convenzioni e aspettative comuni. Ad esempio, una funzione chiamata getSomething che in realtà non restituisce nulla.
Obfuscator sono elementi di codice scritti in modo inutilmente contorto, complesso o "ingegnoso", che nascondono l'intento sottostante del codice dietro un'astrazione superflua. Questi code smell rendono il codice sorgente confuso da leggere e quindi rendono difficile per i programmatori in futuro comprenderlo e modificarlo secondo necessità.
Esempi di obfuscator includono:
Separazione verticale: distanze inutilmente grandi tra elementi di codice correlati e pertinenti, come una variabile dichiarata all'inizio di un metodo che viene utilizzata solo 50 righe dopo.
Obscured intent: la categoria più ampia di funzioni, variabili, nomi e numeri il cui scopo non è né intuitivo né chiaro nel contesto.
Espressione booleana complicata: flussi logici contorti, come quelli contenenti doppi negativi o catene complesse di
Clever code: codice funzionante che sostituisce un linguaggio personalizzato e di difficile comprensione in situazioni per le quali esistono già funzioni integrate ampiamente comprese e altre soluzioni convenzionali.
Gli object-oriented abuser (o object-orientation abuser) sono code smell che non applicano in modo completo o corretto i principi della progettazione orientata agli oggetti. Ad esempio, le switch statement sono utili nella programmazione procedurale, ma dovrebbero essere evitate nella programmazione orientata agli oggetti.
Esempi di object-oriented abuser includono:
Classe alternativa con interfacce diverse: classi che eseguono funzioni simili utilizzando nomi di metodo completamente diversi.
Refused bequest: sottoclassi che non necessitano o non utilizzano metodi ereditati.
Switch statement (o anche Conditional Complexity oppure Repeated Switch): la stessa identica istruzione switch duplicata in tutto il codice.
Campo temporaneo: una variabile creata dove spesso non è necessaria, in genere utilizzata solo in determinate circostanze.
Il beneficio principale della comprensione dei code smell è che il loro riconoscimento consente un refactoring più efficace: la pratica di aggiornare il codice sorgente senza modificarne il comportamento o la funzionalità esterni. Riordinare regolarmente il codice tramite refactoring è essenziale per ridurre il debito tecnico e facilitare miglioramenti e aggiunte rapidi ed efficaci alla codebase nel tempo.
Il refactoring può essere in gran parte inteso come il processo di identificazione e indirizzo del code smell. Il suo obiettivo è apportare correzioni ai problemi prima ancora che abbiano un impatto negativo sulla funzionalità: a quel punto il processo è più simile al debug che al refactoring.
Le moderne piattaforme di ingegneria agentica, come IBM® Bob, spesso forniscono suggerimenti di refactoring automatizzati in tempo reale. Attraverso un ampio addestramento sui code smell nel contesto di codebase reali (piuttosto che memorizzare le definizioni di code smell) le funzionalità di refactoring di codice AI di queste piattaforme permettono agli sviluppatori di scalare il loro output senza un corrispondente aumento di codice generato dall'AI ingombrante che sarà difficile da comprendere e modificare in seguito.
Accelera la distribuzione del software con IBM® Bob, il tuo partner AI per uno sviluppo sicuro e consapevole degli intenti.
Sviluppa, implementa e gestisci le applicazioni AI più velocemente con strumenti pensati per le aziende.
Reinventa i sistemi legacy con la modernizzazione intelligente dell'AI.