Recientemente estaba escribiendo un pequeño fragmento de código que indicaría de forma amigable para los humanos la antigüedad de un evento. Por ejemplo, podría indicar que el evento ocurrió "Hace tres semanas" o "Hace un mes" o "Ayer".
Los requisitos eran relativamente claros y este era un caso perfecto para el desarrollo basado en pruebas. Escribí las pruebas una por una, implementando el código para pasar cada prueba, y todo parecía funcionar perfectamente. Hasta que apareció un error en producción.
Aquí está el código relevante:
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.
Las pruebas verificaban el caso de un evento que ocurría hoy, ayer, hace cuatro días, hace dos semanas, hace una semana, etc., y el código se creó en consecuencia.
Lo que me perdí es que un evento puede ocurrir un día antes de ayer, mientras que fue hace un día: por ejemplo, un evento que sucedió hace veintiséis horas sería hace un día, mientras que no exactamente ayer si ahora es la 1 de la mañana. Más exactamente, es un punto algo, pero como delta
es un número entero, será solo uno. En este caso, la aplicación muestra "Hace un día", que obviamente es inesperado y no se maneja en el código. Se puede solucionar agregando:
if delta == 1:
return "A day ago"
justo después de calcular el delta
.
Si bien la única consecuencia negativa del error es que perdí media hora preguntándome cómo podría suceder este caso (y creyendo que tiene que ver con las zonas horarias, a pesar del uso uniforme de UTC en el código), su presencia me preocupa. Indica que:
- Es muy fácil cometer un error lógico incluso en un código fuente tan simple.
- El desarrollo impulsado por pruebas no ayudó.
También preocupante es que no puedo ver cómo se pueden evitar estos errores. Además de pensar más antes de escribir el código, la única forma en que puedo pensar es en agregar muchas afirmaciones para los casos que creo que nunca sucederían (como creía que hace un día es necesariamente ayer), y luego recorrer cada segundo por los últimos diez años, verificando cualquier violación de afirmación, que parece demasiado compleja.
¿Cómo podría evitar crear este error en primer lugar?