最近,我正在编写一小段代码,以人类友好的方式指示事件的年代。例如,它可能表明该事件发生在“三周前”或“一个月前”或“昨天”。
需求相对明确,这是测试驱动开发的完美案例。我一个接一个地编写了测试,实现了通过每个测试的代码,一切似乎都正常运行。直到生产中出现错误为止。
这是相关的代码段:
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.
测试正在检查今天,昨天,四天前,两周前,一周前等事件发生的情况,并相应地构建了代码。
我错过的是,某事件可能在前一天发生,而可能是一天前发生:例如,发生在26小时前的事件可能是一天前发生,而如果现在是凌晨1点,则可能不是昨天,更确切地说,这是一点的东西,但由于delta
是整数,所以它将只是一个。在这种情况下,应用程序显示“一天前”,这显然是意外的,并且在代码中未处理。可以通过添加以下内容进行修复:
if delta == 1:
return "A day ago"
在计算完之后delta
。
尽管该错误的唯一负面影响是我浪费了半个小时才想知道这种情况的发生方式(并且相信它与时区有关,尽管在代码中统一使用了UTC),但它的存在却使我感到困扰。它表明:
- 即使在这样简单的源代码中,犯逻辑错误也是非常容易的。
- 测试驱动的开发无济于事。
同样令人担忧的是,我看不到如何避免此类错误。除了在编写代码之前要多思考之外,我唯一能想到的方法是为我认为永远不会发生的情况添加很多断言(例如我认为一天前一定是昨天),然后逐一遍历在过去的十年中,检查是否有任何违反声明的情况,这似乎太复杂了。
我如何才能避免一开始就创建此错误?