ObjectSquare [2005 年 1 月号]

動くもの主義! オブジェクト工房

第 1 弾 ライントレーサ・シミュレータ

〜 中編 設計を考える! 〜

株式会社 オージス総研
技術部
黒田 洋介
Kuroda_Yousuke@ogis-ri.co.jp


忙中閑あり・・・とは言いますが、忙しい毎日の息抜きがわりに、ちょっと面白いアプリをネタにしてオブジェクト指向をしてみませんか? 今回のお題はライントレーサのシミュレータです。ライントレーサとは、ラインに沿って走るロボットのことですが、ライントレーサをネタに、「カナリマジメ」にオブジェクト指向してみました。


目次

1. はじめに
2. 設計を行う前に
3. システム構成の決定
4. 論理構造の設計
5. まとめ
6. 参考文献

1. はじめに

本連載はライントレーサをシミュレートするアプリケーションを作成する過程でどのような分析・設計・実装を行うか、実践を通じてまとめたものです。前編では、ライントレーサ・シミュレータ(*1)の分析を「どんなシミュレータを作成するか」という視点から説明しました。中編では、「どうやって目的を達成するか」という視点から設計について説明します。

本記事では、設計を行う際の入力として UML による分析モデルを用い、どのように設計モデルにマッピングさせていくかについて触れます。

対象となる読者としては、業務や研究などで設計・実装を行っている方を想定しています。
特に、分析工程での成果物を、設計工程の成果物に変換する際に何を手がかりにすればよいのか迷う方、日々の設計作業で設計判断の根拠に自信が持てない方を対象としています。また、実装者の方(設計を受け取って実装をする方)ですが、受け取った設計が「なぜこのような設計になったのか」を読み取る際の手がかりを見つけることに苦労する方を対象としています。

2. 設計を行う前に

設計作業を行う前に、設計とは何をする作業なのか整理します。ソフトウェアの世界だけではなく、一般的な「設計」とは何を指しているのか、代表的な国語辞典を引いてみました。

(広辞苑 第五版より)
ある目的を具体化する作業。製作・工事などに当り、工費・敷地・材料及び構造上の諸点などの計画を立て図面その他の方式で明示すること。
(大辞林 第二版より)
機械類の製作や建築・土木工事に際して、仕上がりの形や構造を図面などによって表すこと。

上記の辞書では建築などを前提にしているようですが、設計とはさまざまな視点から、目的を具体化するための構造を図面などで明示する作業を指すようです。ソフトウェアの世界でも、本質的には変わりません。

設計工程でどのような作業を行うのかは、開発の対象となるソフトウェアによって変わってきますが、主に以下の作業が行われます。

また、これらの設計判断は機能要求(ユースケース)や機能外要求及び分析工程の成果物から決定されなくてはなりません。以下に本システムの機能要求と機能外要求の一覧を載せます。(前編からの抜粋です。)

機能要求一覧(ユースケース:(一台の)ライントレーサをシミュレートする)

機能外要求一覧

設計作業時に注意しなくてはならないことは、「これが決まっていないと実装が行えない」最低限の情報を必ず網羅しなくてはならないということです。当然、設計時の情報が曖昧だと、実装者の作業にもぶれが出てきます。少し極端な例になりますが、筆者は以下のような経験をしたことがあります。筆者は設計者の立場で、実装をされる方に設計を渡した際のことです。口頭であるオープンソースライブラリの使用を実装者に依頼したのですが、うっかり設計書に書きそびれてしまいました。開発環境を定義するドキュメントにはそのライブラリを書いておいたことと、実装者の方も頷いていたので、大丈夫かなと思ったのですが、結果としてそのライブラリが使われない実装物が出来上がってしまいました。もちろんこれは、設計側の手落ちです。

それでは、これから上記の情報を元にライントレーサ・シミュレータの設計を見ていきます。

3. システム構成の決定

まずはシステムの構成を決定します。アプリケーションの形態(スタンドアロン、クライアント/サーバなど)やプラットフォームは、さまざまな制約条件から判断しなくてはなりません。制約条件は機能外要求と機能要求から導き出します。以下に、ライントレーサ・シミュレータのシステム構成の決定における制約条件とシステム構成(表 3.1)をまとめました。

制約条件
(1) 異なるプラットフォームへの移植は考える必要が無い
(2) 誰でも試せるプラットフォームが良い
(3) それなりの 3D 描画処理機能(パフォーマンス含む)が必要
(4) 開発工数の削減
(5) 技術調査につなげたい

表 3.1 システム構成
システム構成 スタンドアロン
動作環境 Windows2000/XP
.NET Framework 1.1
DirectX9
開発環境 .NET Framework 1.1 C#(VisualStudio.NET 2003)

機能要求の中で、「さまざまな視点からライントレーサの動きを観察できること(2 次元ではなく、3 次元で観察できること)」というものが有ったため、3D を描画する必要があります。労力的に自分で実装することは難しいので、ミドルウェア(ライブラリ)として DirectX を利用しました。DirectX とは、Microsoft 社が開発したマルチメディア用の低レベル高機能な API 群であり、このような機能要求を実現することに向いています。同様の機能要求を実現する手段として OpenGL というものも存在します。本来は、機能外要求からどちらが適しているか確認する必要があります。

(4) の制約から、Win32API を直接操作するような開発は行わず、.NET Framework か Java のどちらかを選択することにしました。C# 選定の理由としては、Java の事例はたくさん手元にありましたが、C# の事例は少なかったため、技術調査を兼ねて C# を選択することにしました。

C# + DirectX という組み合わせのリスクとしては、どちらも Windows に依存する技術なので、異なるプラットフォームへの移植は諦めなくてはなりません。また、ユーザーは .NET Framework + DirectX を別途インストールしないといけないので、ユーザーがこのアプリケーションを動かすことには手間がかかります。(2) の制約にぶつかってしまいますが、今回は (4) を優先させることにしました。

システム構成の決定の際に参照する情報としては、機能要求と機能外要求が挙げられます。機能外要求の段階では、設計判断を行う際の情報としては大まかな場合が多いです。上記の制約条件を洗い出すところまでブレークダウンしなくてはなりません。

システムの構成を決定したので、次は論理構造を設計します。ここからは前回と同様開発の流れ図(図3.1)を参照しながら進めます。

開発の流れ
図 3.1 開発の流れ

4. 論理構造の設計

ここからは、論理構造の設計になります。まずはパッケージ構造を明らかにして、それからパッケージごとに「静的構造の設計」と「詳細レベルの責務決定(動的構造の設計)」を行います。

■ パッケージ構造

分析時に作成したドメインチャート(下図 4.1 参照)を元に、パッケージ構造の設計を行います。分析時に抜き出したドメインは、多少再利用性などは考えられてはいるものの、基本的には分析時に抜き出したクラスを意味的にくくっているだけです。設計時には、拡張性や再利用性なども考慮してドメインを実装可能な設計要素(UML ではパッケージ、C# では名前空間)にマッピングしなくてはなりません。どのような拡張性や再利用性が必要かを判断するために、制約条件を洗い出す必要があります。

制約条件は機能要求や機能外要求などから導き出す必要があります。今回作成するシステムはシミュレータなので、検証したいアルゴリズム、シミュレート対象のメカニズムやシミュレータ内の世界の環境設定については、柔軟に変更できることが求められます。裏を返すとライントレーサ以外のものをシミュレートする必要はありません。シミュレーションの方式を変更する必要は無いわけです。ちょっと極端な話を書けば、飛行機を制御するプログラムや、二足歩行を制御するためのアルゴリズムを検証できる必要はありません。以上を踏まえて、本記事では以下のような制約条件があるものとして設計を行いました。

制約条件
(1) ライントレーサを制御するアルゴリズムを容易に取り替えたい
(2) ライントレーサのメカニズムを容易に取り替えたい
(3) (2) に関係してライントレーサの外見も容易に変更したい
(4) シミュレーション世界の環境を容易に変更したい
(5) シミュレーションの方式を変更できる必要はない

図 4.2 パッケージ構造及び表 4.2 パッケージ一覧を参照してください。分析の時点で制約条件 (1), (2) に対応させるために、LineTracer / Mechanism パッケージを抜き出しています。ただし、LineTracer / Mechanism が変わるたびに Simulator パッケージも変更しなくてはならなくなると、(1), (2) の制約に反するので、依存関係を逆転させました。この依存関係は Simulator, LineTracer, Mechanism パッケージ内の設計を行う際の制約条件になります。

これと関連して、制約条件 (3) に対応する必要があります。これはメカニズムが変更されること(センサーの数が増えるなど)で、描画処理が変わる可能性があるためです。分析時は描画を Rendering に行わせていましたが、ライントレーサの外見の変化に対応する部分は新たに LineTracerRendering として抜き出しました。これによって、制約条件 (3) の「ライントレーサの外見も容易に変更したい」を達成することができます。

分析時のドメインチャート
図 4.1 分析時のドメインチャート(前編より抜粋)

パッケージ構造
図 4.2 パッケージ構造

また、システムを意味的に区切るレイヤー(パーティション)を定義しました(表 4.1 参照)。レイヤー(パーティション)とは、システムを構成するパッケージを意味的に分けるためのものです。レイヤーは横、パーティションは縦に区切ります。今回は意味的な区切りとして利用していますが、下位のレイヤーにあるパッケージは、上位のレイヤーにあるパッケージを参照できないなどの制約を作る場合もあります。これは、保守性の向上を狙ったものです。レイヤーパターンはポピュラーなアーキテクチャに関するパターンです。参考文献[2]を参照してください。

表 4.1 レイヤー・パーティション一覧
レイヤー(パーティション)名 説明
UI ユーザーとのやり取りを行うパッケージ群
Application システム固有のパッケージ群
Drawing システム固有の描画を担当するパッケージ群
Simulation シミュレーションに関係するパッケージ群
Library & Framework 全パッケージから利用されるライブラリやフレームワークを実現するパッケージ群

表 4.2 パッケージ一覧
パッケージ名 説明
UI ユーザーとのやり取りを行うクラス群の宣言
LineTraceSystem システムの起動を担当するクラス群の宣言
Simulator シミュレーションを進行するフレームワークのような個所
Rendering 描画を行う基本的なクラス群の宣言(DirectX のラッピングも行う)
LineTracerRendering 本システム特有の描画処理を行うクラス群の宣言
LineTracer シミュレーション中、ライントレーサの意思決定を行うクラス群の宣言
Mechanism シミュレーション中、ライントレーサの動きを制御するクラス群の宣言
Environment シミュレーションの環境情報(地面の色など)をもつクラス群の宣言

この場合、パッケージ構造は抜き出されたものの、パッケージ間のやり取りをどうするのか決定していません。今回は Facade パターン(参考文献[1])を用います。各パッケージに Facade クラスを用意して、パッケージ内部での変更が他のパッケージに影響を及ぼさないようにします。これは、制約条件の (1) 〜 (4) を達成するためです。制約条件の (1) 〜 (4) に該当する変更の影響範囲がそのパッケージ内に収まっていないと、制約条件を満たすことが難しくなるためです。

このように、設計はどこにどのような特性をもたせるべきなのか、判断材料を明らかにした上で「必要十分」な設計を行う必要があります。かくいう筆者もよくやってしまうのですが、必要があるかどうかわからない拡張性を「将来のために」という理由で設計に追加してしまうことがあります。これは、こうした制約条件がよく飲み込めていない場合にやってしまいます。無駄な作業を省くためにも、設計上の制約条件を明らかにする必要があるのです。

パッケージ構造が決定されたので、各パッケージの設計モデルを作成します。ここでは、主要なパッケージ(Simulator, LineTracer, Mechanism パッケージ)の設計モデルについて触れます。

分析クラス図
【参考】分析クラス図(前編より抜粋)

分析シーケンス図
【参考】分析シーケンス図(前編より抜粋)

■ Simulator パッケージの設計

関連する制約条件
(1) ライントレーサを制御するアルゴリズムを容易に取り替えたい
(2) ライントレーサのメカニズムを容易に取り替えたい
(3) (2) に関係してライントレーサの外見も容易に変更したい
(4) シミュレーション世界の環境を容易に変更したい
(5) シミュレーション世界内で起こる変化を管理したい

□ 静的構造の設計

図 4.3 を参照してください。LineTracerSimulator クラスは、シミュレーションを進行するためのクラスです。このクラスは Facade の役割を果たします。そして、制約 (1), (2), (3) に対応するために、3 つのインターフェース(IRobot, IBody, IRenderer インターフェース)を作成します。これらのインターフェースは、それぞれ LineTracer パッケージ、Mechanism パッケージ、LineTracerRendering パッケージで実装されます。これにより、ライントレーサの制御アルゴリズムなどに関する変更による影響範囲を各パッケージ内に収めることができます。

Simulator クラス図
図 4.3 Simulator クラス図

□ 詳細レベルの責務決定(動的構造の設計)

図 4.4 では、どのようにシミュレータを制御するか設計しています。Client オブジェクトは LineTracerSimulator クラスの利用者です。(本システムでは、LineTracerSimulator クラスのメソッドを呼び出すのは UI パッケージのクラスです。)LineTracerSimulator クラスは IBody にライントレーサの移動を依頼してから、センサーによる値の感知を依頼します。そして、IRobot に行動決定を行わせてから、ライントレーサの描画を行います。IRobot が行動決定しても、ライントレーサは IBody.Move() メソッドが呼ばれるまでは実際には移動しないことに注意してください。シミュレーション内での変化を LineTracerSimulator クラスが管理することで、整合性が崩れないようにしているのです。ですので、IRobot の実装クラスは IBody.Move() メソッドを呼び出してはなりません。制約条件 (5) に対応します。

Simulator シーケンス図
図 4.4 Simulator シーケンス図

■ LineTracer パッケージの設計

関連する制約条件
(1) ライントレーサを制御するアルゴリズムを容易に取り替えたい

□ 静的構造の設計

図 4.5 を参照してください。制約条件 (1) を満たすために、ここでは Simulator パッケージで宣言されている IRobot インターフェースを実装した Robot クラスを作成します。今のところこのクラスしかないのですが、Robot クラスは本パッケージの Facade の役割を果たします。ライントレーサのアルゴリズムを変更する場合は、Robot クラスを作り変えるか、制御アルゴリズムをクラスとして抜き出すか(Strategy パターン:参考文献[1])することになるでしょう。

LineTracer クラス図
図 4.5 LineTracer クラス図

□ 詳細レベルの責務決定(動的構造の設計)

Robot クラスは IRobot インターフェースで宣言された Think メソッドの実装しか持たないので、ここではシーケンス図を省略しています。ライントレーサを複雑なアルゴリズムで制御する場合は、さまざまなクラスやメソッドが登場する可能性がありますが、今は単純な制御アルゴリズムを用いているため、このパッケージは非常に単純な構造になっています。制御アルゴリズムは図 4.6 を参照してください。「前進中」の場合は IBody.Forward() メソッドが呼ばれ、ライントレーサは前進します。「右方向回転中」「左方向回転中」の場合は IBody.Rotate() メソッドが呼ばれます。

Robot クラスのステートチャート図
図 4.6 Robot クラスのステートチャート図(前編より抜粋)

■ Mechanism パッケージの設計

関連する制約条件
(2) ライントレーサのメカニズムを容易に取り替えたい

□ 静的構造の設計

図 4.7 を参照してください。制約条件 (2) に対応するために、IBody を実装する Body クラス(Facade の役割を担う)を作成します。そして、内部的に光センサーの役割を果たす Sensor クラスを作成します。

Mechanism クラス図
図 4.7 Mechanism クラス図

□ 詳細レベルの責務決定(動的構造の設計)

図 4.8 のシーケンス図は地面の色を感知する場合のシーケンス図です。本システムは単純なシステムなので、分析レベルのシーケンス図を英訳してメソッドを作る程度の作業で済みます。難しいシステムになると、さまざまなテクニカルなメソッドが出現することがあります。

Mechanism シーケンス図
図 4.8 Mechanism シーケンス図(地面の色を感知する)

以上、やや駆け足でしたが、ライントレーサ・シミュレータの設計の一部を紹介しました。

5. まとめ

以上で、ライントレーサ・シミュレータの設計が完了しました。制約条件が緩やかだったので、単純な設計になりました。もっと厳しい制約条件があればそれに応じてさまざまな設計を行わなくてはならなかったでしょう。

設計時に注意して欲しい点は、分析モデルを詳細化して設計モデルを作成するだけではなく、それぞれの設計判断時に、制約条件という形で判断材料をまとめることです。分析工程の成果物として、機能要求や分析モデルだけではなく、それらに機能外要求も絡めて制約条件を洗い出してやることで、「設計判断の根拠」を明確にすることができます。このような作業をしておくと、レビュー時に「なぜこうしたのか」を説明しやすくなります。また、実装者の立場としては、「なぜこうなっているのか」を理解する鍵になります。
(もっとも、制約条件という形で列挙する必要はありません。書き方はドキュメントの形式などにも左右されるので、適切な書き方を随時検討する必要があります。クラス図にノートを貼るだけでも理解容易性は向上するでしょう。)

ちょっと話が脱線しますが、筆者は以前に保守開発に入ったことがあります。設計書やソースコードなどから、「ある機能がどのように動いているのか」はある程度理解できるのですが、「なぜそのようになっているのか」に関する記述が少なかったため、「なぜそのようになっているのか」が読み取れず苦労しました。「なぜそのようになっているのか」ということが理解できないと、ある設計判断がどこまで影響を及ぼしているのか掴みにくいため、保守開発は難しくなります。たとえば、ある部分で Facade パターンっぽいものを使っているのですが、「あるパッケージをサブシステムに見立てて内部構造を隠蔽するために Facade パターンを用いている」のか「あるパッケージを便利に使うためのクラスが Facade のように見えているだけ」なのか判断がつかなかったりします。前者を後者と勘違いしてソースコードに手を入れると、後々大変なことになるのは容易に想像がつきます。

設計を決める際に判断材料とした制約条件が書き残されていれば、こうした問題は発生しません。納期などの事情で、システムが本来有るべき姿から離れてしまっている場合でも、制約条件(と予算)があれば修正作業をすることが可能になります。制約条件を書き出すことには、設計時の判断根拠になる、レビュー時に利用できる、保守段階に入ってから開発者に設計を理解させる要因となるなど、さまざまな利点があります。逆に、このような記述が見つからない場合は、実装者の方もしくは保守担当の方は設計の担当者に制約条件を確認する必要があります。

以上で設計に関する話は終了です。次回は今回の成果物を元に実装を行います。あわせて、UML で記述した論理構造をどのように実装(.NET Framework 1.1 C#)にマッピングしていくかについて触れます。

6. 参考文献

[1] エリック・ガンマ、ラルフ・ジョンソン、リチャード・ヘルム、ジョン・ブリシディース著 本位田 真一、吉田 和樹訳「オブジェクト指向における再利用のためのデザインパターン」(1999 年, ソフトバンクパブリッシング )
[2] F.ブッシュマン、R.ムニエ、H.ローネルト、P.ゾンメルラード、M.スタル著 金澤 典子、水野 貴之、桜井 麻里、関 富登志、千葉 寛之訳「ソフトウェアアーキテクチャ」(2000 年, 近代科学社)




© 2005 OGIS-RI Co., Ltd.
Prev Index
Prev Index