Recentemente stavo scrivendo un piccolo pezzo di codice che indicherebbe in modo umano a quanto vecchio è un evento. Ad esempio, potrebbe indicare che l'evento è accaduto "Tre settimane fa" o "Un mese fa" o "Ieri".
I requisiti erano relativamente chiari e questo era un caso perfetto per lo sviluppo basato sui test. Ho scritto i test uno per uno, implementando il codice per superare ogni test e tutto sembrava funzionare perfettamente. Fino a quando un bug è apparso in produzione.
Ecco la parte di codice rilevante:
now = datetime.datetime.utcnow()
today = now.date()
if event_date.date() == today:
return "Today"
yesterday = today - datetime.timedelta(1)
if event_date.date() == yesterday:
return "Yesterday"
delta = (now - event_date).days
if delta < 7:
return _number_to_text(delta) + " days ago"
if delta < 30:
weeks = math.floor(delta / 7)
if weeks == 1:
return "A week ago"
return _number_to_text(weeks) + " weeks ago"
if delta < 365:
... # Handle months and years in similar manner.
I test stavano verificando il caso di un evento accaduto oggi, ieri, quattro giorni fa, due settimane fa, una settimana fa, ecc., e il codice è stato costruito di conseguenza.
Quello che mi è sfuggito è che un evento può accadere un giorno prima di ieri, pur essendo un giorno fa: per esempio un evento accaduto ventisei ore fa sarebbe un giorno fa, mentre non esattamente ieri se ora è l'1. Più esattamente, è un punto qualcosa, ma dal momento che delta
è un numero intero, sarà solo uno. In questo caso, l'applicazione visualizza "Un giorno fa", che è ovviamente inaspettata e non gestita nel codice. Può essere risolto aggiungendo:
if delta == 1:
return "A day ago"
subito dopo aver calcolato delta
.
Mentre l'unica conseguenza negativa del bug è che ho sprecato mezz'ora a chiedermi come questo caso potrebbe accadere (e credendo che abbia a che fare con i fusi orari, nonostante l'uso uniforme di UTC nel codice), la sua presenza è mi preoccupa Indica che:
- È molto facile commettere un errore logico anche in un semplice codice sorgente.
- Lo sviluppo guidato dai test non ha aiutato.
Anche preoccupante è che non riesco a vedere come potrebbero essere evitati tali errori. A parte pensare prima di scrivere il codice, l'unico modo che posso pensare è di aggiungere un sacco di asserzioni per i casi che credo non accadrà mai (come credevo che un giorno fa sia necessariamente ieri), e poi di passare in rassegna ogni secondo per negli ultimi dieci anni, verificando l'eventuale violazione di asserzione, che sembra troppo complessa.
Come potrei evitare di creare questo bug in primo luogo?