Di recente ho scritto un piccolo codice che indicherebbe in modo umano quanti anni ha un evento. Ad esempio, potrebbe indicare che l'evento si è verificato "Tre settimane fa" o "Un mese fa" o "Ieri".
I requisiti erano relativamente chiari e questo era un caso perfetto per lo sviluppo guidato dai 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 che si verifica oggi, ieri, quattro giorni fa, due settimane fa, una settimana fa, ecc. E il codice è stato creato di conseguenza.
Quello che mi è mancato è che un evento può accadere un giorno prima, mentre era un giorno fa: per esempio un evento che si è verificato ventisei ore fa sarebbe un giorno fa, mentre non esattamente ieri se ora è l'una. Più esattamente, è un punto qualcosa, ma poiché delta
è un numero intero, sarà solo uno. In questo caso, l'applicazione visualizza "One days ago", che è ovviamente imprevisto e non gestito nel codice. Può essere risolto aggiungendo:
if delta == 1:
return "A day ago"
subito dopo aver calcolato il delta
.
Mentre l'unica conseguenza negativa del bug è che ho perso mezz'ora chiedendomi come potesse accadere questo caso (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 codice sorgente così semplice.
- Lo sviluppo guidato dai test non ha aiutato.
Inoltre è preoccupante il fatto che non riesco a vedere come si possano evitare tali bug. A parte pensare di più prima di scrivere il codice, l'unico modo in cui riesco a pensare è quello di aggiungere un sacco di asserzioni per i casi che credo non accaderebbero mai (come credevo che un giorno fa fosse necessariamente ieri) e poi scorrere ogni secondo per negli ultimi dieci anni, verificando l'eventuale violazione delle asserzioni, che sembra troppo complessa.
Come potrei evitare di creare questo bug in primo luogo?