ソフトウェアを設計するとき、関連性の高い部分をひとまとめにしたものをモジュールと呼びます。どのようにモジュールに分割するかは、設計における重要な論点です。
一般的に、モジュール内の各機能の関連性が高く(=モジュール強度が強く)、ほかのモジュールへの依存度が低い(=モジュール結合度が低い)ほど良い設計とされています。
オプジェクト指向をサポートする言語では、オブジェクトがモジュールに相当します。一方、C言語が主流だった時代は「どの機能をどのソースファイルに収めるか」というのがモジュール分割の論点でした。
いずれにしても、ソフトウェア開発は要求される処理を洗い出すことから始める場合が多いのではないでしょうか。実現すべき処理がリストアップできたら、それらの詳細を明らかにして、モジュールに割り当てていく流れです。
このとき、「まずああして、次にこうして……」と処理の順番を考えて、「時間軸」に沿った設計をしがちです。でも、ある程度大きなソフトウェアを設計するときは、この手法がうまくいく見込みは少ないでしょう。
なぜなら……
- 要求は増えたり変わったりする
- 事前に予測できるのは、それらの変更の一部に過ぎない
- そうして生じた設計の「ひずみ」を、根本的に解決するための時間的な余裕はない
- その結果、徐々にソフトウェア全体が複雑化していく
こうして、はじめのうちはモジュールが適切に分割されていたとしても、分割すべき場所がいつの間にか変わってしまうことが少なくないのです。でも、分割の境界線を変えるには大きなコストがかかるので、やり直しがきくことは多くはありません。
そうこうしているうちに、どのモジュールが何の処理をするためのものだったのかが分からなくなり、ソフトウェア全体が一つの巨大なモジュールのようになっていきます。複雑すぎて手に負えない迷路のような状態です。
「オブジェクト指向」について調べたことがある人は、「アクターモデル」という用語を知っているのではないでしょうか。これは、ソフトウェア全体を「アクター」で構成する設計手法です。アクターは、それぞれが自分の役割をもっていて、互いにメッセージを送り合って協調的に動作します。
もし、演劇を「時間軸」で設計するとしたら、「第1幕」や「第2幕」などのモジュールに分割することになります。でも、完成するまでにストーリーは変わるかもしれません。「第1幕のあのセリフは第2幕にもっていこう」などとやっているうちに、モジュールの境界線は曖昧になっていきます。
これに対し、演劇を「空間軸」で設計するのは、「役者(アクター)」でモジュールを分割するということです。舞台というソフトウェアを構成する主要なオブジェクトは、役者たちだからです。それぞれがメッセージを送り合い、自身のキャラクターを演じることによって舞台が進行していきます。
ソフトウェアを空間軸で設計するのは、演劇の登場人物をデザインするのに似ています。全体をアクターに分割し、それぞれに役割を与えるのです。これは、ソフトウェアを実在の「もの(=オブジェクト)」の集まりとしてとらえるということです。
オブジェクトごとの役割が明確であれば、要求を満たすために必要な処理を割り振るのも難しくはありません。あとから要求が増えたり変わったりしても、どのオブジェクトに対応させるべきかは明らかでしょう。
また、このような役割に応じた分割手法には、関連する実装が自然と近くに集まってくるというメリットもあります。必要以上の労力をかけずに、モジュール強度が強く、モジュール結合度が低い状態を保てるのです。
コーディングをする際も、1つのオブジェクトだけに注目すれば済む場合が増えるでしょう。
開発に使う言語をC言語からC++に移行すれば、ソフトウェアをオブジェクトの集まりとして設計しやすくなるのはたしかでしょう。とはいえ現実には、簡単に移行できない場合も少なくないはずです。
それでも、C言語で同様の設計をすることは可能です。「オブジェクトで考える」というのは、「オブジェクト指向言語を使う」こととイコールではないからです。
C++では、オブジェクトの種類をクラス(class
)で定義します。クラスは、そのオブジェクトの内部状態を示す変数と、外から見たときの動作を決めるものです。これにより、それぞれのオブジェクトは自律的に振る舞いながら、自分の役割を果たします。
C言語でこれと近いことができるのは構造体(struct
)です。オブジェクトの内部状態を示す変数は、構造体に直接含められます。オブジェクトの動作は、構造体を扱う関数として定義すればよいでしょう。
このとき、構造体ごとに別々のソースファイルを作るようにします。そうすれば、構造体を扱う関数も対応するソースファイルに自然と集めやすくなり、モジュールの境界がハッキリします。