Magnolia Tech

いつもコードのことばかり考えている人のために。

ドメイン知識を隠すコード、隠さないコード

2021/12/20追記

指摘されて気づいてしまいましたが、間違ってますね... 以前スライドを書いた時に全然気づいていませんでした

反省のために消さずに、取り消して残しておきます


年齢計算ニ関スル法律」という法律がある。

明治三十五年法律第五十号(年齢計算ニ関スル法律) | e-Gov法令検索

とても短い法律で条文は3つしかない。

① 年齢ハ出生ノ日ヨリ之ヲ起算ス ② 民法第百四十三条ノ規定ハ年齢ノ計算ニ之ヲ準用ス ③ 明治六年第三十六号布告ハ之ヲ廃止ス

ポイントは①で、生まれた日から起算するので法律上は1年が経過した時に1つ歳を取ることになる。つまり、誕生日の前の日の24時に年齢が加算されるので、日単位でみると誕生日の前の日にもう年齢は進んでいる、ということになる。

同じ年の4月2日生まれの人と、4月1日生まれの人とでは小学校に入学する年度が違う、というのはよく聞く話だと思う。

この仕組みの面白いところは、その仕組み上、閏年に関する例外ルールを決める必要が無いところにある。もし、誕生日に年を取る、という法律だったら、閏年の2月29日生まれのことを考慮して、「ただし、閏年の2月29生まれの人は2月28日を以って年を取ることとする」みたいな例外ルールが必要になってしまう。

例えば道路交通法では誕生日を基準とするので、閏年生まれの2月29日生まれに関する規定が書かれている。


例えばRubyで指定した年齢に到達する日を計算するとしたら、以下のようなコードになる。

irb(main):001:0> require "date"
=> true

irb(main):002:0> birthday = Date.new(2000, 2, 29)
=> #<Date: 2000-02-29 ((2451604j,0s,0n),+0s,2299161j)>

irb(main):003:0> birthday.prev_day(1).next_year(18)
=> #<Date: 2018-02-28 ((2458178j,0s,0n),+0s,2299161j)>

しかし、間違って先にnext_yearを呼び出してしまうと、結果が変わってしまう。 (厳密に言うと2018年の2月29日の前の日、という解釈が正しい気もするけど、そんな日は存在しないので、このような計算になるはず)

irb(main):001:0> require "date"
=> true

irb(main):002:0> birthday = Date.new(2000, 2, 29)
=> #<Date: 2000-02-29 ((2451604j,0s,0n),+0s,2299161j)>

irb(main):003:0> birthday.next_year(18).prev_day(1)
=> #<Date: 2018-02-27 ((2458177j,0s,0n),+0s,2299161j)>

これがもし閏年以外の2月29日に関する例外が規定されていれば、上記のようなシンプルなコードにはならなかったはずで、とても美しい。

しかし、これにはこれで罠が有って、上記のうち、後者のようなコードを書いていても容易にバグを見つけられない可能性が潜在化する。

閏年には2月29日がある、という暦のドメイン知識が隠れてしまっているのだ。


このようにインプットデータにはいろいろな種類があるのに、非常にシンプルなコードで例外も含めたデータを上手く処理できてしまうことがある。こうなってくるといくらコードだけを見ても必要なドメイン知識はすべて分からない、ということが発生する。上記の例はひじょうにシンプルな例だけど、きっとこんなデータとコードの関係がいろいろなところに隠れていて、それがドメイン知識を隠蔽してしまうことが有る、という話でした。