私は最近、イベントがどれくらい古いかを人間に優しい方法で示す小さなコードを書いていました。たとえば、イベントが「3週間前」または「1か月前」または「昨日」に発生したことを示すことができます。
要件は比較的明確であり、これはテスト駆動開発に最適なケースでした。テストを1つずつ作成し、各テストに合格するコードを実装しましたが、すべてが完全に機能するように見えました。本番でバグが現れるまで。
関連するコードは次のとおりです。
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.
テストでは、今日、昨日、4日前、2週間前、1週間前などに発生するイベントのケースをチェックし、それに応じてコードをビルドしました。
私が逃したことは、イベントは昨日の前日に発生する可能性があり、1日前であるということです:例えば、26時間前に発生するイベントは1日前であり、今が午前1時であれば正確には昨日ではありません何かですが、delta
は整数であるため、1つになります。この場合、アプリケーションには「1日前」と表示されますが、これは明らかに予期せず、コードでは処理されません。以下を追加することで修正できます。
if delta == 1:
return "A day ago"
を計算した直後delta
。
バグの唯一の否定的な結果は、このケースがどのように発生するのか疑問に思って30分浪費したことです(そして、コードでUTCが均一に使用されているにもかかわらず、タイムゾーンに関係していると信じています)が、その存在は私を悩ませています。次のことを示しています。
- このような単純なソースコードであっても、論理的な間違いを犯すのは非常に簡単です。
- テスト駆動開発は役に立ちませんでした。
また、気になるのは、このようなバグを回避する方法がわからないことです。コードを書く前にもっと考えることは別として、私が考えることができる唯一の方法は、決して起こらないと信じるケース(1日前は必然的に昨日だと思っていたように)に多くのアサートを追加し、それから毎秒ループすることです過去10年間、アサーション違反をチェックします。これは複雑すぎるようです。
そもそもこのバグを作成しないようにするにはどうすればよいですか?