設計のための問題の捉え方

magnolia_k_氏:実は今日で「設計Night」に出たの2回目なんですね。1回目は、今年の3月ぐらいに吉祥寺の居酒屋で、しんぺいさんのブログを見せながらみんなで酒を飲む「酔いどれ設計Night」という会をやって。7人ぐらいですごい楽しかったんですけど、今日は僕としんぺいさん以外、全員抽選に外れるという(笑)。

(会場笑)

悲しいことが起きましたので、その人たちには後で、ぜひスライド見てもらおうかなと思っています。

「設計のための、問題の捉え方」ということで、ドメイン知識の暗黙知を形式知にするというお話をさせていただきたいと思います。magnoliaと申します。よろしくお願いします。

先ほどしんぺいさんのほうから振り返りがあったんですけども、これまでの設計に関する議論というのをギュッと圧縮すると、この4点あるんじゃないかなという話です。

「設計とは問題を分割して構造化をすること」「適切に構造化をするためには、パターンと設計原則が有効であるということ」「パターンを適用する指針が設計原則であるということ」「設計原則の適用は、どこが変わりやすいポイントなのかを見極めるところから始まります」というのが、これまでの設計に関する議論ではないかということが挙げられます。

今日のお題なんですけども、先ほどしんぺいさんからもご紹介がありましたとおり、暗黙知のボトルネックがどうやって設計原則を使うかというところではなくて、問題そのものの捉え方に移動してきたというところで、この問題との向き合い方というのを、僕のほうで発表させていただきたいと考えております。

改めまして、magnoliaと申します。

Twitterがこういうアカウント(@magnolia_k_)なので、ぜひフォローしていただければと思います。PerlとScalaが好きで、設計についていろんなツイートをしております。吉祥寺.pmというイベントのオーガナイザーをしております。

吉祥寺.pmをちょっとご紹介をすると、3ヶ月に1回、平日の夜に開催していて、50人ぐらい参加して、2時間でトーク4本、LT6本、プラスアルファということで、密度が高めであっという間に過ぎていく2時間なんです。

テーマは限らず、ゆるやかな設定をしていて、懇親会参加率が非常に高くて、場所を探すのが大変です。

ということで、「今日お話すること」ということで、「そもそも問題を捉えるとは?」と「ドメイン知識の暗黙知を見つける」「暗黙知から形式知に変えていく」「継続的に問題を捉えるためには?」という話をさせていただきます。

「問題を捉える」とはなにか?

「そもそも問題を捉えるとは何か?」というところに立ち戻りますが、家にある本をいろいろ読んでみたところ、良い本がありましたのでご紹介します。

いかにして問題をとくか

有名な本なのでひょっとしたらみなさんご存じかもしれませんが、G. ポリアさんという数学者の方が書かれた『いかにして問題をとくか』という本があるんですけども。この本が非常に良い本で、これをひっくり返して、先月ぐらいずっと読んでたんですけども、この本に問題を解くステップが書いてあるんですね。

問題を解くステップというのは4つあります。「問題を理解すること」「計画を立てること」「計画を実行すること」「振り返ってみること」という4つが書かれているんですね。今回は問題を捉えるということなので、そのなかでも「問題を理解すること」に着目してみたいと思います。

「問題を理解すること」というところに何が書かれてるか超要約するとこう書いてあるんですね。

もう少し本旨は長いのでぜひみなさん買って読んでいただきたいんですけども。

「未知のものは何ですか?」。つまり「いまから解こうと思ってる問題は何ですか?」「その問題に与えられたデータは何ですか?」「その条件は何ですか?」というのが最初にきています。非常にすばらしいですね。

次に「その条件は十分ですか? 矛盾してませんか?」という問いかけがくるんですね。「なるほど」と、これは大事なお話ですね。

最後に「条件は書き表すことができるか?」って書いてあるんですね。「条件は書き表すことができるか?」「これ、すごい大事なことだな」って。僕が常々思っていることで、これに関するケーススタディを1つご紹介したいと思います。

ケーススタディ「年齢計算ニ関スル法律」

僕の大好きな法律ベスト3っていうのがあるんですけど、1つは年齢計算ニ関スル法律っていうのがあります。

右下に書いてあるんですけど、「年齢は出生の日からこれを起算する」と「民法第143条に規定」。これは期間を示すような法律なんですけど、それは「年齢の計算のときはこれを使ってくださいね」って書いている非常にシンプルな法律なんです。

「年齢の到達日を求める」ということを考えてみるんですけど、年齢が加算されるっていうのは、起算日に応当する日の前日の満了時午後12時なんですね。簡単に言うと誕生日の前の日なんですね。日本の法律では、誕生日の前の日に年を取るっていうのが原則になっていて、それぞれの法律で特別に書いてないかぎりは前の日に取るんですね。

ということで、「例えばこれを実装してみるとこうなります」ということで。

さっき「PerlとScalaが好き」って言ったのに、Rubyで書き換えたんですけど(笑)。

(会場笑)

黄色いところを見てもらうとわかるんですけど、birthdayっていうオブジェクトですね。Dateモジュールっていうのがあって、日付を管理するモジュールなんですけども。next_yearっていうメソッドがあって、これはその指定した何年後の日付を求めるっていうメソッドですね。previous_dayっていうのは、指定した日付の分だけ、前の日にカレンダーを戻していくっていう、そういうメソッドになっています。

なので、これでいくと、一番下の行に計算した結果を書いてるんですけど、「2000年の11月8日に生まれた人が18歳に到達するのは何日ですか?」っていうと、2018年の11月7日なんですね。今日ではなくて昨日年齢が到達しますっていうことがここではわかります。ということで、確かに実行すると正しい数字が出てくるんですね。

そこで、レビュアーからの指摘が出てきます。

厳しいレビュアーがやってくるんですけど、「あなた、日付の処理なんだから、誕生日が2月29日の人はちゃんとフォローしてるよね?」という厳しい指摘がやってきまして、「あ、何でしたっけ」っていうことになって、実際に入れてみると2月27日が出てきました。

「お、ちょっと間違ってんじゃねーか」「何なんだこれは?」という話になるんですけども。

これは何かというと、Rubyのnext_yearというのは、この真ん中ぐらいに書いてあるんですけど、2月29日の1年後はうるう年ではないので、そのときは2月28日を返すんですね。

Dateモジュールのnext_yearメソッドは、求めた日が存在しない日の場合、存在する日まで日を減算するんですね。

ちなみにこれはあくまでもRubyのDateモジュールの挙動であって、例えば昔のJavaのCalendarモジュール、PerlのTime::Pieceっていうモジュールではぜんぜん違うような挙動をするので、これはたまたまRubyがこうだというだけのことになっています。

シンプルなロジックは得意な条件の存在を隠してしまう

ということで、そう言われた僕はこう言うわけです。「いや、法律のどこにもそんな条件は書いてないじゃないですか」って。

ですが、そんなことを言っても先輩に怒られるだけなので、ちゃんとコードを書き直しました。ということで、うるう年を考慮した実装というので今回考えてみたのは先に1日減算してみました。

previous_dayの1っていうのを先に呼び出して、その後にnext_yearを呼び出せば、2000年2月29日に生まれた人が18歳に到達する日は、2018年の2月28日がちゃんと算出されますね。「ヤッター!」というかたちになるわけです。

なんですけども、よくよく振り返ってみると、年齢計算ニ関スル法律が示す計算方法っていうのが、非常にシンプルなルールで、2月29日生まれっていう特異な条件の対応ができてしまってるんですね。

よくあると思うんですね。シンプルなロジックで、いろんなバリエーションに対応してて、すごいうまいロジックがあると思うんですけど。そのうまいロジックって、それが逆に特異な条件の存在を隠しちゃうんじゃないかなっていうのが常々思っている問題です。

仮に、たまたま後者の実装を最初からやったとしても、コード上のどこにも2月29日って出てこないんですよね。やっぱりこれわかんないですよね。未来の人は絶対にわかりませんっていうことで、この「うるう年以外の2月29日を考慮する」という設計の意図が隠れてしまうということで、僕らはこれを知ってしまった以上は、レビュアーにちゃんと指摘されてしまった以上は、後世の人にこの意図を残すべきじゃないかな、というふうに思います。

テストを書いて意図を残す

じゃあどうしたらいいかっていうと、僕らはこういうテストを書けばいいよねということで、ちゃんとテストを書いて意図を残してみます。

test/unitを使ってテストを書いて、「こういう2月29日のパターンも計算しなきゃダメだよ」っていうことを残していくのがやるべきことなんじゃないかなと思うんですね。

あとは、場合によってなんですけども、2月29日に対するロジックをあえてそのプログラムに入れることで、意図のわかりやすさを優先する場合っていうのもあるかなと思います。それはたぶんプロジェクトにおける優先度、わかりやすさ、レビュアーの趣味、そのときの気分とかいろいろあると思うんですけど、意図を残すっていうことが非常に大事だなと思います。

ちなみに余談なんですけども、誕生日を起点とするような法律では、うるう年以外の2月29日に関する対応が明示的に法律で書かれてるんですね。例えば、道路交通法では「2月29日生まれの人は、うるう年以外はこうやって計算しなさい」ってちゃんと書いてあるので、そういう計算を実装しなさいって言われた場合には絶対バグらないですねと。「法律最高!」みたいな話になるわけです。

こういうことで「条件を書き出すっていうのはかくも難しいですね」というのが、僕らの悩みではないかなということが、言えるんじゃないかと。

ということで、ちょっとおさらいになるんですけども、「問題を捉える」というのはG. ポリアの言葉を借りれば、問題を構成する3つの要素「未知のもの」「データ」「条件」を適切にそろえるということ。それが問題を捉えたことになるんじゃないかなというのが、今日、私から紹介をしたい言葉になります。

ドメイン知識と暗黙知

ですが、現実にはこの3つの要素のうち「条件を適切にそろえること」と言われても超難しいんですよね。何が難しいかというと、ドメイン知識には「暗黙知」があるんですよ。

「何なんだ?」ということでお話をすると、ドメインというのは、分野、領域、範囲という意味なので、つまり「ドメイン知識」というのはいま取り組んでいる問題の分野・領域に関する知識全部ですね。とにかくありとあらゆるそのプロジェクトに関する知識が、ドメイン知識といえます。

暗黙知というのは、言語化・記号化されてない知ということです。難しく書いてますが、要は「書かれてません」ということですね。どこのドキュメントにも書かれてないし、なんのプログラムにも書かれてないことはやっぱり暗黙知だと。

つまり、ドメイン知識の暗黙知っていうのは、問題に関する“書き表されてない知識”のことをいうのではないかというのが、今日ご紹介したいものになります。

先ほどの年齢到達日の例でいくと、未知のところは「年齢到達日を算出するプログラムは何になりますか?」が解くべき問題で、データは「2000年11月8日生まれの人は、2018年11月7日に18歳に到達しますよ」、条件としては「2月29日生まれはこうしてくださいね」っていうのが条件になるんですけど、これがパッと見るとよくわかりませんでしたねということですね。

あくまでもこれ、2月29日生まれなので、カレンダーのことなので所詮366個しかバリエーションはないので、これはたぶんみなさん、気が付く人は気が付くと思うんです。だけど、やっぱり現実の問題では気が付かないようなパターンもあれば、そういう難しい問題もけっこうあるんじゃないかなと、けっこう象徴的な問題としてご紹介させてもらいました。

ということで、問題を適切に理解するために必要な条件がドメイン知識の暗黙知として隠れてしまっている状態というのは、僕らが対応していかなければいけない一番の課題じゃないかなと思っています。