J'écrivais récemment un petit morceau de code qui indiquerait de manière conviviale l'âge d'un événement. Par exemple, cela pourrait indiquer que l'événement s'est produit «Il y a trois semaines» ou «Il y a un mois» ou «Hier».
Les exigences étaient relativement claires et constituaient un exemple parfait pour un développement piloté par des tests. J'ai écrit les tests un par un, en implémentant le code pour réussir chaque test, et tout semblait parfaitement fonctionner. Jusqu'à ce qu'un bogue apparaisse dans la production.
Voici le morceau de code pertinent:
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.
Les tests vérifiaient le cas d'un événement survenu aujourd'hui, hier, il y a quatre jours, il y a deux semaines, il y a une semaine, etc., et le code a été construit en conséquence.
Ce qui m’a manqué, c’est qu’un événement peut se produire un jour avant hier, alors qu’il ya un jour: par exemple, un événement se produisant il y a vingt-six heures le serait il ya un jour, alors que ce n’est pas exactement hier si nous avons maintenant une heure du matin. quelque chose, mais puisque le delta
est un entier, ce ne sera qu'un. Dans ce cas, l'application affiche «Il y a un jour», ce qui est évidemment inattendu et non géré dans le code. Il peut être corrigé en ajoutant:
if delta == 1:
return "A day ago"
juste après le calcul du delta
.
La seule conséquence négative de ce bug est que j'ai perdu une demi-heure à me demander comment cette affaire pourrait se dérouler (en pensant qu'il s'agit de fuseaux horaires, malgré l'utilisation uniforme de l'UTC dans le code), mais sa présence me trouble. Il indique que:
- Il est très facile de commettre une erreur logique même dans un code source aussi simple.
- Le développement piloté par les tests n'a pas aidé.
Ce qui est également inquiétant, c'est que je ne vois pas comment on pourrait éviter de tels insectes. En plus de réfléchir avant d’écrire du code, la seule façon de penser est d’ajouter beaucoup d’affirmations pour les cas qui, à mon avis, ne se produiraient jamais (comme je le pensais il ya un jour, c’est nécessairement hier), puis de passer en revue chaque seconde ces dix dernières années, en recherchant toute violation d’affirmation, ce qui semble trop complexe.
Comment pourrais-je éviter de créer ce bogue en premier lieu?