Definizione
Refactoring (o Code Refactoring) è la pratica di ristrutturare codice esistente per migliorarne design interno, leggibilità, e manutenibilità senza modificare il comportamento esterno osservabile. Il refactoring riorganizza la struttura del codice mantenendo identiche le funzionalità e output.
Il termine è stato popolarizzato da Martin Fowler nel libro “Refactoring: Improving the Design of Existing Code” (1999), che ha catalogato tecniche specifiche (Extract Method, Move Field, Replace Conditional with Polymorphism) applicabili sistematicamente. Il libro definisce refactoring come “a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior”.
Refactoring è fondamentale per gestire technical debt: permette di ripagare il debt senza fare big-rewrite rischiosi. È anche terzo step del ciclo Test-Driven Development (Red-Green-Refactor).
Obiettivi del refactoring
Migliorare design
Ridurre complexity: semplificare logica complessa, ridurre cyclomatic complexity, eliminare nested conditionals profondi.
Eliminare duplicazione: applicare DRY (Don’t Repeat Yourself), estrarre commonality in funzioni/classi riusabili.
Aumentare cohesion: raggruppare codice correlato, separare concern non correlati (Single Responsibility Principle).
Ridurre coupling: minimizzare dipendenze tra moduli, rendere component più independenti e sostituibili.
Migliorare leggibilità
Naming significativo: rinominare variabili/funzioni/classi con nomi descrittivi (“calculate_total_price” invece di “ctp”).
Estrarre intent: sostituire magic number con named constant, estrarre expression complesse in variabili con nome chiaro.
Rimuovere dead code: eliminare codice non utilizzato, commented-out code, feature flag scaduti.
Facilitare feature future
Preparare extension: refactoring rende più facile aggiungere feature. “Make the change easy, then make the easy change” (Kent Beck).
Ridurre brittleness: codice fragile (dove piccola change rompe molte cose) viene reso più robusto e modulare.
Refactoring Catalog (Fowler)
Extract Method
Estrarre porzione di codice in metodo separato con nome descrittivo.
# Before
def print_owing():
print_banner()
# print details
print(f"name: {self.name}")
print(f"amount: {self.get_outstanding()}")
# After
def print_owing():
print_banner()
print_details()
def print_details():
print(f"name: {self.name}")
print(f"amount: {self.get_outstanding()}")
Rename Variable/Method
Cambiare nome per riflettere intent.
// Before
let d = calculateTime();
// After
let elapsedTimeInDays = calculateTime();
Replace Conditional with Polymorphism
Sostituire switch/if-else con inheritance e polymorphism.
// Before
if (type == ENGINEER) { return monthlySalary; }
else if (type == SALESMAN) { return monthlySalary + commission; }
// After (con polymorphism)
abstract class Employee { abstract int payAmount(); }
class Engineer extends Employee { int payAmount() { return monthlySalary; } }
class Salesman extends Employee { int payAmount() { return monthlySalary + commission; } }
Extract Class
Quando una classe fa troppo, splittare in due classi con responsabilità separate.
Inline Method
Opposto di Extract Method: se metodo è triviale o usato una sola volta, incorporarlo nel chiamante.
Catalogo completo: Fowler ha documentato 70+ refactoring named nel libro e su refactoring.com/catalog.
Quando refactorare
Rule of Three (Don Robererts)
Prima volta: scrivi codice. Seconda volta: duplichi (con reluctance). Terza volta: refactor per eliminare duplicazione.
“Three strikes and you refactor”.
Continuous refactoring
Opportunistic refactoring: ogni volta che tocchi codice, lascialo un po’ più pulito. Boy Scout Rule: “leave the code cleaner than you found it”.
Preparatory refactoring: prima di aggiungere feature, refactor per rendere l’aggiunta facile. “Make the change easy (warning: this may be hard), then make the easy change” (Kent Beck).
Comprehension refactoring: mentre leggi codice per capirlo, refactor per renderlo più chiaro. Rinomina variabile, estrai metodo. Il refactoring documenta la tua comprensione.
Dedicated refactoring time
Tech debt sprints: ogni N sprint di feature, dedicare sprint a refactoring major. Controverso perché crea separazione tra “feature work” e “quality work”.
20% time: allocare 15-20% di sprint velocity a tech debt paydown, incluso refactoring. Più sostenibile di sprint dedicati.
Refactoring safety
Prerequisito: test automatici
Refactoring è safe solo con comprehensive test suite. I test verificano che behavior non è cambiato.
Workflow: run test (green) → refactor → run test (green). Se test fail dopo refactor, hai introdotto regression: undo e retry.
Test-first refactoring: se area non ha test, scrivere test prima di refactorare (characterization test). “Working Effectively with Legacy Code” (Feathers) ha tecniche.
Small steps
Refactoring efficace procede con micro-step: cambiamenti minuscoli, testati frequentemente (ogni 2-5 minuti). Se vai in rabbit hole, undo è easy.
Anti-pattern: big-bang refactor dove modifichi 50 file in 2 settimane. Rischio alto, hard to review, merge conflict inevitabili.
Version control discipline
Frequent commit: commit dopo ogni refactoring step che passa test. Message chiara: “Extract calculateTax method”.
Atomic commit: ogni commit contiene un refactoring logico, non mix di refactor + feature + bugfix. Facilita review e revert.
Refactoring vs. Rewrite
Refactoring (incrementale)
Modifica design preservando comportamento, un passo alla volta. Codebase rimane funzionante durante l’intero processo.
Pro: low risk, continuous delivery, gradual improvement. Contro: può essere slower per transformation radicali.
Rewrite (big-bang)
Riscrivere component/sistema da zero. Stoppi development su old codebase, scrivi new version, poi switch.
Pro: opportunità di redesign radicale, eliminare tutti legacy issue. Contro: altissimo rischio, feature parity difficile, “second-system syndrome”, tempo al mercato lungo.
When rewrite: technology stack obsoleto beyond salvage (es: migrazione da COBOL), architettura fundamentally broken, business requirement completamente diverso.
Preferenza: refactoring incrementale è quasi sempre preferibile. “Strangler Fig Pattern” (gradual replacement) è middle ground.
Tool support
IDE-automated refactoring
Modern IDE (IntelliJ, VS Code, Eclipse) hanno refactoring automatizzato:
- Rename symbol (con scope awareness)
- Extract method/variable/constant
- Inline variable/method
- Move class
- Change signature
Vantaggio: IDE garantisce che refactoring è behavior-preserving, aggiorna tutti reference.
Static analysis
Tool come SonarQube, CodeClimate, ReSharper identificano code smell e suggeriscono refactoring.
Metrics: cyclomatic complexity, code duplication, method length, class coupling.
Refactoring-aware VCS
Git non capisce refactoring: “rename file” appare come “delete + create”. Tool come Plastic SCM, GitLens hanno semantic diff che capiscono refactoring.
Considerazioni pratiche
Bilanciamento: non over-refactor. “Premature optimization is root of all evil” (Knuth). Refactor quando pain è reale, non anticipatorio.
Team agreement: refactoring significativo (architectural change) va discusso con team, non unilateral. Usare ADR (Architectural Decision Record).
Refactoring in PR: distinguere PR di refactoring da PR di feature. Reviewing 500-line refactor + 200-line feature insieme è cognitive overload. Splittare.
Legacy code: applicare refactoring a codebase senza test richiede prima scrivere test (characterization test). È upfront investment ma necessario per safety.
Metrics: tracciare code quality metric (complexity, duplication) nel tempo per verificare che refactoring sta migliorando codebase, non peggiorando.
Fraintendimenti comuni
”Refactoring significa riscrivere il codice”
No. Rewrite è diverso da refactoring. Rewrite cambia implementation wholesale, refactoring modifica struttura preservando behavior. Refactoring è incremental, rewrite è big-bang.
”Non abbiamo tempo per refactorare”
Paradosso: non refactorare rallenta development nel lungo periodo perché codice diventa sempre più complesso. “We don’t have time to go slow” (Kent Beck). Refactoring è investment che paga velocità futura.
”Refactoring è solo per codice legacy”
Falso. Refactoring è continuous practice anche su codice nuovo. Design emerge iterativamente, primo tentativo raramente è ottimale. TDD include refactoring come terzo step del ciclo (Red-Green-Refactor).
”Basta automated refactoring tool dell’IDE”
Tool aiutano ma non sono sufficienti. Automated refactoring copre mechanical transformation (rename, extract), ma design-level refactoring (cambiare architettura, applicare pattern) richiede human judgment. Tool sono enabler, non sostituto di skill.
Termini correlati
- Technical Debt: refactoring è principale tecnica per ripagare debt
- Test-Driven Development: refactoring è terzo step del ciclo TDD
- Code Review: reviewer spesso suggeriscono refactoring opportunities
- Pair Programming: pairing rende refactoring più coraggioso con safety net
Fonti
- Fowler, M. (2018). Refactoring: Improving the Design of Existing Code (2nd Edition)
- Fowler, M. Refactoring Catalog
- Feathers, M. (2004). Working Effectively with Legacy Code
- Kerievsky, J. (2004). Refactoring to Patterns
- Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship