Anatomia di un Refactoring
Vi è mai capitato di guardare del codice che avete scritto tempo fa e pensare “ma chi è stato quel barbaro?”. Beh, è esattamente quello che è successo a me qualche ora fa, quando ho ritrovato uno script Python che avevo scritto per analizzare i numeri del Lotto.
L’Inizio: Uno Script, Tante Speranze
Tutto è iniziato con un semplice script. Sapete, uno di quelli che scrivete di fretta, magari durante la pausa pranzo, pensando “tanto è solo per provare una cosa”. Era essenzialmente un ammasso di codice che prendeva dei dati da un CSV e faceva qualche previsione usando scikit-learn.
# Lo script originale, un vero capolavoro di "funziona non toccare"
from pandas import read_csv
from sklearn.tree import DecisionTreeClassifier
# ... altro codice che mi vergogno di mostrare ...
Non era niente di che, ma ehi, funzionava! E sapete cosa succede quando qualcosa funziona, no? Esatto: inizi ad aggiungere features. Ed è qui che le cose hanno iniziato a diventare interessanti (leggi: un casino).
Il Momento “Oh No”
È arrivato quel momento che tutti noi sviluppatori conosciamo bene: quando ti rendi conto che il tuo “piccolo script” sta diventando qualcosa di più grande e che forse, solo forse, dovresti iniziare a strutturarlo meglio.
La goccia che ha fatto traboccare il vaso? Quando ho provato ad aggiungere una semplice funzionalità per visualizzare le statistiche e mi sono ritrovato a dover modificare il codice in tre punti diversi, pregando che non si rompesse tutto.
L’Illuminazione: Design Patterns al Salvataggio!
Ed ecco il momento “eureka”: perché non trasformare questo script in qualcosa di più serio? Ho iniziato a ripensare la struttura usando alcuni design pattern. E no, non perché sono un fanatico dei pattern (ok, forse un po’), ma perché effettivamente avevano senso in questo caso.
Ho iniziato con lo Strategy Pattern per la parte di predizione. Sapete, invece di avere un unico algoritmo monolitico, perché non rendere tutto più modulare?
# Molto meglio, no?
class DecisionTreePredictor(PredictorInterface):
def predict(self, features: List) -> List[int]:
# Ora posso dormire la notte
La CLI: Perché i Data Scientists Meritano Cose Belle
A un certo punto mi sono chiesto: perché far inserire i parametri da linea di comando come dei barbari? E così è nata l’idea di creare una CLI interattiva. Sapete, una di quelle cose che quando le usi pensi “ah, però!”.
class LottoConsole(cmd.Cmd):
intro = """
--------------------------------------------------
Benvenuto in Oracolo!
Digita 'help' o '?' per la lista dei comandi.
--------------------------------------------------
"""
prompt = '(oracolo) '
E sì, ho passato più tempo del necessario a scegliere i colori dell’output. Ma dai, chi non l’ha mai fatto?
Statistiche: Perché i Numeri Sono Meglio se Sono Belli
Ho aggiunto un po’ di visualizzazioni ASCII. Sì, potevo usare matplotlib o qualche libreria più sofisticata, ma c’è qualcosa di poetico in un grafico fatto con caratteri ASCII:
01 |███████████ (42) <- Questo mi ha fatto perdere un'ora di debug
02 |████████ (31) <- Ma ne è valsa la pena
03 |██████████████ (55)
Testing: La Parte Noiosa (Ma Necessaria)
Ok, lo ammetto: all’inizio non avevo scritto test. Lo so, lo so, è male. Ma hey, ho rimediato! Ho scritto una suite di test completa, e indovinate un po’? Ho trovato bug che non sapevo nemmeno di avere.
def test_predict_command(mock_cli):
# Questo test mi ha fatto bestemmiare in aramaico antico
console.do_predict("01/01/2024 MI")
assert "Test Prediction Output" in fake_out.getvalue()
Migliorato ed aggiornato il container docker: Perché “Funziona Sul Mio PC” Non È Una Scusa
Ora basta un:
docker-compose run --rm oracolo
E voilà, funziona ovunque! (Beh, quasi ovunque. Windows fa sempre storia a sé.)
Conclusione: Ne è Valsa la Pena?
Dopo ore di refactoring, test scritti, cancellati e riscritti, e qualche crisi esistenziale sul naming delle variabili, posso dire che ne è valsa la pena? Assolutamente sì!
Ora ho un’applicazione che:
- Ha una struttura che ha senso
- È testabile (e testata!)
- Ha una CLI che non fa vergognare
- È facile da estendere
- Mi fa sentire un po’ meno in colpa quando dico di essere un programmatore
E la cosa più importante? Ho imparato che anche il codice più umile può evolversi in qualcosa di decente, con un po’ di amore (e tanto caffè).
P.S. Il codice è su GitHub, sentitevi liberi di dargli un’occhiata. E sì, accetto pull request, ma per favore, niente tabs vs spaces war nei commenti!
P.P.S. No, l’applicazione non vi farà vincere al Lotto. Ma almeno ora potete perdere con stile! 😉
Ah, e se vi state chiedendo perché l’ho chiamato “Oracolo”… beh, suonava meglio di “PrevisioniLottoInPythonVersione2FinalFinalForRealThisTime.py”!