La decomposizione è la suddivisione di un compito complesso in sottocompiti o funzioni più semplici e gestibili. Storicamente, la decomposizione è emersa come principio chiave della programmazione modulare: semplifica la gestione della complessità, la testabilità e il riutilizzo del codice.
Problema: Senza decomposizione, il codice diventa un "monolite": è difficile da leggere, mantenere e modificare, e diventa più complicato scrivere test e riutilizzare parti del programma.
Soluzione: In Python, la decomposizione viene realizzata tramite: suddivisione della logica in funzioni, classi, moduli; denominazione chiara; uso di composizione e astrazioni. Ciò consente di scrivere codice leggibile e scalabile.
Esempio di codice:
# Codice non compatto e monolitico numbers = [1, 2, 3, 4] squares = [] for n in numbers: if n % 2 == 0: squares.append(n**2) print(squares) # Variante decomposizione def is_even(n): return n % 2 == 0 def square(n): return n ** 2 def filter_and_apply(numbers, predicate, func): return [func(n) for n in numbers if predicate(n)] numbers = [1, 2, 3, 4] result = filter_and_apply(numbers, is_even, square) print(result)
Caratteristiche principali:
Una funzione può implementare tutta la logica di business se è piccola?
Spesso si pensa che se il compito è leggero, sia lecito scrivere "una funzione di 100 righe". Questo è un anti-pattern: anche un compito piccolo diventa complicato con i minimi cambiamenti; microfunzioni sono molto più facili da testare e mantenere.
Le funzioni con logica identica ma nomi diversi possono essere considerate decomposizione?
No, la duplicazione del codice è una cattiva decomposizione. Il codice ripetuto indica confini errati tra i sottocompiti. È sempre necessario estrarre la funzionalità ripetitiva in funzioni ausiliarie.
Esempio:
def add_user(): pass # logica def add_admin(): pass # la stessa logica di sopra!
È necessario decomporre i compiti ausiliari se vengono utilizzati solo in un luogo?
Sì, spesso tali funzioni possono semplificare il codice anche se sono usate una sola volta (ad esempio, estraendo condizioni o filtri in una funzione separata).
Un progetto di elaborazione dati era gestito tramite una enorme funzione di 300 righe. Ogni bug generava panico: impossibile capire rapidamente cosa e dove fosse successo, il testing era praticamente impossibile.
Pro:
Contro:
Stesso progetto, refactoring — il codice è suddiviso in piccole funzioni e classi basate sulla logica principale (lettura, validazione, elaborazione, scrittura dei dati).
Pro:
Contro: