オブジェクト指向でモジュール化するとはどういうことか?

増田亨氏(以下、増田):「オブジェクト指向でモジュール化する」というのは2番目のテーマですね。

ここはある意味ハードルでして。まずこの議論をするときは、ソフトウェアのモジュールはなんだという話をしたほうがいいと思っていて。

モジュールという言葉はあちこちで使われてしまっているので、いろんな定義があります。一応、今日のこの文脈の中では、「ソフトウェアのモジュールとはなんであるか」ということに対して、ソースコードを扱いやすい塊にグルーピングして、それぞれのソースコードの塊を1つのファイルとして書くと。

コンパイル型の言語であれば、そのソースファイル単位にコンパイルしてテスト・実行ができるようなものをモジュールと呼びましょう。感覚的にはそういうことです。

あとはモジュール間、ソースファイル間の依存関係を小さくするというのがモジュールの考え方。ファイルを分けることはある意味どうにでもできますが、ファイルを分けてもありとあらゆるファイルの参照について、常に同期が取れていないと、コンパイルすらできないというのは、いわゆるモジュールの設計としてはうまくいっていない。できるだけ1つのファイル単位でコンパイルできるのが理想的です。

もちろんソフトウェアなので、ある程度依存関係を持たなければいけなくなりますが、それをいかに小さくするか、いかに少なくするか。そういうかたちでファイルを分割していくというのがソフトウェアのモジュールの考え方です。

モジュール化の2つのアプローチ

モジュール化には、基本的には2つのアプローチしかなくて。入出力の処理の単位ごとにモジュール化するというのは、伝統的なやり方です。今日紹介するいわゆるオブジェクト指向のモジュール化のポイントは、計算する値の種類ごとにやります。

つまり、計算する値にはどんな種類があるのかということを最初に見つけて、その種類ごとにモジュールをどう用意しましょうと。オブジェクト指向の考え方はそういうことです。

一番単純で簡単な例はStringですね。文字列を扱いたいという。操作する対象の値に文字列という種類があって、それに対して文字列を操作するロジックをずらっと集めてきましょうと。

Stringをどこでどう入出力したい、ディスクに書き込みたい、画面から入力を取りたいという入出力の関心事は、Stringという単位でモジュール化するのではなく、あくまでも文字列、キャラクターの配列という値に対して、なにをしたいかというかたちで別のファイルにまとめます。

入出力単位のモジュールとは、入力を起点に、出力するための処理手順の単位でファイルを分けるというやり方で、もうちょっと開発方法論的に言えば機能分割。大機能、中機能、小機能というように、だんだん機能分割しながら、各機能の入出力を定義してあげて、それで入出力処理を書くという、そういう単位でファイルを書いていく。これは値に注目するのではなく、入出力の処理に注目してモジュール構造を作っていくと。

基本的にはここのやり方の場合、一連の処理は必ず時系列に並びますから、入出力に注目したモジュールについて、内部に並んでいる処理は時系列に呼ばれる順番に並びます。もしサブルーチンに切り出したとしても、時系列にどうやって呼び出すかという構造の上でサブルーチンライブラリができあがっていきます。

値の種類ごとのモジュール化

値によるモジュール化、値の種類によるモジュール化は、いつどういうタイミングで使われるかではなくて。Stringに対してサブストリングでやりたいのとlengthを知りたいのはどういう順番でやらなければいけないかというのは一切関係ないわけですよね。

Stringを扱いたいなら、サブストリングを取りたいし、lengthをチェックしたいときもあるし、空っぽかどうかをチェックしたいこともあるよねと。値の周りにロジックを並べて、それを1つのモジュールにします。

だから、入出力というイベントに対して時系列にモジュール化する従来のアプローチに対して、オブジェクト指向の考え方は、値の種類を見つけて、その値の種類でやりたいことのロジックをその周りに集めて1つのモジュールにします。

ということでこっちも一緒に説明してしまおうかな。

例えばゲームで言えば、まさに「レベル」「HP」「MP」「Exp」、防具・武器の種類の「Enum」というか「一覧」といったものが値です。扱いたい値としてある。その値に対してどんな判定がしたいのか、どんな計算がしたいのかということに注目して、グルーピングしながらモジュールを作っていく。そういうアプローチです。

計算のほうは、言葉としてはいろいろ出てきます。HPというのは増える・減るという言い方になりますが、ほかの数値だとなんでしょう? ちょっと言葉はわからないですね。

ただ、計算の種類はそんなにあるわけではなくて。まず一致/不一致の判定をして、大小の判定をします。次に、最大、限界値に対して収まっているか・収まっていないかというような判定をします。それから、足し算・引き算・かけ算・割り算。あとは値からリテラル表現への変換という、いわゆる値の文字列表現だったり、文字列表現を値に読み取り直したり、外部とやり取りしたりするときにはよく出てきますね。

値の種類はいっぱいあるけれど、(計算の種類は)このぐらいしかないんですね。例えば、レベルに対してやりたいことはなにかと言うと、大小判定や足し算はありますが、レベルとして引き算はするんですかね? 戻すからなにかあるのか。ただ、レベル同士のかけ算はない。レベルの割り算というのもたぶんないですね。

そういう意味では、いわゆるレベルを整数で表すとしたら、整数という値の種類と考えてしまうと、たぶん一通りなければ整数としては演算できません。「レベル」という値の種類をどうしたいんだといったときには、やりたい計算は限られてくる。

そういうふうに値の種類を特定して、その特定された種類に必要な計算だけを1つのモジュールにするということがオブジェクト指向のアプローチなんですね。

値の種類は多いです。計算の種類は少ない。計算の種類はこれだけだし、「これはどれが必要か?」「これいらねぇんじゃね?」というような話はこの程度の議論をすればいいだけだから、値の種類さえ見つけられれば、モジュール(クラス)はだいぶ見えてくる。

値の種類があって、ロジックがあって、結果を表す値の種類があります。

モジュール分割は、具体的には値の種類ごとにまずソースのコードを分けて、「レベル型」「HP型」「MP型」といった計算に使いそうな値の種類をとにかくどんどん候補として挙げていきます。それぞれに対して、さっき言った計算の種類を、取捨選択しながら当てはめていきます。

これも同じですね。流れとしてはゲームロジックを値の種類ごとにまず整理して、型を発見して。型とはいわゆる値の種類+計算の種類。それが集まったものを型という。その型について、実際にプログラミングコードをどうするかと言うと、クラスの名前で、内部的にはどういうデータの持ち方をするかはちょっと置いておいて、こういうロジックが必要だよねというメソッドを集める。

それから、クラス構文で記述した内容について、実行時のオブジェクトとして具体的な値を割り当てて、その具体的な値に対してクラスで宣言したメソッドでロジックを呼び出していく。これがオブジェクト指向の基本的な作り方なんですね。

だから、ドメインロジックには値の種類があって、ドメイン駆動設計とはその値に対して「どんな計算がしたいのか」ということの整理です。

そのドメインロジックについて、具体的にソースコードとしてどうモジュール化するのかと言ったら、入力を起点にした出力スクリプトではなく、値の種類に対して「こんな値に関心があって、こういうロジック・計算があると便利なんだよね」というかたちでモジュールを作っていくというのがオブジェクト指向の作り方であり、それがドメイン駆動設計のモジュール構造、オブジェクト指向によるモジュール構造でやろうという考え方の基本です。

だから、計算・判定というドメインロジックに焦点を合わせるということは、必然的に値の種類とその計算に焦点を合わせることなので、「それを実現するとしたらオブジェクト指向のモジュール化しかないのでは?」というのが基本的な発想なんですね。型というのは概念的なもので、例えばソフトウェアで書くとしたら、Javaでは実装がない、インターフェース宣言はわりと型をそのまま書いた感じになる。

クラスは、実際の計算ロジックや判定ロジックを置く場所として書くので、型とクラスだったら、どっちかというと型は仕様と言うのかなという考え方です。型の実装をクラス構文で書くというのがオブジェクト指向。用語的にはそういうことになるんですね。

オブジェクト指向のモジュール化 考え方とやり方

オブジェクト指向のモジュール化の考え方とやり方は、いっぱいあります。ドメインロジックに焦点を合わせてオブジェクト指向でモジュール化するという観点に立つのであれば、「値オブジェクト」「区分オブジェクト」「コレクションオブジェクト」という3つのパターンさえごく自然にできるようになったら、ほとんどの計算ロジック・判定ロジックはモジュール化されて、あまり迷わなくても「こうしてもみようか」と言うアイデアができます。

今日はこういう言葉がわからない人もいるかもしれないから、名著をご紹介します(笑)。

(会場笑)

ぜひこれを2章ぐらいまで読んでいただければ。

現場で役立つシステム設計の原則 〜変更を楽で安全にするオブジェクト指向の実践技法

ガチでやりたい人は(笑)。持っていらっしゃる方はいますか? バートランド・メイヤーの『オブジェクト指向入門』2分冊。

(会場挙手)

うわ、すごい! ガチでやろうか(笑)。

アプリケーション開発をするときに、これを読まなければアプリケーション開発ができないということはないです。でも、オブジェクトでいろいろ設計に迷ったり、「こういうものは、どういう考え方でやったらいいんだろう?」と迷ったりしたときに、オブジェクト指向の本で読むべきものはおそらくこの本です。

オブジェクト指向に対して、こんな考え方もこんな考え方もあるよね。それはオブジェクト指向以外の考え方も含めて、いろいろな選択肢を出して、いろいろな複数の評価基準を設けて、「この場合はこういうふうに考えるとこういう結果になる」というようなことが延々と書いてあるんですね。

だから、全部を頭から読んでいくとすぐ眠くなって嫌になってしまいます。とくにオブジェクト指向というのはピンと来ないんですが、設計に関して悩んだときに、オブジェクト指向では、例えば「例外処理は、そもそもどういう考え方から生まれてきているんだ?」というようなところまで本当に勉強しにいこうとしたら、網羅性という意味で、この本はたぶん一番いい本です。

ほかにも勉強したほうがいい本は山ほどあると思いますが、なにかあったときにガチに勉強しようと思ったら、この本は本当におすすめですね。私も「全部読んでいるか?」「全部わかっているんですか?」と言われたら、「違う」「ノー」としか答えようがないですが、「参考にしていますか?」「役に立っていますか?」と言われたら、もう完璧です。非常に参考になる。

インクリメントに設計する

「インクリメントに設計するとはどういうこと?」という3番目の話です。これも最初に言ったように、ドメインロジックの焦点を当ててオブジェクト指向でモジュール化するというのは、絶対にアップフロントにはできないんですね。インクリメントにやっていくしかない。最初からよい設計はできません。

当たり前ですが、そもそも開発を始めるとき、対象領域の知識が貧弱ですよね。もちろんみなさんはゲームに関してある程度のバックグラウンドを持っていて、ゲーム開発をされることが多いと思いますが、そうは言ったって新しいゲームを作ろうと思ったら、未知の領域や、未定の事項が山ほどあるから、開発を始めたときは、作ろうとしているゲームに関する知識がとても貧弱です。

それから、こういうこともみなさんは経験があると思います。最初に書いたコード、昔に書いたコードは、自分がいろいろ経験を積んだり、ほかの部分を作ってみたりしたあとでそこに戻ってきたら、絶対にもっといい書き方があるという。実際に書き直すかどうかは別として、学んでいると、そういうアイデアは絶対に出てきますよね。

だから、コードは一度書いたらそれが正解になっていることはまずなくて、時間とともに自分自身の経験も判断力が上がってくる。あるいは、ほかの人のセカンドオピニオン、サードオピニオンに触れる機会が増えれば増えるほど、「もっとこうできるんじゃないかな?」というふうに当然コードは変わってきます。

あとは、ソフトウェア開発において最初の要求は曖昧で、時間とともに具体的で詳細になっていくというのは、重力のような話なので、どうしようもないですよね。

ときどき、「ちゃんと仕様書を作ってくれたら作れる」と言うエンジニアがいるんですけれども、僕は、ちょっと悪い言葉で言うと「バカか」と。「そんなの無理だよ。ないものねだりだよ」と。そんなことができれば……本当に重力のようなもので、重力に逆らって飛び上がるのが僕の仕事だと思っているので。

時間とともにだんだんソフトウェアを育てていくしかなくなる。「最初から全部詳細に決まっていないから、アップフロントに決められるわけないよね」というような。

これはちょっと別の話で。要求が時間とともに変化するというのは誰かが悪いわけではなく、環境が変われば……例えばゲームだとリリースしてバグではないフィードバックが返ってくれば、あるいは新しいコンテンツが出てきたり、新しいタイプのゲームが出てくれば、それに合わせてこっちもなにか変えていかなければいけないということが当然起きるんです。これも重力のようなもので当然変わってきます。

こういうことに対して、ソフトウェアを作っていくというのは、アップフロントにすべてガチガチに決めて、コードを書いてテストするだけではなくて、設計を常に変更・拡張・改善し続けるということが、日常の仕事として重要になります。

コードの変更は設計面からの評価とセットである

そうですね。同じことを書いていますが、知識が広がってきます。掘り下げます。コードを書いたら見直していきます。詳細になっていったらそれをコードに反映して、変化したらコードに反映する。

コードを書き直すというのは簡単に1行if文をぶち込めばいいという話ではなくて。設計ごとにモジュール構造……あるいは関心事について、どこの関心・どの関心事をどのモジュールを置くかということは絶対にインパクトがあるので、コードをちょっと変えるということはモジュール構造の設計のやり直し、あるいは再確認かもしれない。

「このモジュールはここに置いておいてもよかったんだな。このモジュール設計いけてるじゃん」という再確認かもしれません。いずれにしても、視点・観点としては、コード1行の変化はモジュール構造としてどうなのかという設計観点でのアセスメント、評価というのが常にセットになっています。

そこをほったらかして、「とりあえずここにif文入れれば動くから」ということを100回、1,000回繰り返したら、とんでもないコードになってしまう。みなさんも経験としてそういうコードを見ていらっしゃると思います。

とにかくコードの1行の変更はモジュール構造とどこかで連動してくる。そういう目でいつもコードを変更していくということは、ある意味インクリメントの設計ということの具体例ですね。

あとは、確認になってしまいますが、そうは言ってもいろいろな値の組み合わせで、ゲームロジックや業務アプリケーションは複雑になります。そのときに複雑な組み合わせにいきなり格闘してもうまくいかないので。

パターンとしてははっきりしています。

「基本的な値の種類を見つけましょうね。HPだったらHPだけ見つけて、HPに関して、そこでHPの加算だけに閉じたロジックだけでまず型を作ってしまいましょう」というようなかたちでプリミティブな型をいっぱい集めていって、ある程度集まってきたら、その値と値の種類を組み合わせてロジックを置く。

この場合は「どっちに置こうか」というようにスッと収まればいいですが、だいたい収まらないことが出てくるので、だったら型A・型B、HP・MP・SPを一緒に持ったポイント、ステータスというか、そういう型を新たに第3のクラスとして見つけて、そこに置いておこうというようなかたちで発展させていく。

あとは、型が増えてくると整理がつかなくなってくるので、1つは意味的に、そのパッケージ構造でグループピングする。オブジェクト指向の場合、もう1つ強力な型のサブタイピングという……Aという型に対してサブタイプのA-1、A-2、A-3というような。

例えば「顧客型」に対して「子供」「老人」「一般」というように、サブの型でロジックが違う場合に、サブタイピングすることによってロジックを整理する、構造的に型を整理するというやり方があるので。

順番としてはここからいきなり入るのではなく、パーツになるもの、独立したものを見つけながら、だんだん組み合わせたり、グルーピングの構造を発展させていったりします。

ターン方式のバトルの設計アイデア

例えば、ターン方式のバトルがいいのかどうかというのは、今はちょっと置いておきますが、仮にターン方式でやろうとした場合、例えばターンはいろいろな状況の情報を持っていて、総合で複合的に判断しなければいけないので、おそらく個々の値を束ねたかたちで「ターン型」というような型ができるのではないかと。

バトルはターンがいくつか繰り返されるわけだから、それはターンの履歴というか、ターンのコレクションが集まってバトルのようになるのではと。ターンはもしかしたらオフェンシブなターンかディフェンシブなターンかという、「ターン」という型に対してサブタイプとして「オフェンス」「ディフェンス」という捉え方があるかもしれない。

バトルコンテキストとは、バトルが始まるときには、このバトルのときにスタートラインで持たなければいけない情報、対象になるような情報が山ほどあるので、それを「バトルコンテキスト型」というかたちでドーンと持ってしまって、あとは実際にそのバトルを実際にやる、実行するというような。

これは1つの設計アイデアなので、うまくいくかどうかということは、もちろん実際にコード書いて確認するという話です。例えばこういうアイデアがありそうですよね。

型のグルーピングの設計アイデア

あとは「ポイント」。さっき言ったように、これは人間から見て、名前からして明らかに「〇〇ポイント」「〇〇ポイント」「〇〇ポイント」になっているから、「ポイント型」という汎用な型のサブタイプであることは自明です。

ただ、設計として考えたときには、本当にそういうサブタイピングにすることがプログラムとしていい構造かどうかは別問題です。概念として、「ポイントという同じグループだよね」ということはあるけれど、「ポイント型」というかたちで1つの共通の型であると宣言することがプログラムの構造としていいかどうかは、やってみなければわからない。

アイテムもそうで、ある意味アンチパターン的に書きましたが、一応、食料・薬・巻物などは、「アイテム」というかたちで汎用のタイプにすることができて。「所持品」として、内部的にはリストのアイテムを持っているitemsというかたちで持つことはできます。

ただ、実際には、「なにか食べたい」「薬を使う」ということに対する……所持品に対して「薬を使う」といったときに、内部でたぶん「アイテムを操作して、薬を見つけて〜」というようなロジックが入ってきてしまいそうなんですね。

ということは、リストの食料、リストの薬、リストの巻物というように、最初からサブタイプではなく、特有タイプだけのリストを3つ並べるほうがいい。モジュールとしてわかりやすくなるのではないかという議論がある。

なにか「これ」という定番の答えがあるわけではなく、自分たちが作ろうとしているゲームのロジックやゲームの特徴に合わせながら、こういうものを見つけていく。それが、ドメインロジックに焦点を合わせたオブジェクト指向のモジュール化をインクリメントに設計していくという実際の活動の一部です、ということですね。

本当に繰り返しなんですよね。まず知識。とにかくドメインの知識を広げるということが最初にきて、それをコードで表現してみて、コードでいったん表現したら「動いた。ああ、よかった」で終わりではなくて、もっといいコードの書き方、もっといいモジュール化があるのではないかということを考える。

とくになにか追加や変更があったときには、モジュールとしてどうなんだということを常にワンクッション考えるようにして、ソフトウェアを成長させていく、設計を改善していくと。

ゲーム開発をドメイン駆動設計で進める際の障害

ゲーム開発を進めるときの障害として、そうは言っても入出力。やっぱりビジュアル、サウンド、インタラクション、通信が〜、データベースが〜。

これはテクロスさんと実際に京都でやったときに最初にぶち当たった壁で。これに対しては私もなかなか対抗する術がなくて。「ドメインロジックに集中しようよ」ということを伝えるのにだいぶ苦労して。少なくともチーム全体でそれが当たり前になるという段階まではなかなかいけなかったというのが正直なところですかね。

ただ、それでもやっぱりドメインロジックをオブジェクト指向的に表現するんだということに関しては、なにかしらをお伝えできたのかなとちょっぴり自負はしています。

あとは、たぶん大きな問題はフレームワーク。今はゲームを作るときにフレームワークがあるので、スクラッチで書くなんてたぶんないと思うんですね。

ただし、今のメジャーなフレームワークは、少し私が見せてもらっているもので、ほとんどが入力イベントハンドラーに対して、出力スクリプトが書けるという構造が前提になっています。だから、それをそのままそこにコードを書こうとしても、ドメインロジックの置き場所がなくなってしまうんですね。

だから、それをやるにはたぶんこのフレームワークを使ったときにドメインロジックの置き場所を「こういうふうに置こう」とアーキテクチャ的に一工夫・一捻りを加えた上で取り組んでいかなければ、結局は入力イベントにぶら下がった入出力ごとのモジュールにドメインロジックを書くはめになる。そこのところはちょっとチャレンジだなと。

あとは当たり前ですけれど、開発プロセスも、今言ったようなドメインロジック、ドメイン駆動設計の考え方で、開発のチーム・開発の関係者全員がそっちを向いているわけではないので。フェーズ分けや分業も当然やっているだろうし。

コードを書き始めるタイミングも、早い段階から見つけられると言っても、そもそも開発チームがある段階まで、コンセプトを作っている段階では出てこなかったり、「もうちょっとビジュアルができあがってきてからやろうよ」というような会社としての決定があればなかなかコードに書けなかったり。

あるいはコード変更の理由の混乱ですね。とにかく変えたいニーズはある程度動き始めたらボコボコ上がってくるんですけれど、それはモデル変更として考えるべきなのか、ビューの変更で済ませるべきなのか、あるいはモデルを変えてビューも変えるということをやるべきなのか、というようなことをちゃんと考えるのはなかなか難しい。

「いいから直して」「ここはすぐ直して」というようなことがどうしても出てくるので、そこに対してドメイン駆動設計でがんばろうとすると、けっこう障害になります。

ドメイン駆動設計を導入するためのアプローチ

最後に、ヒントというか、いくつかのアプローチです。

これは私らしいところで、思い切って飛び込む。少数精鋭で小規模で徹底してドメイン駆動設計で振り切る。学びのための実験プロジェクト。これは効果絶大です。ただし、「……」と書いてあるとおり、これはなかなかできない。できないので、一応選択肢はあるよと。「がんばってみる手もあるのではないか」ということで挙げておきます。

今度は、逆に振り切ると、従来のやり方と戦わない。オフィシャルに全体的にはやらずにこっそりと……例えば「HPの単純な足し算・引き算のロジックだけHP型で作ってみよう」「勝敗判定のtrue/falseを勝敗、勝ち・負けという、Enumというか列挙型で表現してみよう」というのを選択できる。そういうことをちょこちょこやっていくことで、じわじわ味わいながら広げていくというアプローチもあるのではないかなと。

それから、私が現実的だと思っていることとして……数値の計算ロジックだけは、加減乗除だけは必ず独自の型にラッピングしろよと。全部一緒にやる必要はないですが、コレクションの操作ロジックだけは、配列やコレクションの操作だけはいったんコレクションオブジェクトにラッピングして、そこに書くということだけは守ろうと。

あとはBooleanを返すのを禁止にして、必ずBooleanではなく判定結果を最低、列挙型の名前にする。可能であれば列挙型の中に振る舞いを持たせて、Booleanを判定したあとに「なにをしたいか」というところまで含めて返すオブジェクトを作ってあげる。

これは全部一緒にやる必要はなくて、最初は「こっそりと」というようなところから始めて、ある程度当たりがついてきたらこういうことをチームの約束事としてやってみるというのが現実的なやり方になるのではないのかなと私自身は思っています。でも、こういうことを考えるのはみなさん自身なので。

とにかく重要なのは、実際にコードを書いて、その結果を確認しながら議論を進めていくことです。机上の空論でドメイン駆動設計をやるか・やらないかという議論はやってもダメです。時間のムダだと思う。やるんだったらちょっとコードを書いてみて、コードでどうなるんだということを実際に確認した上で同じ土俵で議論をするというようなことをやるのが一番重要なのではないかと思っています。

違いを理解した上で、コードを書きながら議論する

最後にまとめということで、今日言ってきたことの繰り返しなんですが。

(ドメイン駆動設計は)ソフトウェア設計の1つなんですよと。基本はとにかく関心をどう分離するか、モジュール構造をどうするかということに対して、ドメイン駆動設計らしいアプローチ……「ドメイン駆動設計にこだわっているアプローチはこうですよ」ということを今日はお話しさせていただきました。

基本的にはこの3点。

ドメインロジックの焦点を合わせ、オブジェクト指向でモジュール化し、必然的にインクリメントに段階的にこういった設計を行いました。

従来のアプローチ、今まで慣れてきたアプローチはこうで、それに対してこういうことに振り切り、ここにこだわりに行くんだというのがドメイン駆動設計のスタイルですね。

大切なのは、まずこういう違いがあるんだということを理解して、自分たちの設計スタイルを決めていくというときに、どっちのスタイルなんだということを議論して。それも、空中戦をするのではなく、ちゃんとコードを書きながら「こういうふうに書いたらこうなるよね」という具体的なコードで議論していく。そういうことがドメイン駆動設計に取りかかる道なのかなと考えています。

終わりかな。そうですね。では、ちょっと長くなりましたが、私の話はこれで終わらせていただきます。どうもありがとうございました。

(会場拍手)

クラス名の付け方について

司会者:せっかくなので、まず今回の勉強会でなにかご質問のある方は挙手いただければ。どうぞお願いします。

質問者1:非常にわかりやすいお話をありがとうございました。実際にゲーム開発をしていく上で直面した問題点として、ドメイン駆動の視座から見て正しいのかどうかで疑問に思っていることが1つありまして。

エンジン側と上物の両方を書いていたんですね。3人の小さいチームでディレクターがいて、僕がエンジニアのようなかたちで書いていたんです。

例えば横スクロールのアクションゲームで「地面に立つユニットかどうか」「重力の影響を受けるかどうか」というようなものはコンポーネント化しました。「死ぬかどうか」だったらHPを持っているようなところをコンポーネント化したんですね。それで型で縛って書けるようにして、書きました。

例えばスキルの中で「設置すると周りにいる敵を吸い込むブラックホールのようなものを出したい」と。「そのブラックホールを出した場合、地面に足がついている敵は逃げるようにしたい」という要望があって。

そのときは、ブラックホール側のスキルのコンポーネントから参照された地面に自由に歩けるかどうかというコンポーネントを参照して、というようなことをやらなければいけないので、ブラックホール自体はクラスで書いていたんですね。

そうすると、クラス名やファイル名自体のところに「ブラックホール」としか書けないという問題があって。だんだんプログラム自体のファイル名や固有名詞になってくるんです。

dressingやheelというものがファイルのクラス名として全部出てきてしまって。ふだん業務ロジックを書いている身からすると「それはデータのレイヤーなのでは?」というのがすごく出てくるんですね。

ただ、ほかになにかいい名前のつけ方もわからないしというところで、すごく気落ち悪い思いをしながらずっと作っていまして。そのあたりについて、どうしたらいいというようなことがあれば教えていただきたいなと。

増田:私も、テクロスさんと初めてお手伝いをさせていただいたときに感じました。固有名詞がクラス名というのは、業務アプリケーションでは絶対にNGなんですが、まず違うなというのがわかりました。

今日の話の流れで言うと、値の種類の名前は、ロジックがその周りにぶら下がっている値として、名前に使えそうだったらOKなんですよ。ブラックホールは数値モデルや計算モデルがありそうですよね。だから、ありな気がする。

質問者1:なるほど(笑)。

増田:うん。だから、それをわざわざ一般的な名前にしなくてもいい気がする。

質問者1:そうすると「聖なるディバイン」だったり……。

増田:「聖なる」あたりはね(笑)。

質問者1:そこはゲームだからいいかということなのか。

増田:ゲームだからいいかというのではなく、そういう意味ではあくまでも設計なので、「聖なる」というのは、「聖である」ということを数値化したり、例えばどのぐらい聖なるかを比較したいというロジックがあるんだったら、「聖なる」「聖性」というようなクラスでもいいと思いますよ。

逆に言うと、値の種類名やロジックはぜんぜんわからないけれど、ゲームの言葉としては出てくるというのは、ドメインロジックの焦点としてはまず外しておいたほうが。

重要なのは値の種類名として有効か。つまり計算ロジックや判定ロジックをそこに集めるという、ロジックの置き場所として意味を成すかどうかというのが判断基準ということですね。

質問者1:先にロジックに当たりをつけて、それがまとまりやすいようになにがあるかを振り返るということで大丈夫ですね?

増田:そうそう、そうです。

質問者1:なるほど。

増田:ただし、順番に気をつけてほしくて。けっこうロジックや計算が先に見つかってしまうことがあるんですね。そこでやるとそれは手続き型になってしまうので、そうではなく、「値の種類とはなんなんだ?」「この計算にどんな値を使っているのか」「この計算結果の値に名前をつけるとしたら何になるんだろう?」というように、常に値ドリブンでロジックをそこにグループピングするというのがオブジェクト指向の基本スタイルだと思います。

質問者1:わかりました。ありがとうございます。

要件定義で言語化できるのは3割でいい

司会者:ありがとうございました。ほかの方はいらっしゃいますか? どうぞ。

質問者2:どうもありがとうございます。ここ1年ぐらい、DDDを業務で実践しています。ディレクションするディレクターの人と話していると、そのあたりで要求やドメインという概念が出てきますが、ディレクターの頭の中も残念ながら暗黙的というか、ものすごくぼやっとしていたりしていて。

そこの輪郭を掴んでいくときのコツのようなものがなかなか掴めないということについて悩んでいたりしていて。増田先生はどういう苦労をされているのかというのが個人的に気になっています。

増田:「先生」というところが気にいらないなぁ。エンジニアのコミュニティに出てくる人間として、それはちょっとつらいというか……それは置いておいて。

(会場笑)

実は要件定義を専門にされている神崎さんという方と昨日お話しする機会があって、非常におもしろい話が聞けて。神崎さんは、モヤっとしたところから要件定義することを生業にされている方なんですよ。

彼曰く、ユーザーさんやディレクターさんのうち、仕様を意識して言語化できるのは1割がいいとこだと。

ソフトウェアを作ろうとしたときに決めるべきロジック仕様を100だとしたら、放っておいたら10くらいまでしか言語化できません。たぶん今がそういう状態なわけですね。

神崎さんは要件定義のプロですが、RDRA(ラドラ)という方法論や神崎さんのノウハウを使って要点を引っ張り出して言語化しても3割ぐらいです。100のうち引っ張り出せるのは3割ぐらい。あとの7割はユーザーやディレクターさんから見たら、良きに計らえ。つまり、言語化できないし、言語化する気もないというのが、さっき言った重力というか、現実ですと。

ただ、それだけでは救いようがないので、神崎さんがなにをおっしゃっているかと言うと、1割だと残り9割を良しなにというのはきついけれど、3割ぐらいまで引っ張り出せるとパターンがいくつか見えてくるので、良しなにも手がかりが出てくると。

だから、逆にいうと、良しなの手がかりを見つけられるぐらいまではがんばってコミュニケーションして、それがある程度見つかってきたら、あとは自分たちが「このときこういうパターンだったから、こっちのときもこういうパターンにすればいいんだよな」という当たりをつけて、こっちでどんどん決めていって。

ヤバそうなところは「これでいいですか?」と聞くけれど、基本的には良しなに、良きに計らえと思っているから、良きに計らってしまおうと。あとで発覚したときに直せばいいと。

要件定義のプロである神崎さんをしても3割ぐらいまでしか引っ張り出せないというお話を聞いた瞬間に、「これはエンジニアが良きに計らっていくしかないんだな」と非常に(思いました)。

ただし、放っておいたら1割ぐらいしか言語化してくれないので、9割を良きに計らうのは相当危険ですよ。がんばって2割、3割ぐらいまでは引っ張り出しにいくという努力はして。だけど、せいぜいそこまでしか出てこないと見切って、あとは自分たちで作っていくということになるのではないかということです。

質問者2:なるほど。ありがとうございます。

増田:それが仕事だとおっしゃっていました。

司会者:ありがとうございます。それではいったんここで終わりたいと思います。もう一度、増田さんに拍手をお願いします。

増田:どうもありがとうございました。

(会場拍手)