Recentemente, eu estava escrevendo um pequeno pedaço de código que indicava de maneira amigável ao ser humano quantos anos um evento tem. Por exemplo, isso pode indicar que o evento aconteceu "Três semanas atrás" ou "Um mês atrás" ou "Ontem".
Os requisitos eram relativamente claros e esse era um caso perfeito para o desenvolvimento orientado a testes. Eu escrevi os testes um por um, implementando o código para passar em cada teste, e tudo parecia funcionar perfeitamente. Até que um bug apareceu na produção.
Aqui está o trecho de 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.
Os testes estavam verificando o caso de um evento acontecendo hoje, ontem, quatro dias atrás, duas semanas atrás, uma semana atrás, etc., e o código foi construído de acordo.
O que eu perdi é que um evento pode acontecer um dia antes de ontem, sendo um dia atrás: por exemplo, um evento ocorrendo vinte e seis horas atrás seria um dia atrás, e não exatamente ontem, se agora for 1h. Mais exatamente, é um ponto alguma coisa, mas como o delta
é um número inteiro, será apenas um. Nesse caso, o aplicativo exibe "Um dia atrás", o que é obviamente inesperado e não tratado no código. Pode ser corrigido adicionando:
if delta == 1:
return "A day ago"
logo após calcular o delta
.
Embora a única consequência negativa do bug seja que eu perdi meia hora me perguntando como esse caso poderia acontecer (e acreditando que isso tenha a ver com fusos horários, apesar do uso uniforme do UTC no código), sua presença está me incomodando. Indica que:
- É muito fácil cometer um erro lógico, mesmo em um código-fonte tão simples.
- O desenvolvimento orientado a testes não ajudou.
Também preocupante é que não consigo ver como esses erros podem ser evitados. Além de pensar mais antes de escrever código, a única maneira de pensar é adicionar muitas afirmações para os casos que acredito que nunca aconteceriam (como eu acreditava que um dia atrás é necessariamente ontem) e, em seguida, percorrer cada segundo para nos últimos dez anos, verificando qualquer violação de afirmação, que pareça muito complexa.
Como eu poderia evitar criar esse bug em primeiro lugar?