Parliamone
// tecnologie.refactoring-patterns

Refactoring Patterns

Dai code smell ai pattern architetturali: tassonomia, meccanismi di trasformazione sicura e frontiere dell'automazione nel refactoring del software.

Software Architecture

Executive summary

Quando una base di codice cresce nel tempo, la sua struttura interna tende a degradarsi, rendendo ogni modifica successiva più lenta, più costosa e più soggetta a errori. Esistono tecniche consolidate per riorganizzare il codice senza alterarne il comportamento visibile dall'esterno, riducendo progressivamente il costo della manutenzione e dell'evoluzione del sistema. L'analisi che segue esamina i principali schemi di intervento, da quelli che agiscono sulla singola funzione fino a quelli che guidano la sostituzione progressiva di interi sistemi, valutando il ruolo delle verifiche automatiche e degli strumenti di sviluppo nel rendere queste trasformazioni affidabili. Dall'esame emerge che l'efficacia di questi interventi dipende dalla combinazione di criteri di riconoscimento dei problemi strutturali, garanzie formali sulla stabilità del sistema e strumenti che riducano il rischio di errore umano.


Background

Il concetto di refactoring come disciplina formale nel software engineering ha origini accademiche precise. La tesi di dottorato di William F. Opdyke, difesa nel 1992 presso l'Università dell'Illinois, costituisce il primo trattamento sistematico del tema: Opdyke definisce un insieme di ristrutturazioni per framework orientati agli oggetti, ciascuna corredata da precondizioni formali che garantiscono la preservazione del comportamento del programma [1]. Il contributo fondamentale della tesi risiede nella dimostrazione che molte trasformazioni strutturali possono essere automatizzate in modo sicuro, a condizione che le precondizioni siano verificabili, sebbene per alcune di esse la verificabilità sia in generale indecidibile.

Il passaggio dalla formalizzazione accademica alla pratica ingegneristica avviene con la pubblicazione di Refactoring: Improving the Design of Existing Code di Martin Fowler, la cui prima edizione risale al 1999 e la seconda, sostanzialmente riscritta, al 2018 [2]. Fowler sistematizza il refactoring in un catalogo di trasformazioni nominate, 68 nella seconda edizione, organizzate per categoria e corredate da motivazione, meccanica e condizioni di applicabilità. Il catalogo non si limita a descrivere come eseguire una trasformazione: per ciascun refactoring, Fowler esplicita il perché, collegando ogni intervento a un problema strutturale osservabile nel codice.

La relazione tra refactoring e design pattern è stata formalizzata da Joshua Kerievsky in Refactoring to Patterns (2004), che introduce il concetto di refactoring diretto da pattern: sequenze di trasformazioni a basso livello che conducono il design verso, o lontano da, l'implementazione di un pattern specifico [3]. Questo lavoro colma il divario concettuale tra il catalogo di Fowler, orientato alle micro-trasformazioni, e il catalogo di Gamma et al., orientato alle macro-strutture di design.

Sul versante della ricerca empirica, la survey di Mens e Tourwé pubblicata su IEEE Transactions on Software Engineering nel 2004 resta il riferimento più citato per una panoramica sistematica delle attività, tecniche, formalismi e problemi aperti nel campo del refactoring [4]. Gli autori classificano il refactoring lungo cinque dimensioni, attività supportate, tecniche utilizzate, tipi di artefatti, supporto strumentale ed effetti sul processo, fornendo un framework analitico che ha orientato la ricerca successiva per oltre un decennio.

Un aggiornamento significativo di questa panoramica è offerto dallo studio su larga scala di AlOmar et al. (2024), che analizza le relazioni tra esperienza degli sviluppatori e pratiche di refactoring in progetti open-source reali, classificando le motivazioni degli sviluppatori e le relazioni tra refactoring e qualità del codice [5]. I risultati mostrano che la rimozione dei code smell non è la motivazione primaria per la maggior parte delle attività di refactoring: gli sviluppatori intervengono più frequentemente per facilitare l'aggiunta di nuove funzionalità o per migliorare la comprensibilità del codice in vista di modifiche successive.


Code smell come euristica diagnostica

Il termine code smell è stato coniato da Kent Beck durante la stesura del capitolo 3 di Refactoring [2], scritto in collaborazione con Fowler. Un code smell è un'indicazione superficiale che corrisponde tipicamente a un problema strutturale più profondo nel sistema. La distinzione è cruciale: uno smell non è un difetto in sé, ma un segnale euristico che suggerisce l'opportunità di un'indagine più approfondita. Non tutti gli smell richiedono intervento, e la decisione di applicare un refactoring implica un giudizio contestuale che tiene conto della frequenza del cambiamento, dell'estensione del codice coinvolto e del costo dell'intervento rispetto al costo dell'inerzia.

La tassonomia originale di Fowler e Beck comprende 22 smell, organizzati implicitamente per scala di impatto. Mäntylä e Lassenius hanno successivamente proposto una classificazione formale che raggruppa gli smell in categorie quali bloaters (Long Method, Large Class, Long Parameter List), object-orientation abusers (Switch Statements, Refused Bequest), change preventers (Divergent Change, Shotgun Surgery), dispensables (Duplicated Code, Dead Code, Lazy Class) e couplers (Feature Envy, Inappropriate Intimacy) [6]. Questa tassonomia ha il merito di rendere esplicita la relazione tra smell e proprietà di design violata: i bloaters indicano violazioni del principio di responsabilità singola, i change preventers segnalano accoppiamento eccessivo, i couplers rivelano allocazione impropria delle responsabilità tra classi.

L'utilità pratica dei code smell come guida al refactoring è stata oggetto di validazione empirica. Palomba et al. hanno dimostrato che la presenza di smell quali God Class e Feature Envy è statisticamente correlata a una maggiore probabilità di introduzione di difetti nelle classi affette [7]. Tuttavia, lo stesso studio evidenzia che non tutti gli smell hanno uguale impatto: alcuni, come Comments o Data Class, mostrano correlazioni deboli con i difetti, suggerendo che la prioritizzazione degli interventi di refactoring non può basarsi esclusivamente sulla presenza di smell, ma deve incorporare metriche di rischio e frequenza di modifica.

Un aspetto spesso sottovalutato riguarda la soggettività intrinseca nel riconoscimento degli smell. Studi empirici mostrano che sviluppatori con diversi livelli di esperienza identificano smell differenti nello stesso codice, e che il consenso inter-valutatore è moderato per smell come Long Method (dove la soglia di "troppo lungo" è contestuale) e più alto per smell strutturali come Duplicated Code [5]. Questa variabilità implica che l'adozione di strumenti di rilevamento automatico, come PMD, SonarQube o strumenti basati su analisi statica, può ridurre l'inconsistenza ma non eliminarla, poiché la configurazione delle soglie richiede comunque un giudizio ingegneristico calibrato sul contesto del progetto.


Pattern di refactoring strutturale: dal metodo alla classe

I refactoring strutturali operano alla scala del codice sorgente, trasformando l'organizzazione interna di metodi, classi e moduli senza modificare il comportamento osservabile del sistema. Il catalogo di Fowler [2] organizza queste trasformazioni in gruppi funzionali; tra questi, i pattern più frequentemente applicati nella pratica industriale meritano un'analisi dettagliata dei meccanismi e delle condizioni di applicabilità.

Extract Function

Extract Function (denominato Extract Method nella prima edizione) è il refactoring più frequentemente applicato nella pratica, come confermato da studi empirici basati sull'analisi di commit in progetti open-source [8]. La meccanica prevede l'identificazione di un frammento di codice coeso all'interno di una funzione, la sua estrazione in una nuova funzione con nome descrittivo, e la sostituzione del frammento originale con una chiamata alla nuova funzione. Le variabili locali utilizzate dal frammento diventano parametri o valori di ritorno.

La semplicità apparente della trasformazione nasconde complessità semantiche non trascurabili. Il frammento estratto può contenere assegnazioni a variabili locali della funzione originale, riferimenti a stato mutabile condiviso, o istruzioni di controllo del flusso (come return o break) il cui significato cambia quando il contesto di esecuzione si sposta in una funzione separata. La preservazione del comportamento richiede che tutte queste dipendenze siano correttamente identificate e gestite, un compito che l'analisi statica del codice può automatizzare attraverso la costruzione del grafo delle dipendenze dei dati e del controllo del flusso.

Extract Class e Move Method

Quando una classe accumula responsabilità eterogenee, lo smell Large Class, il refactoring appropriato è Extract Class: si identifica un sottoinsieme coeso di campi e metodi, lo si sposta in una nuova classe, e si stabilisce una relazione di composizione tra la classe originale e quella estratta. Simmetricamente, Move Method (o Move Function) trasferisce un metodo dalla classe in cui risiede alla classe i cui dati utilizza prevalentemente, risolvendo lo smell Feature Envy.

Entrambe le trasformazioni richiedono un'analisi delle dipendenze che va oltre il singolo metodo. Lo spostamento di un metodo può rompere l'incapsulamento se il metodo accede a membri privati della classe di origine, e l'estrazione di una classe può introdurre cicli di dipendenza se la separazione delle responsabilità non è pulita. Fowler raccomanda un approccio incrementale: spostare un campo o un metodo alla volta, eseguendo i test dopo ogni micro-trasformazione, piuttosto che tentare un'estrazione completa in un singolo passo [2].

Inline e Replace

Il catalogo include anche trasformazioni inverse: Inline Function riassorbe una funzione il cui corpo è altrettanto chiaro del suo nome, Inline Class riunisce due classi quando la separazione non è più giustificata. Questi refactoring "inversi" sono essenziali perché il design ottimale non è statico: una decomposizione appropriata in una fase del progetto può diventare eccessiva in un'altra, quando i requisiti cambiano e le responsabilità si redistribuiscono.

Un pattern particolarmente efficace per la semplificazione della logica condizionale è Replace Conditional with Polymorphism, che trasforma catene di if/else o switch basate sul tipo in una gerarchia di classi con metodi polimorfici [2]. Questo refactoring elimina lo smell Switch Statements e sposta la variabilità dal controllo del flusso alla struttura del tipo, facilitando l'estensione futura senza modificare il codice esistente, un'applicazione diretta del principio open-closed.

Un secondo pattern rilevante per la riduzione dell'accoppiamento tra chiamante e chiamato è Introduce Parameter Object: quando una funzione accetta un lungo elenco di parametri, lo smell Long Parameter List, questi vengono raggruppati in un oggetto dedicato con campi nominati espliciti [2]. Il beneficio non è puramente estetico: il nuovo tipo rappresenta un concetto di dominio che può acquisire validazione, comportamento e semantica propria nel tempo. La sostituzione di firme con molti primitivi scalari con un oggetto ricco riduce la probabilità di errori di trasposizione degli argomenti (passare i parametri nell'ordine sbagliato), un tipo di difetto che il compilatore non rileva quando i parametri hanno tipo omogeneo.

Composizione di refactoring

Nella pratica, i refactoring raramente si applicano in isolamento. Kerievsky [3] dimostra che la migrazione verso un design pattern richiede tipicamente sequenze di 5-15 refactoring elementari: ad esempio, la trasformazione di una logica condizionale complessa nel pattern Strategy può richiedere una combinazione di Extract Method, Move Method, Extract Class, Replace Conditional with Polymorphism e Replace Constructor with Factory Method. La composizione sequenziale preserva il comportamento se ogni singolo passo lo preserva, ma il numero di passi amplifica il rischio di errore manuale, una motivazione fondamentale per il supporto strumentale discusso nelle sezioni successive.


Refactoring architetturale: Strangler Fig e Branch by Abstraction

I refactoring strutturali descritti nella sezione precedente operano alla scala del codice sorgente, tipicamente all'interno di una singola codebase. Quando l'obiettivo è la sostituzione o la trasformazione di componenti architetturali, moduli, servizi, sottosistemi, servono pattern che operano a una scala diversa, dove il vincolo principale non è solo la preservazione del comportamento ma anche la continuità del servizio in produzione.

Strangler Fig

Il pattern Strangler Fig è stato proposto da Martin Fowler nel 2004, ispirato dall'osservazione delle piante strangolatrici nelle foreste pluviali del Queensland [9]. La metafora descrive un processo in cui un nuovo sistema cresce gradualmente attorno a quello esistente, intercettandone progressivamente le funzionalità, fino a quando il sistema originale può essere rimosso. A differenza di una riscrittura completa (big bang rewrite), lo Strangler Fig mantiene il sistema esistente in funzione durante l'intera transizione, riducendo il rischio e consentendo un rollback granulare.

La meccanica del pattern si articola in tre fasi iterative. Nella prima fase (transform), si implementa una porzione della nuova funzionalità nel nuovo sistema. Nella seconda fase (coexist), il vecchio e il nuovo sistema operano in parallelo, con un meccanismo di routing, tipicamente un proxy, un API gateway o un layer di event interception, che dirige il traffico verso l'uno o l'altro in base a regole configurabili. Nella terza fase (eliminate), quando la nuova implementazione è validata, il traffico residuo viene migrato e il componente corrispondente del sistema legacy viene dismesso.

L'adozione industriale del pattern è ampiamente documentata nel contesto della migrazione da architetture monolitiche a microservizi. Shopify ha pubblicato un caso di studio dettagliato sull'applicazione dello Strangler Fig alla migrazione del proprio monolite Ruby on Rails, evidenziando come il pattern consenta di distribuire il lavoro di migrazione su team multipli operanti in parallelo su porzioni indipendenti del sistema [10]. Il vincolo critico è l'identificazione delle seam architetturali, i confini lungo i quali il sistema può essere decomposto senza introdurre dipendenze circolari tra vecchio e nuovo.

Branch by Abstraction

Branch by Abstraction è un pattern complementare allo Strangler Fig, che opera alla scala del codice sorgente anziché dell'infrastruttura. Il termine è stato introdotto da Paul Hammant (con credito a Stacy Curl per l'idea originale) e formalizzato da Fowler [11]. Il pattern consente di eseguire sostituzioni su larga scala, ad esempio la migrazione da una libreria a un'altra, o la riscrittura di un sottosistema, mantenendo il codice in uno stato rilasciabile in ogni momento.

La meccanica prevede cinque passi. Primo, si introduce un'astrazione (tipicamente un'interfaccia o una classe astratta) che incapsula l'interazione con il componente da sostituire. Secondo, si modificano tutti i client del componente affinché utilizzino l'astrazione anziché l'implementazione concreta. Terzo, si crea una nuova implementazione dell'astrazione. Quarto, si migrano i client dalla vecchia alla nuova implementazione, in modo incrementale e verificabile. Quinto, si rimuove la vecchia implementazione e, se l'astrazione non ha più ragione di esistere, si rimuove anche quella.

Il vantaggio fondamentale di Branch by Abstraction rispetto ai branch di versioning (feature branch nel sistema di controllo di versione) è che tutto il codice risiede nel trunk e il sistema è compilabile e rilasciabile in ogni momento. Non si accumulano merge conflict, e l'integrazione continua può validare ogni passo incrementale. Questo lo rende particolarmente adatto a contesti di trunk-based development e continuous delivery [11].

La relazione tra Strangler Fig e Branch by Abstraction è di complementarità: il primo opera a livello architetturale, direzionando il traffico tra sistemi coesistenti; il secondo opera a livello di codice, instradando le chiamate tra implementazioni coesistenti attraverso un'astrazione. In progetti complessi, i due pattern si applicano simultaneamente a scale diverse dello stesso processo di migrazione.


Il ruolo dei test nel refactoring sicuro

La definizione stessa di refactoring, trasformazione che preserva il comportamento osservabile, implica la necessità di un meccanismo per verificare tale preservazione. Nella pratica, questo meccanismo è costituito dai test automatizzati. La relazione tra test e refactoring è bidirezionale: i test rendono il refactoring sicuro, e il refactoring rende il codice più testabile.

Characterization test e il problema del codice legacy

Michael Feathers, in Working Effectively with Legacy Code (2004), definisce il codice legacy come "codice senza test", una definizione operativa che evidenzia come l'assenza di test sia il principale ostacolo al refactoring sicuro [12]. Per aggredire questo problema, Feathers introduce il concetto di characterization test: un test che cattura il comportamento attuale del codice, indipendentemente dal fatto che tale comportamento sia corretto o meno. L'obiettivo non è verificare la conformità a una specifica, ma creare una baseline che consenta di rilevare cambiamenti involontari.

Il processo di Feathers per il refactoring del codice legacy segue una sequenza disciplinata: (1) identificare i punti di cambiamento, (2) identificare i punti di test, (3) rompere le dipendenze per rendere il codice testabile, (4) scrivere characterization test, (5) eseguire le modifiche e il refactoring. Il passo (3) è il più delicato: per inserire test in codice fortemente accoppiato, è necessario applicare trasformazioni minimali e sicure, che Feathers chiama dependency-breaking techniques, come Extract Interface, Parameterize Constructor o Subclass and Override Method.

Il concetto di seam

Feathers introduce il concetto di seam: un punto nel codice in cui il comportamento può essere modificato senza alterare il codice stesso in quel punto [12]. Le seam consentono di iniettare comportamenti di test (mock, stub, spy) senza modificare il codice in produzione. Le seam possono essere di diversi tipi, object seam (sfruttano il polimorfismo), preprocessing seam (sfruttano le direttive del preprocessore), link seam (sfruttano il meccanismo di linking), e la loro identificazione è il primo passo per rendere testabile del codice legacy.

La potenza del modello delle seam risiede nel fatto che separa due preoccupazioni: dove si vuole modificare il comportamento (il punto di seam) e come si abilita tale modifica (il tipo di seam). Questa separazione consente di scegliere la tecnica di dependency-breaking più appropriata al linguaggio, al framework e al vincolo di tempo disponibile.

Copertura e falsa sicurezza

È necessario qualificare il ruolo dei test nel refactoring. Una suite di test con copertura del 90% delle linee di codice non garantisce che il 90% dei comportamenti sia verificato: la copertura misura l'esecuzione, non la verifica. Un test che esegue una funzione senza asserzioni sul suo output non rileva cambiamenti di comportamento. Per il refactoring sicuro, la qualità delle asserzioni è più importante della copertura numerica.

Inoltre, i test possono essere fragili: test che verificano dettagli implementativi anziché comportamenti osservabili si rompono ad ogni refactoring, generando falsi negativi che rallentano il lavoro e riducono la fiducia nella suite. La pratica raccomandata è strutturare i test attorno ai comportamenti pubblici, input, output, effetti osservabili, anziché attorno alla struttura interna del codice, in modo che i refactoring strutturali non richiedano la riscrittura dei test [2, 12].


Refactoring assistito da strumenti: dall'IDE ai modelli generativi

L'automazione del refactoring rappresenta un filone di ricerca e sviluppo che accompagna la disciplina fin dalle sue origini. Già la tesi di Opdyke [1] esplora la possibilità di automatizzare le trasformazioni preservando le precondizioni formali. L'evoluzione degli strumenti di refactoring si articola in tre generazioni, ciascuna con capacità e limitazioni distinte.

Prima generazione: refactoring integrato nell'IDE

Gli ambienti di sviluppo moderni, IntelliJ IDEA, Eclipse, Visual Studio, VS Code con language server, incorporano operazioni di refactoring come funzionalità native. Queste operazioni lavorano sull'Abstract Syntax Tree (AST) del codice e sfruttano informazioni di tipo per garantire la correttezza semantica della trasformazione. Un Rename Symbol eseguito dall'IDE aggiorna tutte le occorrenze del simbolo nel progetto, incluse le occorrenze in file diversi, i riferimenti nelle stringhe di documentazione e, nei linguaggi tipizzati, solo le occorrenze che si riferiscono alla stessa entità semantica, distinguendo tra omonimi in scope diversi.

La sicurezza di queste trasformazioni non è assoluta. Daniel et al. hanno sviluppato tecniche per il testing automatizzato dei motori di refactoring degli IDE, identificando difetti nelle implementazioni di Eclipse e IntelliJ per operazioni come Extract Method, Rename e Move [13]. I difetti includono casi in cui il refactoring introduce errori di compilazione, modifica il comportamento del programma, o produce codice semanticamente diverso da quello atteso. Sebbene le implementazioni migliorino ad ogni release, questi risultati ricordano che il refactoring automatizzato dall'IDE non dispensa dalla necessità di test.

Seconda generazione: rilevamento automatico del refactoring

Il rilevamento automatico del refactoring analizza la storia dei commit per identificare quali trasformazioni sono state applicate dagli sviluppatori. RefactoringMiner, sviluppato da Tsantalis et al., è lo strumento di riferimento in questo ambito: nella sua versione più recente supporta il rilevamento di oltre 100 tipi diversi di refactoring in codice Java, raggiungendo valori di precisione superiori al 99% e di recall superiori al 93% su benchmark consolidati [8]. Lo strumento opera confrontando l'AST di versioni successive del codice e applicando un algoritmo di matching che identifica le corrispondenze tra entità rinominate, spostate, estratte o fuse.

L'importanza di strumenti come RefactoringMiner va oltre la ricerca accademica. In contesti industriali, il rilevamento automatico consente di costruire metriche sulla frequenza e sulla tipologia dei refactoring applicati in un progetto, correlando queste metriche con indicatori di qualità come la densità di difetti, il tasso di modifica e la complessità ciclomatica. Questi dati informano decisioni sulla prioritizzazione degli interventi di refactoring e sull'allocazione delle risorse di manutenzione.

Terza generazione: refactoring assistito da LLM

La frontiera più recente è l'applicazione di modelli di linguaggio di grandi dimensioni (LLM) al refactoring del codice. EM-Assist, un plugin per IntelliJ IDEA presentato a FSE 2024, combina suggerimenti generati da LLM con analisi statica per raccomandare refactoring di tipo Extract Method [14]. Il sistema genera candidati attraverso il modello generativo, li filtra utilizzando tecniche di program slicing per eliminare quelli invalidi o non utili, e li presenta allo sviluppatore come azioni eseguibili all'interno dell'IDE.

La valutazione su 1.752 refactoring reali estratti da progetti open-source mostra un recall del 53,4% tra le prime cinque raccomandazioni, rispetto al 39,4% del miglior strumento precedente basato esclusivamente su analisi statica [14]. Tuttavia, la precisione del sistema rimane un problema aperto: molti suggerimenti generati dall'LLM sono sintatticamente validi ma semanticamente indesiderabili, estraggono frammenti di codice che non costituiscono unità logiche coese. Questo evidenzia una limitazione fondamentale dell'approccio: i modelli generativi operano su pattern statistici nel testo del codice, non sulla semantica del programma, e la nozione di "buon refactoring" richiede una comprensione delle intenzioni dello sviluppatore che eccede le capacità attuali dei modelli.

Uno studio sistematico più ampio, presentato a ICSE 2025, ha esaminato opportunità e limitazioni del refactoring guidato da LLM su una gamma più ampia di tipi di trasformazione [15]. I risultati confermano che gli LLM sono più efficaci per refactoring locali (rename, extract variable, extract function) e meno affidabili per trasformazioni che richiedono comprensione delle dipendenze inter-classe o conoscenza dell'architettura complessiva. La combinazione di suggerimenti LLM con verifiche basate su analisi statica e test emerge come il paradigma più promettente, in cui il modello generativo amplia lo spazio delle trasformazioni candidate e gli strumenti formali ne filtrano la correttezza.


Limiti, rischi e problemi aperti

L'adozione sistematica del refactoring nella pratica ingegneristica incontra limitazioni che è opportuno esplicitare.

Il primo limite è economico. Ogni attività di refactoring ha un costo diretto, tempo di sviluppo, e un costo opportunità, funzionalità non sviluppate durante l'intervento. La letteratura empirica mostra che gli sviluppatori applicano refactoring prevalentemente in concomitanza con l'aggiunta di nuove funzionalità, non come attività indipendente [5], suggerendo che il refactoring "puro" è difficile da giustificare senza un beneficio tangibile e immediato. Questo dato ha implicazioni per la pianificazione: integrare il refactoring nel flusso di sviluppo ordinario è più sostenibile che dedicarvi sprint separati.

Il secondo limite è la preservazione del comportamento in assenza di specifiche formali. Il refactoring presume di conservare il comportamento osservabile, ma la definizione di "osservabile" dipende dal contesto. Modifiche al logging, alla formattazione dell'output, all'ordine di iterazione su strutture dati non ordinate, o al timing di operazioni concorrenti possono essere considerate irrilevanti o critiche a seconda del sistema e dei suoi consumatori. Le precondizioni formali di Opdyke [1] e le verifiche automatiche degli IDE coprono proprietà sintattiche e di tipo, ma non catturano invarianti comportamentali arbitrarie.

Il terzo problema riguarda il refactoring di sistemi distribuiti. I pattern descritti in questo articolo, inclusi Strangler Fig e Branch by Abstraction, presuppongono la possibilità di intercettare e reindirizzare le interazioni tra componenti. In sistemi con comunicazione asincrona, consistenza eventuale e contratti di interfaccia impliciti, la nozione stessa di "comportamento preservato" diventa ambigua. La migrazione di un servizio che interagisce con decine di consumatori attraverso eventi richiede tecniche di versioning dei contratti e strategie di compatibilità retroattiva che eccedono il framework classico del refactoring.

Sul fronte dell'automazione, la sfida principale resta la semantica dell'intento. Gli strumenti attuali, dall'analisi statica degli IDE agli LLM, possono identificare cosa trasformare e come trasformarlo in modo sintattico, ma la decisione di se trasformare richiede la comprensione del contesto progettuale, delle priorità del team e della traiettoria evolutiva del sistema. Questa componente decisionale rimane intrinsecamente umana e difficilmente automatizzabile nel breve termine.

La scelta della scala di intervento è essa stessa un problema non banale. I refactoring strutturali (Extract Function, Extract Class) sono appropriati quando il problema è localizzato e la codebase è coperta da test. I pattern architetturali (Strangler Fig, Branch by Abstraction) diventano necessari quando la trasformazione attraversa i confini dei moduli o dei servizi e richiede continuità operativa durante la migrazione. La sovrapposizione tra le due scale, un refactoring strutturale che, ripetuto sistematicamente, produce una trasformazione architetturale, è il territorio meno formalizzato e più bisognoso di ricerca futura, sia sul piano delle metriche decisionali sia su quello del supporto strumentale [4, 15].


Riferimenti

[1] W. F. Opdyke, "Refactoring Object-Oriented Frameworks," Ph.D. Thesis, Department of Computer Science, University of Illinois at Urbana-Champaign, 1992. https://www.laputan.org/pub/papers/opdyke-thesis.pdf

[2] M. Fowler, Refactoring: Improving the Design of Existing Code, 2nd ed., Addison-Wesley, 2018. https://martinfowler.com/books/refactoring.html

[3] J. Kerievsky, Refactoring to Patterns, Addison-Wesley, 2004. https://www.industriallogic.com/refactoring-to-patterns/

[4] T. Mens and T. Tourwé, "A Survey of Software Refactoring," IEEE Transactions on Software Engineering, vol. 30, no. 2, pp. 126-139, 2004. https://doi.org/10.1109/TSE.2004.1265817

[5] E. A. AlOmar et al., "Behind the Scenes: On the Relationship between Developer Experience and Refactoring," Journal of Software: Evolution and Process, vol. 36, no. 1, 2024. https://doi.org/10.1002/smr.2395

[6] M. Mäntylä and C. Lassenius, "Subjective Evaluation of Software Evolvability Using Code Smells: An Empirical Study," Empirical Software Engineering, vol. 11, no. 3, pp. 395-431, 2006. https://mmantyla.github.io/BadCodeSmellsTaxonomy

[7] F. Palomba et al., "Do They Really Smell Bad? A Study on Developers' Perception of Bad Code Smells," in Proc. IEEE International Conference on Software Maintenance and Evolution (ICSME), 2014. https://doi.org/10.1109/ICSME.2014.60

[8] N. Tsantalis et al., "Accurate and Efficient Refactoring Detection in Commit History," in Proc. 40th International Conference on Software Engineering (ICSE), 2018. https://github.com/tsantalis/RefactoringMiner

[9] M. Fowler, "Strangler Fig Application," martinfowler.com, 2004 (revised 2019). https://martinfowler.com/bliki/StranglerFigApplication.html

[10] Shopify Engineering, "Refactoring Legacy Code with the Strangler Fig Pattern," 2023. https://shopify.engineering/refactoring-legacy-code-strangler-fig-pattern

[11] M. Fowler, "Branch by Abstraction," martinfowler.com, 2014. https://martinfowler.com/bliki/BranchByAbstraction.html

[12] M. Feathers, Working Effectively with Legacy Code, Prentice Hall, 2004. https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

[13] B. Daniel et al., "Automated Testing of Refactoring Engines," in Proc. 6th Joint Meeting of the European Software Engineering Conference and ACM SIGSOFT Symposium on the Foundations of Software Engineering (ESEC/FSE), 2007. https://dl.acm.org/doi/10.1145/1287624.1287651

[14] D. Alves et al., "EM-Assist: Safe Automated ExtractMethod Refactoring with LLMs," in Companion Proc. 32nd ACM International Conference on the Foundations of Software Engineering (FSE), 2024. https://arxiv.org/abs/2405.20551

[15] J. Lacerda et al., "LLM-Driven Code Refactoring: Opportunities and Limitations," in Proc. ICSE 2025 Workshop on Intelligent Development Environments (IDE), 2025. https://conf.researchr.org/details/icse-2025/ide-2025-papers/12/LLM-Driven-Code-Refactoring-Opportunities-and-Limitations

Refactoring Patterns

Raccontaci la situazione. Rispondiamo entro 24 ore nei giorni lavorativi.

Tweaks

Light mode
Atmospheric (glass)
Client logos
Terminal hero