開発部エンジニアの守屋です。
早いものでゲーム開発を始めて30年が経ってしまいました。普段はゲームの開発以外にゲームのデータベースから仕様をまとめるWebサービスを作るなど、メインプログラマから開発環境制作までゲーム開発に関わる事は何でもやっています。
気が付いたら仕様がぐちゃぐちゃになっていた……
最近、パッケージ原則について改めて勉強しました。パッケージ原則のパッケージとはプログラムを集めたものを表した言葉です。パソコンで例えるならアプリのインストーラーが近いです。
カメラで撮影した画像をメッセンジャーに張り付けて送信する。当たり前な操作ですが開発者も開発時期も違うアプリがどうして問題なく連携できるのでしょうか?
規模が大きい物を開発していると
- とりあえず進めていたら、いつの間にか情報がぐちゃぐちゃになった。
- 整理したいけど、どうやって方針を決めればいいのか分からない。
- 気が付いたら仕様が変わっていた。
という経験はありませんか?
パッケージ原則はプログラムの公開のルールをまとめていますが、プログラムも元は仕様!
ゲームの仕様公開方法について非エンジニアの方々にも活用できると思い、なるべく技術的な用語を使わずに書いてみました。
パッケージ原則改め仕様公開の原則
たとえば、あなたが何かの仕様を作って公開し、その仕様に基づいて多くの人が作業を進めたとします。時間が経つと、その仕様を元にした様々な成果がたくさん公開されるでしょう。
多くの成果物がその仕様を元に作られているので、公開した仕様を改めて変更するのは大変な責任が伴いますよね。
話が変わりますが、人は「再利用」が得意です。ゲームではチュートリアルをプレイして得た知識を使ってゲームを進めます。プログラミングの世界でも既にあるプログラムをわざわざ一から作り直すことは好ましくない事になっています。
設計も同様で、多くの仕様は再利用を念頭に置いて作られているため、そこからさらに多くの派生した仕様が生まれ、結果として大きな製品が完成していきます。
あなたの考える仕様が他の仕様に依存している場合、その依存している仕様が確定していることを期待しますよね?仮の情報を元に仕様を考えると、その仕様も仮のものになり、問題が発生しても「仕方がない」ということになります。
つまり、仕様を公開するという事は再利用を可能にできる状態を保証する。という事になります。
公開と再利用の一致の原則
「とはいえ、すべてが決まるまで待つのは難しい!!!」ですよね。だからこそ、公開する側と利用する側がスムーズに作業を進められるように、仕様を部品ごとに分けておくことが大切です。
私もよくやってしまうのですが、急いで作りかけのものを公開してしまうと、気がついたら自分の仕様が他の人によって変更されていたり、逆に利用者が必要な変更をためらう状況が生まれてしまうことがあります。こうなると、並行作業をしているはずが、全体の調整に余計な時間がかかることになってしまいます。
そこで、公開する範囲と再利用可能な範囲が一致するように単位を適切に分けて設計することが重要です。
では、その単位をどうやって考えたらいいのでしょうか?
そのヒントとして、「全再利用の原則」や「共通のものは閉じる原則(閉鎖性共通の原則)」が役立ちます。
全再利用の原則
あなたが公開した仕様と他の仕様との間で衝突が起こる可能性もあります。公開された仕様を利用する人は、「この変更は必要だけど、この変更は採用したくない」といった取捨選択ができません。
一見、全再利用の原則は利用者に不便を強いる制約のように思えますが、実は逆で、範囲を決める時には多くを含みすぎないよう注意しなさい。ということを示しています。
機能を改善したいけど、互換性を失うのは困るという状況に陥るのは、仕様の範囲が広すぎることが原因です。
共通のものは閉じる原則
「全再利用の原則」は、分けるべきものはしっかり分けなさいという教えでした。それに対して、「共通のものは閉じる原則」は、関連する仕様を閉じ込めなさいという教えです。
「閉じている」とはどういうこと?
ここで小さな仕様をまとめた仕様をパッケージと呼ぶことにします。チャーハンの注文を受けて配膳するまでを例にしてみましょう。
このパッケージAに「餃子を作る」仕様を追加したとしましょう。
パッケージ内の仕様を変更すれば、パッケージ内のすべてに何らかの影響を与えます。もし影響がなければ、そのパッケージには無関係な仕様が含まれていることになり、「全再利用の原則」に違反している事になります。
また、別のパッケージに影響を与えている。本来は関係が強いにも関わらず別のパッケージに分散してしまうと、パッケージ全体に複雑な依存関係が生まれてしまいます。
かと言って仕様を分ける事ばかり優先してしまうと、パッケージには一つの仕様しか残らなくなり、パッケージの意味がなくなってしまいます。
ここで大切なのは、パッケージの「責務」です。それぞれのパッケージは、その責務に従って考え、他の事は考えない様にするべきです。パッケージの責務が一つの概念でまとめられていれば、パッケージ内で問題が発生しても影響をコントロールできる状況、つまり「自己完結している」ので変更リスクがコントロールされているという事になります。
今回なら注文、調理、配膳というパッケージに分け、仮に「ラーメンを作る」仕様が追加されても、注文と配膳に影響が及ばないようにする事ができます。
凝集度と依存度
二つの原則を守ってパッケージの規模が最小化されていれば、変更する際のリスクも小さくなります。つまりパッケージが「閉じた」状態を保つことができます。
扱いやすいパッケージは、「何が含まれていないか」がはっきりしているので、利用者の考える範囲や不安を減らしてくれます。関係の強い仕様が一つのパッケージに集まっている。凝集度の高いパッケージを用意できると、そのパッケージと他のパッケージとの関係は自然と弱まっていきます。
逆に、少しでも関連がある仕様を無理に同じパッケージにまとめてしまうと、パッケージの概念がぼやけてしまいます。
循環依存関係禁止の原則
循環依存関係禁止の原則とは、二つ以上のパッケージが互いに依存し合うことを避けるべきだというルールです。
想像してみてください。友達Aと友達Bがいます。AさんがBさんに「私はBさんが何をするかで自分の行動を決める」と言い、同時にBさんも「私はAさんが何をするかで自分の行動を決める」と言っています。この場合、AさんもBさんもお互いを見ているので、どちらも動き出せません。これが「循環依存」です。
このような循環依存が仕様の中で起こると、全体が複雑になり仕様の変更や修正が難しくなります。仕様Aを変更しようとすると、その変更が仕様Bに影響を与え、仕様Bも変更しなければならなくなります。かと言って、仕様Bを変更すると、今度はまた仕様Aに影響が出る、といった具合に、変更が終わらないような状況に陥ります。
循環依存を避けるためには、共通の要素を切り出して別の独立した仕様にするのが良い方法です。そうすれば、ある仕様が他の仕様に依存していても、その依存関係が循環して戻ってくることはありません。
安定依存の原則
安定依存の原則とは、「安定した仕様や部分に依存するべきだ」というルールです。
つまり、変更が少ない、安定している部分に依存することで、全体の安定性を保とうという考え方です。
ある建物を建てるとき、基礎部分(安定している部分)にしっかりと依存することで、建物全体が安定します。もし基礎部分が不安定で変わりやすいと、建物全体が不安定になってしまいます。だからこそ、できるだけ安定した部分に依存することが重要です。
ソフトウェアでも、変更が多い、不安定な部分(たとえば、頻繁に更新される機能や実装)に依存すると、全体が不安定になっていきます。
変更が予測できる部分は抽象化する
「変わらない部分が先に予測できれば苦労しない!!!」その通りだと思います。
変わらない事を決める事は難しいですが、変わりそう部分を予測する事は楽にできそうです。
変更が予測できる部分や、頻繁に変更される可能性がある部分は、抽象的な概念に置き換える事で具体的な部分から切り離します。これにより仕様が変わっても依存部分には影響が少なくなります。
たとえば、料理ごとにパッケージをまとめた場合、組み合わせがどんどん増えてしまい、他のパッケージと整合性がとれているか?など考える事が膨れ上がってしまいます。
安定と抽象は比例する原則
変化しやすい部分を具体的に設計し、変化しにくい部分を抽象化して扱うべきだ。という原則です。全体の安定性を保ちながら柔軟に変更に対応するためには特に重要です。
この記事で最も伝えたい”仕様の中で具体的なものを「変えない」と約束するのではなく、その変わらない本質的な部分を見つけ出して「変えない」と約束する”という事なのですが、何を言っているか分かりにくいですよね。
変えない部分を絞り込む事で、変える部分のリスクやダメージを制御する事を指しています。
注文、調理、配膳を例にしましょう。色々な方法を含めるとした事で将来的に仕様を変更する余地ができましたが、ルールも決めずに仕様を追加してしまったらパッケージは不安定なまま。「安定依存の原則」に違反している事になります。
ここでパッケージの間の矢印に、ルールを具体的に決めて考えてみましょう。
- 注文のルール:注文を伝票に書いて調理に渡す
- 調理のルール:調理が終わった料理を配膳台に置く
この二つのルールを具体的に決める事で、各パッケージの中を抽象的にする事ができます。そして、抽象的にできる事でパッケージ内の安定性を作り出しています。
つまり、抽象度と安定度は比例するのです。
これまで料理の発注を例にしてきたので、「火と氷を避けてゴールを目指す」というゲームの仕様で考えてみましょう。
ゲームは何らかの要素でリアクションが起こります。今回なら触れる物が増えるたびにリアクションの組み合わせを考える必要が出てきます。
これを温度の区分という概念を追加して「危険な温度を避けてゴールを目指す」という仕様に変えると、後から「マグマに触れると延焼ダメージ」を追加しやすくなります。
つまり「温度の区分」を管理する事で、触れる物やリアクション側の変更ダメージをコントロールします。
最後に
ここまでで、公開した仕様は安定していなければならない事を説明してきました。ですが、「いきなり仕様を確定できれば苦労はしない!!!」本当にその通りだと思います。そこで、最後に仕様公開に触れて終わろうと思います。
どんな仕様も最初はアイデアを閃く事から始まります。しかし、アイデアというものは何の根拠もありません。
私はエンジニアなので、ゲームのアイデアが閃いたとしても、遊ばせ方や表現の事、音の事を専門家達の相談無しに仕様として公開したら、チームが混乱する事は明らかです。
ソフトウェアのバージョンでアルファ版やベータ版などのバージョン分けを聞いた事があると思います。仕様公開も同様に、アイデアと仕様の間の状態を作り、仕様検討している状態をチームで共有してみましょう。この状態を「提案」と名付けます。
つまり
- 「アイデア」なら実現方法を無視した自由な考え。という事
- 「提案」なら安定化に向けて検討中。という事
- 「仕様」なら安定した。という事
提案を公開して、アイデアの目的、不安な部分、決めきれない部分、改善点をチームで磨き上げた結果を仕様とする。
こうして合意がとれるワークフローを用意する事で、仕様公開への障壁が少しでも低くなると思います。
説明下手な事に加えて、抽象的な内容が含まれていたので分かりにくかったかもしれません。もしも、こんな感じの記事も読んでみたいなどありましたらご意見を頂けると嬉しいです。
ここまで読んで下さり、ありがとうございました。