Sul portatile di ogni sviluppatore c’è almeno un adesivo con una sigla. Tre sono particolarmente famose, e sembrano usciti da un biscotto della fortuna: KISS, YAGNI e DRY. Tienilo semplice. Non ti servirà. Non ripeterti.
Si citano nelle code review per vincere le discussioni, e quasi sempre un po’ a sproposito. Così la mucca è tornata alle fonti: da dove viene ogni sigla, cosa dice davvero e cosa non dice. Con esempi in Python, tutti eseguiti davvero, e una fattoria che vende latte, che è la cosa più seria che conosciamo.
| Sigla | Significa | Nasce | Senso vero | Usata male come |
|---|---|---|---|---|
| KISS | Keep it simple, stupid | Kelly Johnson, Skunk Works della Lockheed | Meno pezzi, meno modi di rompersi | «Non voglio imparare niente di nuovo» |
| YAGNI | You aren’t gonna need it | Extreme Programming, fine anni ’90 | Non costruite funzioni per un futuro che state indovinando | «Niente test, niente refactoring, niente design» |
| DRY | Don’t repeat yourself | The Pragmatic Programmer, 1999 | Ogni conoscenza vive in un posto solo | «Fondete due righe qualsiasi che si somigliano» |
KISS: keep it simple, stupid
KISS non viene dal software. Viene dagli aerei. La fonte più nota è Kelly Johnson, l’ingegnere che fondò gli «Skunk Works» della Lockheed, l’officina segreta che costruì l’aereo spia U-2 nel 1955 e poi l’SR-71 Blackbird, l’aereo più veloce e più alto del suo tempo. Nel ricordo scritto nel 1995 per la National Academy of Sciences americana da Ben Rich, che lavorò con lui agli Skunk Works, tra i motti di Johnson ci sono «Se funziona, non aggiustarlo» e questo:
«Keep it simple, stupid (KISS) è il nostro promemoria costante.» (Tienilo semplice, stupido.)
La frase girava in quegli anni: un dizionario dello slang americano registra un «Project KISS» della Marina degli Stati Uniti già nel 1960. Il senso è lo stesso. Se si può tenere semplice un aereo a Mach 3, si può tenere semplice anche la vostra fattura.
A proposito di fatture. Ecco come si calcola il prezzo di 12,5 litri di latte a 1,20 € al litro, scritto da qualcuno che ha appena letto un libro sui design pattern:
from abc import ABC, abstractmethod
class PricingStrategy(ABC):
@abstractmethod
def price(self, liters: float) -> float: ...
class StandardMilkPricingStrategy(PricingStrategy):
def __init__(self, rate: float):
self.rate = rate
def price(self, liters: float) -> float:
return round(liters * self.rate, 2)
class PricingStrategyFactory:
_registry = {"standard": StandardMilkPricingStrategy}
@classmethod
def create(cls, kind: str, **kwargs) -> PricingStrategy:
return cls._registry[kind](**kwargs)
class MilkInvoiceService:
def __init__(self, factory=PricingStrategyFactory):
self.strategy = factory.create("standard", rate=1.20)
def invoice(self, liters: float) -> float:
return self.strategy.price(liters)
print(MilkInvoiceService().invoice(12.5))Ed ecco la stessa cosa scritta dalla mucca:
def milk_price(liters, rate=1.20):
return round(liters * rate, 2)
print(milk_price(12.5))$ python3 over.py
15.0
$ python3 simple.py
15.0
$ wc -l over.py simple.py
33 over.py
5 simple.py
38 totalStesso risultato, 15 €. Una versione ha una classe astratta, una strategia, una factory, un registro e un servizio: 33 righe per fare una moltiplicazione. L’altra ne ha 5. Quando cambia il prezzo, tutte e due vanno modificate in un punto. Quando c’è un bug, però, una delle due ha cinque posti in cui nasconderlo.
YAGNI: you aren’t gonna need it
YAGNI nasce nell’Extreme Programming alla fine degli anni ’90. Martin Fowler racconta com’è cominciata: sul progetto C3, Chet Hendrickson continuava a proporre funzionalità che un giorno sarebbero servite, e ogni volta Kent Beck rispondeva «non ti servirà». Ron Jeffries, un altro dei fondatori di XP, nel 1998 la riassunse così:
«Implementate le cose sempre quando vi servono davvero, mai quando prevedete soltanto che vi serviranno.»
Ecco una funzione di mungitura scritta da qualcuno che «prevede» molto:
def milk(animal, liters, species="cow", currency="EUR", blockchain=False, plugin=None):
if species == "goat":
raise NotImplementedError("goats: coming soon")
if species == "camel":
raise NotImplementedError("camels: phase 2")
if blockchain:
raise NotImplementedError("ask the investors")
if plugin is not None:
plugin.before_milking(animal)
return f"{animal}: {liters} L"Capre, cammelli, valute, plugin e una blockchain. Vediamo chi la chiama davvero, in tutto il progetto:
$ grep -rn "milk(" --include=*.py . | grep -v "def milk"
./evening.py:3:print(milk("Ercolina", 11.0, species="cow", currency="EUR"))
./morning.py:3:print(milk("Ercolina", 12.5, species="cow"))
./morning.py:4:print(milk("Carolina", 9.0))Tre chiamate. Tutte per mucche, tutte in euro, mai un plugin, mai una blockchain. Il parametro currency non è nemmeno usato dentro la funzione. La versione onesta è questa:
def milk(animal, liters):
return f"{animal}: {liters} L"Fowler spiega perché la versione «non si sa mai» costa più di quanto sembri, con quattro costi. Il costo di costruzione: il tempo passato a scrivere, testare e documentare i cammelli. Il costo del ritardo: quello che nel frattempo non avete costruito, e che i clienti aspettavano davvero. Il costo di mantenimento: ogni parametro inutile rende il codice più pesante da leggere e da cambiare, per sempre. E il costo di riparazione: quando i cammelli arriveranno davvero, i requisiti saranno diversi da quelli che avevate immaginato, e dovrete riscrivere tutto comunque. Il suo esempio è una startup che assicura spedizioni via mare: sta lavorando al rischio tempeste e sa già che tra sei mesi le servirà il rischio pirateria. YAGNI dice: la pirateria non costruitela adesso.
Ma attenzione, perché YAGNI è la più abusata delle tre. Fowler è esplicito: YAGNI non vale per il lavoro che rende il codice più facile da cambiare, come i test, il refactoring e un design pulito. Anzi: YAGNI funziona solo se il codice è facile da cambiare, perché tutta la scommessa è «lo aggiungeremo dopo, quando servirà». Se aggiungerlo dopo è un incubo, la scommessa è persa.
DRY: don’t repeat yourself
DRY viene da The Pragmatic Programmer di Andy Hunt e Dave Thomas, 1999. La definizione originale è più precisa dell’adesivo:
«Ogni conoscenza deve avere una rappresentazione unica, non ambigua e autorevole all’interno di un sistema.»
Notate la parola: conoscenza, non codice. Dave Thomas lo ha ripetuto in un’intervista del 2003: «La maggior parte delle persone pensa che DRY voglia dire non duplicare il codice. Non è quello il suo scopo.» La conoscenza di un sistema, dice, comprende anche gli schemi del database, i piani di test, il sistema di build e perfino la documentazione.
Ecco una vera violazione di DRY. Il prezzo del latte è scritto in due posti. Lunedì il prezzo sale a 1,35 €, e qualcuno aggiorna la fattura. L’etichetta sullo scaffale, invece, no:
# invoice.py
def invoice_total(liters):
return round(liters * 1.35, 2) # new price since Monday
# shelf.py
def shelf_label(liters):
return f"{liters} L - EUR {liters * 1.20:.2f}"$ python3 main.py
shelf: 12.5 L - EUR 15.00
invoice: 16.88Il cliente legge 15,00 € sullo scaffale e ne paga 16,88 alla cassa. Questo non è un problema di stile, è un reclamo. La correzione è dare al prezzo una casa sola:
# prices.py
PRICE_PER_LITER = 1.35 # the one and only place where the price lives
# invoice.py
from prices import PRICE_PER_LITER
def invoice_total(liters):
return round(liters * PRICE_PER_LITER, 2)
# shelf.py
from prices import PRICE_PER_LITER
def shelf_label(liters):
return f"{liters} L - EUR {liters * PRICE_PER_LITER:.2f}"$ python3 main.py
shelf: 12.5 L - EUR 16.88
invoice: 16.88Ora il caso opposto. Queste due funzioni sono identiche, carattere per carattere (non le abbiamo eseguite, sono qui per essere guardate):
def valid_ear_tag(code):
return len(code) == 8 and code.isdigit()
def valid_invoice_number(code):
return len(code) == 8 and code.isdigit()Codice duplicato! Il riflesso DRY dice: unitele in una valid_8_digit_code(). Ma non sono la stessa conoscenza. Una è una regola sulle marche auricolari delle mucche, l’altra è una regola dell’ufficio contabilità. Oggi per caso coincidono. Il giorno in cui cambiano le regole delle marche auricolari, e quelle delle fatture no, la funzione unificata mette su un parametro. Poi un altro. Poi un altro ancora:
def valid_code(code, *, is_ear_tag=False, country_prefix=False,
invoice_year=None, legacy_mode=False, vet_mode=False):
... # 60 lines of ifs. Nobody touches it. Nobody knows why.Sandi Metz ha descritto questa storia passo per passo nel 2016, in «The Wrong Abstraction». Il programmatore A vede una duplicazione ed estrae un’astrazione. Arriva un nuovo requisito che ci sta quasi, e il programmatore B aggiunge un parametro e un if. Si ripete finché nessuno ci capisce più niente. La sua conclusione è diventata famosa: «la duplicazione costa molto meno dell’astrazione sbagliata». E la cura va contro l’istinto: rimettete il codice duplicato dov’era, in ogni chiamante, togliete quello che a ciascuno non serve, e solo allora cercate l’astrazione giusta.
Come si capisce quando unire? Una buona regola pratica è la regola del tre, che Martin Fowler riporta in Refactoring (1999) come consiglio di Don Roberts: la prima volta che fate una cosa, la fate e basta. La seconda volta fate una smorfia davanti alla duplicazione, ma la fate lo stesso. La terza volta, fate refactoring. Con tre esempi sotto mano è molto più facile vedere cosa hanno davvero in comune.
Quando le tre litigano
Le tre sigle non vanno sempre d’accordo. DRY spinge ad astrarre; KISS e YAGNI spingono a non costruire quello che non serve. La funzione con cinque parametri in più qui sopra è quello che succede quando DRY vince troppo presto: per non ripetervi, avete costruito qualcosa che non è semplice e che serve casi che forse non arriveranno mai. In questi casi l’ordine di precedenza della mucca è questo: prima, funziona e si legge? (KISS); poi, serve oggi? (YAGNI); e solo dopo, c’è una conoscenza ripetuta? (DRY).
Muuu. 🐄
Fonti
Gli esempi Python sono stati eseguiti con Python 3.13.5; gli output mostrati sono quelli reali. I due validatori e la funzione con i flag sono illustrativi e non sono stati eseguiti.
- Ben R. Rich, “Clarence Leonard (Kelly) Johnson 1910–1990”, Biographical Memoir, NAS (1995)
- Wikipedia, “KISS principle”
- Ron Jeffries, “You’re NOT gonna need it!” (1998)
- Martin Fowler, “Yagni” (2015)
- Wikipedia, “Don’t repeat yourself”
- Bill Venners, “Orthogonality and the DRY Principle”, interview with Dave Thomas (2003)
- Sandi Metz, “The Wrong Abstraction” (2016)
- Wikipedia, “Rule of three (computer programming)”