Ich habe kürzlich ein kleines Stück Code geschrieben, das auf menschenfreundliche Weise anzeigt, wie alt ein Ereignis ist. Beispielsweise könnte dies darauf hinweisen, dass das Ereignis „vor drei Wochen“ oder „vor einem Monat“ oder „gestern“ stattgefunden hat.
Die Anforderungen waren relativ klar und dies war ein perfekter Fall für eine testgetriebene Entwicklung. Ich schrieb die Tests nacheinander und implementierte den Code, um jeden Test zu bestehen, und alles schien perfekt zu funktionieren. Bis ein Fehler in der Produktion auftauchte.
Hier ist der relevante Code:
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.
Die Tests überprüften den Fall eines Ereignisses, das heute, gestern, vor vier Tagen, vor zwei Wochen, vor einer Woche usw. stattfand, und der Code wurde entsprechend erstellt.
Was ich vermisst habe, ist, dass ein Ereignis vorgestern stattfinden kann, während es vorgestern war: Zum Beispiel wäre ein Ereignis vor sechsundzwanzig Stunden vorgestern, während es nicht genau gestern war, wenn es jetzt 1 Uhr morgens ist. Genauer gesagt, es ist ein Punkt etwas, aber da das delta
eine ganze Zahl ist, wird es nur eine sein. In diesem Fall zeigt die Anwendung "Vor einem Tag" an. Dies ist offensichtlich unerwartet und wird im Code nicht behandelt. Es kann behoben werden, indem Folgendes hinzugefügt wird:
if delta == 1:
return "A day ago"
kurz nach der Berechnung der delta
.
Die einzige negative Konsequenz des Fehlers ist, dass ich eine halbe Stunde damit verbracht habe, mich zu fragen, wie dieser Fall passieren könnte (und zu glauben, dass er trotz der einheitlichen Verwendung von UTC im Code mit Zeitzonen zu tun hat), aber seine Anwesenheit beunruhigt mich. Es zeigt an, dass:
- Es ist sehr einfach, einen logischen Fehler zu begehen, selbst in einem solch einfachen Quellcode.
- Testgetriebene Entwicklung hat nicht geholfen.
Ebenfalls besorgniserregend ist, dass ich nicht sehen kann, wie solche Fehler vermieden werden können. Abgesehen davon, dass ich mehr nachdenke, bevor ich Code schreibe, kann ich mir nur vorstellen, viele Asserts für die Fälle hinzuzufügen, von denen ich glaube, dass sie niemals auftreten würden (so wie ich dachte, dass ein Tag zuvor notwendigerweise gestern ist), und dann jede Sekunde für zu durchlaufen In den letzten zehn Jahren wurde nach Behauptungsverletzungen gesucht, die zu komplex erscheinen.
Wie könnte ich es vermeiden, diesen Bug überhaupt zu erzeugen?