Skip to content

필사 모드: プログラマが時間について信じている嘘

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

はじめに — 嘘リストというジャンル

ソフトウェア開発には「プログラマがXについて信じている嘘」という有名な記事のジャンルがあります。名前についての嘘、住所についての嘘、そしてその中でも最も悪名高いのが時間についての嘘です。形式は単純です。私たちが「当然だ」とみなす命題を並べ、その一つ一つが実は誤りであることを示すのです。

このジャンルが人気なのは、これらの命題があまりにもっともらしいからです。「一日は24時間だ」という言葉に、誰が異を唱えるでしょうか。ところが、まさにその当然さが落とし穴なのです。私たちはこうした仮定を疑いなくコードに刻み、そのコードはたいていうまく動き、そしてある例外的な瞬間に静かに崩れます。

この記事は、時間についての嘘のうち、とりわけ頻繁に開発者を噛む四つを選び、それぞれに実際に起きたバグを一つずつ添えて説明します。この記事の目的は怖がらせることではなく、「自分のコードにこんな仮定が潜んでいないか」を自分で点検してもらうことです。時間を扱うツールが必要なら、このサイトの世界時計で複数の地域の時刻を並べて比較できます。

嘘 1 — 「一日は24時間だ」

最も根本的な嘘です。一日がちょうど24時間、すなわち86,400秒だという仮定は、いくつかの場合に外れます。

サマータイムのせいです。 サマータイムが始まる春のある日、時計は一時間前へジャンプします。その日は23時間の一日です。逆にサマータイムが終わる秋のある日は25時間の一日です。だから「今日の真夜中から明日の真夜中まで」を無条件に86,400秒として計算すると、年に二度間違えます。

さらに、国が標準時そのものを変えたり、日付変更線をまたいだりする特殊な場合には、一日の長さがもっと劇的に変わることもあります。実際、ある地域は政策変更で特定の日付をまるごと飛ばした歴史があります。

実際のバグ。 ある勤怠管理システムが、労働時間を「出勤時刻と退勤時刻の差」で計算していました。ところがサマータイムが終わって25時間になった日、徹夜勤務をした従業員の労働時間が、実際より一時間多く集計されました。逆にサマータイム開始日には一時間少なく集計されました。「一日=24時間、二つの時刻の単純な引き算=経過時間」という仮定が、サマータイムの前で崩れたのです。正しい方法は、二つの瞬間をUTCへ変換してから、その差を測ることです。UTCにはサマータイムがないので、経過時間が正確です。

嘘 2 — 「1分は60秒だ」

これもほとんど常に真ですが、常に真ではありません。先に述べたうるう秒(leap second)のせいです。

地球の自転速度は微細に不規則なので、原子時計に基づく時間と、自転に基づく時間のあいだに誤差が積もります。それを補正するために、国際機関がときおり一日の終わりに1秒を差し込みます。そんな日の最後の1分は、60秒ではなく61秒です。

うるう秒が挿入された日:
  23:59:59
  23:59:60   ← 正常に存在する時刻
  00:00:00

ほとんどのアプリケーションは、これを気にする必要はありません。しかし時刻を非常に精密に扱ったり、秒単位の計算を厳密に行ったりするシステムでは問題になります。

実際のバグ。 過去にうるう秒が挿入された瞬間、複数の大規模サービスが同時に障害に見舞われた有名な事件があります。一部のシステムのカーネルとアプリケーションが「23:59:60」という予期しない値に出会い、無限ループに陥ったり、CPUを100%で焼きつけて止まったりしました。「秒は0から59まで」という仮定が、コードの奥深くに埋め込まれていたのです。この事件のあと、多くの大規模インフラが、うるう秒を一日かけて少しずつ吸収する「leap smearing」方式を導入しました。61秒の1分をそもそもシステムに露出させない戦略です。

嘘 3 — 「時計は前へだけ進む」

時間が単調に前へだけ流れるというのは物理的な直観ですが、コンピュータの時計には当てはまりません。コンピュータの壁時計(wall clock)は後ろへ進みうります。

最もよくある原因はNTP(Network Time Protocol)同期です。コンピュータのローカルの時計は少しずつずれますが、NTPはこれを正確な時刻に合わせます。もしローカルの時計が実際より進んでいたら、NTPは時計を後ろへ戻します。その瞬間、いまがついさっきより過去になります。サマータイムの終了(秋に時計を一時間戻すこと)も地域時刻を巻き戻します。

実際のバグ。 あるサービスが、リクエストの処理時間を次のように測定していました。

start = 現在の壁時計時刻()
... リクエストを処理 ...
duration = 現在の壁時計時刻() - start

ふだんはうまく動いていました。ところが、ちょうど処理の途中でNTPが時計を後ろへ調整すると、durationが負になりました。この負の値がタイムアウト計算とメトリクス集計に流れ込み、あるリクエストは即座にタイムアウトし、ある統計はとんでもない値になりました。原因を突き止めるのに数日かかりました。ほとんど再現できなかったからです。教訓は明快です。経過時間を測るときは、決して後ろへ進まない**単調時計(monotonic clock)**を使わなければなりません。壁時計は「いま何時か」を知るときだけ使い、「どれだけ経ったか」は単調時計で測るのが原則です。

嘘 4 — 「タイムゾーンは固定オフセットだ」

Asia/SeoulはいつもUTC+9で、America/New_YorkはいつもUTC-5だと考えがちです。しかしタイムゾーンは固定オフセットではなく、時間とともに変わる規則の集合です。

最大の理由は、やはりサマータイムです。サマータイムのある地域は、年に二度オフセットが変わります。ニューヨークは冬はUTC-5、夏はUTC-4です。さらに各国は政治的・経済的な理由で時間政策を実際に変えます。サマータイムを新たに導入したり廃止したりし、さらには標準時のオフセットそのものを変更したりします。こうした変更は思っているより頻繁に、ときには数週間前の短い予告とともに起こります。これらすべての変更の履歴を収めているのが、IANAタイムゾーンデータベースです。

実際のバグ。 あるグローバルなカレンダーアプリが、会議の時刻を保存するとき、タイムゾーン名の代わりにその瞬間のオフセットだけを保存していました。ユーザーが「3か月後の午前10時(ニューヨーク)」に会議を入れると、アプリは当時のオフセットであるUTC-4を一緒に固めて保存しました。ところがその3か月のあいだにサマータイムが終わってニューヨークがUTC-5に変わると、保存された会議が実際の壁時計で午前9時に表示されました。ユーザーたちが会議に一時間早く現れる事故が起きたのです。

正しい方法は、未来のイベントをオフセットではなくタイムゾーン名(America/New_York)とともに保存することです。そうすれば、サマータイムの規則がどう変わろうと、表示するときに常に正しい壁時計の時刻へ再計算されます。

その他の嘘 — 手短に一巡り

上の四つのほかにも、「時間についての嘘」リストには有名な項目が多くあります。いくつかだけ手短に触れます。

  • 「一つの国は一つのタイムゾーンを使う。」 いいえ。アメリカ、ロシア、オーストラリアのように複数のタイムゾーンを持つ国は多いです。逆に中国のように国土が広くても単一のタイムゾーンを使う国もあります。
  • 「タイムゾーンのオフセットは時間単位だ。」 いいえ。インドはUTC+5:30、ネパールはUTC+5:45のように、30分・45分単位のオフセットが存在します。オフセットを整数の時間だと仮定したコードは、こうした地域で壊れます。
  • 「二つのタイムスタンプを比較すれば時間の順序がわかる。」 分散システムでは違います。異なるサーバーの時計がずれていると、あとに起きたことのタイムスタンプが、より早く記録されうります。
  • 「うるう年は4年ごとに来る。」 おおむね正しいですが例外があります。100で割り切れる年はうるう年ではなく、そのうち400で割り切れる年はまたうるう年です。2000年はうるう年でしたが、1900年は違いました。
  • 「真夜中は常に存在する。」 いいえ。一部の地域では、サマータイムの切り替えのため、ある日付の00:00が存在しなかったことがあります。

これらの項目に共通するのは、すべて「たいていの場合は真だが例外がある」命題だということです。そしてソフトウェアのバグは、いつもその例外から生まれます。

なぜ私たちは騙され続けるのか

ここで根本的な問いを投げてみましょう。なぜ開発者はこうした嘘に繰り返し騙されるのでしょうか。いくつかの理由があります。

第一に、これらの仮定は日常の経験と一致します。 私たちの多くは、サマータイムがないか、あってもあまり気にしない環境で暮らしています。だから一日が24時間でないかもしれないというのが直観に反します。

第二に、これらのバグはふだんはうまく隠れています。 サマータイムの切り替えは年に二度だけ、うるう秒は数年に一度、NTPの時計の巻き戻しは予測しにくいものです。だからテストではめったに表面化せず、本番環境の特定の瞬間にだけ噴き出します。再現が難しいということはデバッグも難しいということです。

第三に、言語とライブラリが落とし穴を放置しています。 多くの言語の既定の日付型は、タイムゾーン情報のないnaiveな形式をたやすく許し、壁時計と単調時計をはっきり区別してくれません。だから誤った選択が既定値になりやすいのです。

この三つが重なって、時間バグは「よく作り込まれるが、まれにしか発現せず、捕まえにくい」という特有の性質を持ちます。

嘘に騙されない習慣

リストを暗記するだけでは足りません。実務でこの落とし穴を避ける習慣へ移さなければなりません。

  • 保存はUTCで。 サマータイムとオフセット変更の影響を受けない単一の基準で保存すれば、「一日=24時間」や「固定オフセット」のような仮定への依存が減ります。
  • 経過時間は単調時計で。 「時計は前へだけ進む」という嘘への防御です。
  • 未来のイベントはタイムゾーン名とともに。 「タイムゾーンは固定オフセット」という嘘への防御です。
  • タイムゾーンの規則をハードコードせず、IANAデータベースを使う。 そして最新に保つ。
  • テストに極端なケースを入れる。 サマータイム切り替え日、30分・45分オフセットの地域、うるう年の境界、日付変更線の近辺をわざとテストします。
  • 「この値は絶対の瞬間か、暦の日付か」を常に問う。 この一つの問いが、多くの型選択の誤りを防ぎます。

おわりに

「プログラマが時間について信じている嘘」というリストが長く語り継がれる理由は、それが単なるトリビアではなく、実際のバグの地図だからです。一日が24時間であることも、1分が60秒であることも、時計が前へだけ進むことも、タイムゾーンが固定オフセットであることも、すべて「たいていの場合は真」であるにすぎず、「常に真」ではありませんでした。

核心は、これらの嘘を一つ一つ暗記することではなく、その裏にある原理を理解することです。時間は純粋な物理量ではなく、人間の取り決めが幾重にも積み重なった概念であり、その取り決めは地域ごとに異なり、時が経てば変わります。この事実を受け入れ、UTC保存、単調時計、IANAデータベースといった防御の習慣を身につければ、これらの嘘のほとんどはもうあなたのコードを噛めなくなります。複数の地域の時刻を自分で比較してみたいなら、世界時計を開いて、これらの嘘が実際にどう現れるかを確かめてみてください。

参考資料

현재 단락 (1/53)

ソフトウェア開発には「プログラマがXについて信じている嘘」という有名な記事のジャンルがあります。名前についての嘘、住所についての嘘、そしてその中でも最も悪名高いのが時間についての嘘です。形式は単純です...

작성 글자: 0원문 글자: 5,489작성 단락: 0/53