Un emoji mi ha rovinato il pomeriggio.
Stavo elaborando 10.000 record di recensioni clienti attraverso una pipeline di analisi del sentiment il mese scorso. Workflow lineare: raschiare i dati, normalizzare il testo, passarlo a TextBlob, estrarre i punteggi di sentiment. Lo script di pulizia funzionava perfettamente sul mio dataset di test da 500 righe. Così l’ho spinto in produzione.
Dopo 48 minuti, tutto s’è fermato. Niente messaggio di errore. Niente eccezione. Niente avvertimento. Solo bloccato, in attesa indefinita sulla riga 6.842.
L’Incubo di Debug per Cui Nessuno Ti Prepara
Il mio primo istinto? Memory leak. Diecimila righe non dovrebbero mandare in crisi una macchina moderna, ma forse qualcosa si stava accumulando. Ho riavviato il processo con il memory tracking abilitato. Lo stesso comportamento—bloccato alla riga 6.842. Ogni singola volta.
Ho aperto il CSV e ho fissato manualmente la riga 6.842. Nome cliente: tutto bene. Testo della recensione: “Questo prodotto è 💩 non comprarlo.” Valutazione: 5 stelle (contraddittorio, certo, ma sintatticamente ok). Niente che mi saltasse all’occhio.
Poi l’ho visto.
L’emoji.
Eccomi qui dove la mia negligenza diventa rilevante. La mia logica di encoding—quella che avrebbe dovuto essere blindata—era impostata su encoding='latin-1' perché una versione precedente del dataset aveva caratteri spagnoli (ñ, á, é, ecc.) che andavano in pezzi con UTF-8. Così l’avevo bloccata.
Latin-1 gestisce i caratteri europei senza problemi. Ma gli emoji? Sono caratteri UTF-8 a più byte. Quando il lettore CSV di Python ha incontrato quell’emoji cacca, non ha potuto decodificarlo usando Latin-1. E invece di lanciare un’eccezione—invece di darmi qualsiasi feedback—si è fermato lì, silenziosamente.
Il mio dataset da 500 righe aveva zero emoji. I dati di produzione ne avevano 147 su 10.000 righe. Testare con dati reali avrebbe catturato questo immediatamente.
Questa è la parte infuriante. Se avessi testato con dati di produzione rappresentativi invece di un campione sanitizzato, avrei catturato questo prima del deployment. Ma non è quello che facciamo nel mondo reale, vero? Testiamo con dataset giocattolo e speriamo.
Perché i Fallimenti Silenziosi Sono Peggio dei Crash
Un crash sarebbe stata una benedizione. Un crash ti dà una traceback, un numero di riga, un indizio. Questo era un hang silenzioso—il peggior tipo di bug. Python ha semplicemente rinunciato senza dirmi perché. Il modulo csv non lancia un errore di encoding in questo scenario; si limita a bloccarsi per sempre cercando di decodificare qualcosa che non può gestire.
La fix, una volta che l’ho capita, era imbarazzantemente semplice:
import pandas as pd
df = pd.read_csv(
'reviews.csv',
encoding='utf-8',
on_bad_lines='skip' # Salta le righe malformate invece di andare in crash
)
# Se hai bisogno di strappare gli emoji (anche se io non l'ho fatto):
import re
df['review_text'] = df['review_text'].apply(
lambda x: re.sub(r'[^\x00-\x7F]+', '', str(x))
)
df.to_csv('cleaned_reviews.csv', index=False, encoding='utf-8')
Committati a UTF-8 dappertutto. Database, CSV, risposte API, log. Basta mischiare gli encoding perché i tuoi dati legacy avevano una stranezza dal 2019.
Ho iniziato anche a loggare il progresso aggressivamente—ogni 1.000 righe—così se qualcosa si rompe di nuovo, so esattamente dove senza dovermi perlustrare manualmente i CSV. Solo questo avrebbe ridotto il mio tempo di debug da 48 minuti a circa 8.
La Lezione Vera (Spoiler: Non Riguarda gli Emoji)
Sentimi, la cosa degli emoji è un aneddoto simpatico. A tutti piace una buona storia di guerra “mandato in crash da un emoji”. Ma il vero problema qui è la disciplina di testing.
Ho testato con 500 righe pulite, sanitizzate. La produzione aveva 10.000 righe con veri dati sporchi: emoji, casi limite Unicode, mojibake da export CSV andati male. Il mio set di test non rappresentava la realtà.