1.1 責任駆動型設計

A S C I I
A d d i s o n
W e s l e y
P r o g r a m m i n g
S e r i e s
Part 1
用語、
ツール、
技法
Classic Data
Structures in C++
25
第1章
エンジニアリングソフトウェア
本書は、主としてデータ構造とそのデータ構造を操作するアルゴリズム、
そしてデータ構造の応用や使用といったことに関するものであるが、データ
構造を選択し、操作することは、大規模なソフトウェア開発のプロセスの一
部にすぎないことを忘れてはならない。C++言語においてデータ構造を生成
するのに採用されている機構は、とりわけ大規模なソフトウェアシステムの
開発を支援することを念頭において設計されている。大規模なソフトウェア
システムとは、何万、何十万行というコードからなるプログラムで、複数人
から構成されるプログラミングチームによる努力を要するもののことをいう。
そのようなアプリケーションの開発を記述するのに、ソフトウェアエンジニ
アリング( software engineering )という用語が使われることが多い。
本書で述べられているレベルのデータ構造論を履修する学生は、大規模シ
ステム創造の経験がないのが普通なので、単なるコーディングでないソフト
ウェア設計や開発にまとわる困難さについてよく理解できないかもしれない。
データ構造とこのプロセスの関係と C++プログラミング言語が提供する機
構の重要性をよく理解するために、この章ではデータ構造から一歩退いて、
ソフトウェア開発の広範な問題について議論したい。続く章では、データ構
造の特定の型に関連する問題に的を絞ることにする。
この章で採用するアプローチは、ソフトウェアエンジニアリングの解説と
ソフトウェア設計の仮想的なケーススタディとを織り合わせたものである。
ここで述べる責任駆動型設計( responsibility-driven design )と呼ばれるソ
フトウェア開発技法は、方向性のあるソフトウェア進化の反復プロセスであ
り、規模の大小を問わずソフトウェアアプリケーションの生成に適用できる
ものである。基本的アイデアは、手元にある問題の一般的記述から始めて、
その記述(あるいは仕様)を同時に進化と洗練化させ実現に向けて段階的に詳
細化していくというものである。プロセスが進むにつれて、自然と作業は問
26
第1章 エンジニアリングソフトウェア
題分析からプログラムによる解へと進展し、ついには動作する完全なソフト
ウェアアプリケーションへと結実するのだ。
ソフトウェア設計の特定の技法を記述するのに加えてこの章の主要な目標
は、読者がコンピュータプログラムを、相互作用する独立したソフトウェア
コンポーネントの集合体とみなすのを促すことにある。そのようにして開発
されるプログラムが、オブジェクト指向( object-oriented )と呼ばれるのだ。
小規模プログラミングと大規模プログラミング
個人的なプロジェクトでの開発と、ある程度以上の規模のソフトウェアシステム開
発の相違は、「小規模プログラミング( programming in the small )」と「大規模プ
ログラミング( programming in the large )」という用語を用いて記述されることが
多い。端的にいって小規模プログラミングでは、プロジェクトは、次のような属性に
よって特徴付けられる。
●
プログラムコードは、1 人のプログラマか少人数のプログラマのグループによって
開発される。1 人の人間が、プロジェクトのすべての側面を上から下まで、始めか
ら終わりまで理解することができる。
●
ソフトウェア開発プロセスの主要な問題は、対象となる問題を扱うアルゴリズムの
設計と開発である。
一方大規模プログラミングでは、ソフトウェアプロジェクトは、次のような性質に
よって特徴付けられる。
●
ソフトウェアシステムは、大規模なプログラマのチームによって開発される。シス
テムの仕様の決定や設計に携わる人物は、個々のコンポーネントのコーディングに
携わる人物とは異なり、さまざまなコンポーネントから最終製品への統合に携わる
人物は、さらに別人である場合が多い。どの人物をとっても、プロジェクト全体に
責任を負っているとはいえず、プロジェクトのすべての側面を理解している人物も
いない。
●
ソフトウェア開発プロセスでの主要な問題は細部の管理であり、プロジェクトのさ
まざまな部分間の情報の流れの管理である。
学生は小規模プログラミングから始めるけれども、C++言語の多くの側面は、大規
模プログラミングにおける問題に応える形でこそよく理解できる( たとえば 40 ペー
ジのコラム「誰から情報を隠蔽するのか」参照)
。したがって大規模システム開発にと
もなう困難をある程度理解しておくことは、C++言語の理解の予備知識として有効
である。
初学年級の学生にとっても、ソフトウェアフレームワークの使用を通じて大規模プ
ログラミングの側面の一部を経験できるようになった。グラフィカルユーザーイン
ターフェイス( Graphical User Interface, GUI )システムのようなソフトウェアフ
レームワークは、ウィンドウシステムを使用する際の機能のかなりの部分を操作す
る。ソフトウェアフレームワークのユーザーは、ある操作が、どのように効果を発揮
するかの詳細を理解するのではなく、どのようにして新しいソフトウェアコンポーネ
ントをフレームワークに結合して相互作用させればよいかだけを知っていればよい。
1.1 責任駆動型設計
27
こうして比較的単純なプログラムでも、大きなシステムに結合されるコンポーネント
として生成されるのだ。
1.1 責任駆動型設計
この節では、ケーススタディを通じて責任駆動型設計の原則を示す。あな
たは、大きなコンピュータ会社のチーフソフトウェアアーキテクトであると
想像されたい。ある日ボスが、あなたのオフィスに次の主力製品のアイデア
を持ってやって来た。あなたの仕事は、対話型知的キッチンヘルパーを開発
することである。ソフトウェアチームに与えられた作業内容は、きわめてお
おざっぱなものであった( 使用済みのディナーナプキンの裏にボスの手書き
文字が書き殴ってあった)
。
端的にいうと対話型知的キッチンヘルパー
( Interactive Intelligent Kitchen
Helper, IIKH )は、PC ベースのアプリケーションで、平均的な家庭の厨房で
見かけるレシピ用のインデックスボックスを現代的に置き換えるものである。
しかし単にレシピのデータベースを保持するだけでなく、キッチンヘルパー
は、ある程度の期間の食事のプラン―たとえば 1 週間分―を作成する手助け
をするものである。IIKH のユーザーは、端末の前に座ってレシピのデータ
ベースを探り、コンピュータと対話しつつ一連のメニューを作成するのであ
る。IIKH は、人数分の材料を自動的に計算する。IIKH のユーザーは、1 週
間分、特定の日だけ、あるいは特定の食事分だけのメニューを印刷すること
ができる。加えて IIKH は、指定された期間全体、あるいは特定期間( 1 日、
1 食、1 皿)のレシピに必要な材料の買い物リストも印刷する。
28
第1章 エンジニアリングソフトウェア
ほとんどのソフトウェアシステムの最初の記述がそうであるように、IIKH
の仕様も多くの重要な点できわめてあいまいである。また IIKH をサポート
するソフトウェアシステムの最終的な設計と開発には、数人のプログラマが
協働してあたらなければならないということも明らかである。したがってソ
フトウェアチームの最初の目標は、記述に含まれるあいまいさを明確にして、
個々のチームメンバーによる開発に割り当てられるように、どのようにプロ
ジェクトをコンポーネント( 構成要素)へと分割できるかの概要を決定する
ことである。
Welcome
to the
IIKH
the
Interactive
Intelligent
Kitchen
Helper
Press Return
to begin
C++言語が促すプログラミングのオブジェクト指向スタイルの基礎となる
ものは、ソフトウェアをその「行動」つまり実行される動作に基づいて記述す
ることである。IIKH の開発を記述していく中で、多くのレベルでこのこと
は繰り返し現れるであろう。最初あなたのチームは、きわめて抽象度の高い
レベルでアプリケーション全体の動作を決定しなければならない。そうする
と、さまざまなソフトウェアシステムの動作の記述が、自然に浮かび上がっ
てくるはずである。すべての動作が識別されて記述されてからでないと、ソ
フトウェア設計チームは、コーディング段階へと進めないのだ。次にいくつ
かの節で、このアプリケーションを生産するためにソフトウェア設計チーム
が行う作業を追跡することにしよう。
1.1 責任駆動型設計
29
1.1.1 シナリオに沿って作業する
最初の作業は、仕様の詳細化である。すでに述べたように最初の仕様とい
うのは、ほとんど必ずといってよいほどあいまいで、最も一般的な事柄を除
いて明確さに欠ける。この段階には、いくつかの目標がある。最初のものは、
最終製品の「見かけと感じ( look and feel )」をよく表現することである。そ
のうえでこの情報は、クライアント(この場合はボス)にフィードバックされ
て、元の概念と合致するかどうか検討されるのだ。多くの場合ほとんど不可
避的に、最終的なアプリケーション用の仕様は、ソフトウェアシステムの生
産の途中で変更されるものなので、変更に容易に対応できるように設計する
こと、そしてできる限り早い時期に潜在的な変更に注意しておくことが重要
である。同じく重要なことに、この時点で最終的ソフトウェアシステムの構
造に関してきわめて高いレベルの決定を行うことができるというのがある。
とりわけ実行される動作をコンポーネントへと写像できるのである。
ビルディングや自動車エンジンのような複雑な物理システムをエンジニア
リングする際には、設計をいくつかの単位に分割することで、それを単純化
することができる。同様にソフトウェアエンジニアリングも、ソフトウェア
コンポーネントを識別して開発することにより、単純化することができる。
コンポーネント( component )とは、単に作業を実行する抽象的なもの、つ
まりある責任を果たすものである。この時点では、コンポーネントの最終的
表現を正確に知る必要はないし、コンポーネントがどのように作業を実行す
るかを知る必要もない。コンポーネントは、最終的には関数になるかもしれ
ないし、構造体やクラス(次章で言及するソフトウェア抽象の 1 つの型)、あ
るいはほかのコンポーネントの集合になるかもしれない。開発のこのレベル
では、次のようなコンポーネントの 2 つの重要な性質だけが明らかになって
いる。
●
コンポーネントは、明確に定義された少数の責任をもつ。
●
コンポーネントは、必要最小限の範囲でほかのコンポーネントと相互作用
する。
2 番目の性質の背後にある理由については、後ほど議論する。さしあたっ
ては、単にコンポーネントの責任の識別だけを考慮することにしよう。
CRC カード
責任を記録する
コンポーネントとその責任を発見するために、あなたのチームはシナリオ
に沿って仕様を詰めることにする。つまり実行できるシステムがすでにある
かのようにして、アプリケーションの実行を演技してみるのだ。実行される
すべての動作は、識別されるといずれかのコンポーネントに責任として割り
当てられる。
30
第1章 エンジニアリングソフトウェア
このプロセスの一部として、コンポーネントを 4 インチ×6 インチのイン
デックスカードを使って表すのは効果的である。カードの表面に記入する
のは、ソフトウェアコンポーネントの名前、コンポーネントの責任、そして
そのコンポーネントが相互作用しなければならないほかのコンポーネント
の名前である。このようなカードは CRC カードとして知られるが、それは
Component( コンポーネント)、Responsibility( 責任)、Collaborator( 協
働者)を意味している。1 枚のインデックスカードは、1 つのソフトウェアコ
ンポーネントに対応する。コンポーネントの責任を発見するごとに、それを
インデックスカードの表面に記録していけばよいのだ。次のとおり。
コンポーネント名
関連するコンポーネント
このコンポーネントに
割り当てられた
責任の記述
ほかの
コンポーネントのリスト
シナリオに沿って作業を進める際、設計チームのメンバーそれぞれに異な
る CRC カードを割り当てるのが有効である。コンポーネントを表現してい
るカードを保持している人物は、シナリオシミュレーションの間、そのソフ
トウェアの「 代理」として振る舞うだけでなく、関連するソフトウェアコン
ポーネントの責任を記録する係となる。設計チームのメンバーは、ソフト
ウェアシステムの動作を記述するのだが、ソフトウェアシステムがほかのコ
ンポーネントのサービスを必要とするときには、「制御」をほかの設計チー
ムメンバーに渡す。
インデックスカードの長所は、ありふれたものなのでどこでも手に入るこ
と、安価でかつ消去可能であること等である。つまり試行錯誤が容易にでき
るのだ。なぜなら別の設計も試みられるし、なにより途中で放棄してももっ
たいないということがない。カードが物理的に分かれているという点も、さ
まざまなコンポーネントの論理的分離の重要性を直感的に理解するのに役立
つだけでなく、( 後述する)結合と連結の概念を強調するのにも役立つ。イ
ンデックスカードの制約も、またおおよその複雑さを推し量るのに役立つ。
1 枚のカードのスペースに入りきらないほどの作業を実行しなければならな
1.1 責任駆動型設計
31
いようなコンポーネントは、おそらく複雑すぎるわけで、何らかのより単純
な解を求める必要がある。おそらく作業をいくつかの新しいコンポーネント
へと分割して、責任のいくつかをほかへ移さなければならないであろう。
what/who サイクル
この節のはじめで述べたようにコンポーネントの識別は、実用システムを
想像する過程で行われる。多くの場合これは、what/who 質問のサイクルに
よって進められる。まずプログラミングチームは、どのような( what )動作
が次に実行されるかを識別する。さらにそれは、その動作を誰が( who )行
うのかという疑問に答える作業が続く。このようにしてソフトウェアシステ
ムの設計は、クラブのような人の集団を組織化する過程に似ている。実行さ
れるべき動作は、いずれかのコンポーネントの責任として割り当てられなけ
ればならないのだ( いくらかくだけたいい方ではあるが、ある種の現象は自
然発生し得ることを主張するバンパーステッカー*1が流行っている。しかし
現実世界では、ほとんどあり得ないことでもある。何かが実行されるときに
は、その動作の実行を割り当てられたエージェントが必ずいるものなのだ)
。
ドキュメンテーション
この時点でドキュメンテーションの作成も開始する。どのようなソフトウェ
アシステムにも必須のドキュメントが 2 つある。すなわちユーザーマニュア
ルとシステム設計ドキュメンテーションである。どちらについても、プログ
ラムコードの第 1 行目に取りかかる前に開始することができる。
ユーザーマニュアルは、ユーザーの視点からシステムとの対話を記述する
ものであり、開発チームが抱いているアプリケーションの概念と、クライア
ントのそれが合致しているかどうかを検証するすぐれた手段である。シナリ
オを作成する中で下される決定は、最終的なアプリケーション中でユーザー
が下さなければならない決定にきわめて密接に合致しているので、ユーザー
マニュアルの作成は、シナリオに沿って進められるプロセスに自然とぴった
り合致するのだ。
設計ドキュメンテーションは、ソフトウェア設計のプロセス中で下される
主な決定を記録するものなので、関連する詳細の多くを忘れてしまう前に、
これらの決定がまだ作成者の心の中で新鮮である設計のプロセスの途中で作
成しなければならない。開発の初期にソフトウェアシステムの一般的な概略
を記述するのは、思ったよりも容易である。あまりにも早い時期に注意が、
個々のコンポーネントやモジュールのレベルに移ってしまうのが問題なのだ。
モジュールレベルのドキュメントももちろん重要ではあるが、個々のモジュー
ルの詳細に必要以上にこだわってしまうと、システムを受け継ぐソフトウェ
*1 ( 訳注)このバンパーステッカーは、"Shit Happens"というもの。意訳すると、「何
が起ころうと、俺のせいじゃねえよ。」とでもなるか。
32
第1章 エンジニアリングソフトウェア
ア保守担当者が、構造の全体像をつかむのがむずかしくなってしまう。
CRC カードは、設計ドキュメンテーションの 1 つの側面にすぎない。CRC
カードに反映されない重要な決定事項というのも多く存在する。最終決定
に影響を及ぼす要因のみならず、主な設計上の選択についての賛否両論も記
録しておかなければならない。プロジェクトスケジュールのログなり作業日
誌もきちんと保存しておかなければならない。ユーザーマニュアルと設計ド
キュメントの両方とも、ソフトウェアそのものが進化し詳細化を深めるのと
同じように、時とともに詳細化と進化を深めなければならないのだ。
コンポーネントと動作
IIKH に立ち戻ろう。システムが開始するとユーザーに、魅力的なウィンド
ウが提示されるようになると、あなたのチームは決定したとする。このウィ
ンドウを表示する責任は、グリーターと呼ばれるコンポーネントに割り当て
ることにする。まだ決まっていない方法( おそらくプルダウンメニュー、あ
るいはおそらくボタンかキーの押下、あるいはおそらくタッチスクリーン)
を使ってユーザーは、複数の動作から 1 つを選択する。最初あなたは、5 つ
の動作を識別した。次のとおり。
1. 既存のレシピのデータベースを探索するが、特定の食事プランへの参照
はない。
2. データベースに新しいレシピを加える。
3. 既存のレシピを変更したり、注記を加える。
4. 食事のために既存のプランを検討する。
5. 新しい食事プランを作成する。
これらの動作は、自然と 2 つのグループへと分割できる。前半の 3 つの動
作は、レシピデータベースに関連するもので、後半の 2 つは、メニュープラ
ンに関連するものである。その結果あなたのチームは、次にこれら 2 つの責
任に対応するコンポーネントを生成することに決定した。シナリオを進め
るに従い、あなたのチームはさしあたって食事プラン管理は無視してレシピ
データベースコンポーネントの動作を詳細化することにした。図 1-1 に、グ
リーターを表現する最初の CRC カードを示す。
おおざっぱにいえば、レシピデータベースコンポーネントの責任とは、単
にレシピの集合を保持するだけのことである。この作業の 3 つの要素につい
てはすでに識別した。すなわちレシピコンポーネントデータベースは、既存
のレシピのライブラリをユーザーが探索して編集できるような何らかの機能
を提供し、そして新しいレシピをデータベースに取り込めるようにもしなけ
ればならない。
どのようにしてユーザーにデータベースを探索させるかについての数多く
の決定も、いずれは下さなければならない。たとえばユーザーに最初「スー
1.1 責任駆動型設計
グリーター
関連するコンポーネント
最初の説明メッセージを表示する
データベースマネージャ
ユーザーにオプションを提示する
プランマネージャ
33
次のいずれかに制御を渡す
レシピデータベース
処理用プランマネージャ
図 1-1 グリーター用の CRC カード
プ」、「サラダ」、「アントレ」、「デザート」といったカテゴリのリストを示
すべきであろうか。あるいはユーザーが探索を狭めるために、キーワードを
記述できるようにするべきであろうか。それはつまり材料のリストを提供し
て、その中の項目(「アーモンド、苺、チーズ」)を含むすべてのレシピを見
るとか、前もって挿入しておいたキーワード(「ボブの好物」)のリストを見
るとかをできるようにすることである。スクロールバーを使うべきであろう
か、それともサムホールを使うべきであろうか。これらのことを考えるのは
楽しいことではあるが、認識しなければならない重要な点は、この時点で決
定しなければならない類のことではないということである( コラム「変更に
備える」35 ページ参照)。これらの事柄は、1 個のコンポーネントにのみ関
連することで、ほかのシステムの機能には影響を与えない。シナリオを進め
るのに必要なのは、何らかの手段によってユーザーが特定のレシピを選択で
きることを述べておきさえすればよいということである。
各レシピは、それぞれ特定のレシピコンポーネントにより識別される。いっ
たんレシピが選択されたならば、制御は、関連するレシピオブジェクトへと
渡される。レシピは、一定の情報を含んでいなければならない。たとえばレ
シピは、材料の集合とその材料を最終製品( 料理)へと変換するステップの
記述から構成される。このシナリオでは、レシピコンポーネントは、ほかの
動作も実行しなければならない。たとえばレシピコンポーネントは、端末の
スクリーン上にレシピを対話的に表示する。ユーザーが、材料のリストや作
業手順に手を入れて、レシピに注釈や変更を加えることができるようにした
い。あるいはユーザーは、レシピのコピーを印刷することを求めるかもしれ
ない。これらすべての動作は、レシピコンポーネントが行わなければならな
いことである( 当分の間レシピを単数扱いとする。設計段階では、これを多
34
第1章 エンジニアリングソフトウェア
くの実際のレシピを代表する典型的なレシピとして考えることにする。コン
ポーネントの単数と複数の問題については、本書の後半で議論し直すことに
する)
。
ユーザーがデータベースを探索する折りに望まれる動作について検討した
うえで、レシピデータベースマネージャに立ち戻ることにして、さしあたって
ユーザーが新しいレシピを加える意志を表明したことにしよう。データベー
スマネージャは、何らかの方法で新しいレシピを加えるべきカテゴリを決定
する( ここでもどのようにこれを行うかの詳細は、開発のこの時点では重要
でない)。そして新しいレシピの名前の問い合わせを行ったうえで、新しい
レシピコンポーネントを生成し、ユーザーが空白のエントリを編集できるよ
うにする。このようにこの新しい作業を実行するのに必要なことは、すでに
識別された作業である既存レシピの編集をユーザーができるようにするとい
う内容のサブセットであることがわかる。
データベースの探索と新しいレシピの作成について見たので、グリーター
に立ち戻って日々のメニュープランの開発について検討することにしよう。
プランマネージャが、この作業を行う。何らかの方法により( しつこいよう
だが、どのようにこれを行うかの詳細は、開発のこの時点では重要でない)
ユーザーは、既存のプランを保存できるようになっている。したがってプラ
ンマネージャは、すでに作成されているプランの検索、あるいは新しいプラ
ンの作業から開始できるようになっていなければならない。後者の場合ユー
ザーは、作成されるプランのための日付リストを入力するように求められ
る。それぞれの日付は、別個の日付コンポーネントに関連付けられている。
ユーザーは、さらなる検討を加えるために特定の日付を選択することができ
るが、その場合制御は、対応する日付コンポーネントに渡される。プランマ
ネージャのほかの作業には、計画される期間全体にわたってレシピを印字す
ることも含まれる。最後にユーザーは、全期間用の買い物リストを作成する
ように指示できる。
日付コンポーネントは、食事の集合だけでなくユーザーが提供したいろい
ろな注釈( 誕生日、さまざまな記念日等)をも保持する。日付コンポーネン
トは、特定の日付に関するディスプレイ上の情報を印字する。何らかの手段
により( やはり未指定)ユーザーは、特定の日付に関するすべての情報を印
字することも、特定の食事の詳細についても探ることができる。後者の場合
制御は、食事コンポーネントに渡される。
食事コンポーネントは、拡張されたレシピの集合を保持する。ここでいう
拡張されたとは、ユーザーがレシピを 2 人分や 3 人分あるいはもっと多人数
分という具合に指示してもよいことによる。食事コンポーネントは、食事に
関する情報を表示する。ユーザーは、特定の食事にレシピを加えたり削除し
たり、あるいはその食事に関する情報を印字するように指示することができ
る。新しいレシピを見つけるためにユーザーは、この時点でレシピデータベー
1.1 責任駆動型設計
35
グリーター
プランマネージャ
日付
レシピデータベース
食事
レシピ
図 1-2 IIKH の 6 つのコンポーネント間のコミュニケーション
スを探索できるようになっていなければならない。したがって食事コンポー
ネントは、レシピデータベースと情報のやりとりができなければならない。
このようにして設計チームは、考えられるすべてのシナリオの検討を続け
る。これまでで作成されないシナリオの主要なカテゴリは、例外的な場合に
関するものである。たとえば対応して合致するレシピがないのに、そのレシ
ピを表すキーワードをユーザーが選択したらどうなるであろうか。開始した
後でそのまま進めるのでなく、新しいレシピを入力するような作業を中断す
るにはどうすればよいのであろうか。それぞれの可能性についてよく調べて、
それぞれの状況を扱う責任をいくつかのコンポーネントに割り当てなければ
ならない。
さまざまなシナリオに沿って検討を重ねた後、あなたの設計チームは、最終
的にすべての動作は 6 個のコンポーネントによって適切に扱うことができる
と決定を下した(図 1-2 )
。グリーターコンポーネントは、プランマネージャ
コンポーネントとレシピデータベースコンポーネントとだけコミュニケー
ションを取ればよい。プランマネージャコンポーネントは、日付コンポーネ
ントとだけ、そして日付エージェントは、食事コンポーネントとだけコミュ
ニケーションを取ればよい。食事コンポーネントは、レシピマネージャとコ
ミュニケーションを取るが、このエージェントを通じて個々のレシピにアク
セスするのである。
変更に備える
人生で変わらないことといえば不確かなことと変化することだけであるとは、昔か
らよくいわれている。ソフトウェアも例外ではない。ソフトウェアシステムの最初の
仕様と設計をどのように注意深く作成したとしても、システムが稼働しているうちに
は、ほぼ確実にユーザーの要望や要求は変わるので、ソフトウェア自体も変更しなけ
ればならなくなる。プログラマやソフトウェア設計者は、そういったものを予想して
おく必要があり、次のように心がけておかなければならない。
36
第1章 エンジニアリングソフトウェア
●
変更の影響を受けるコンポーネントをできるだけ少なくする、という主目標を維持
する。表面上や機能的には大幅な変更に見えたとしても、プログラムコードでは、
2 、3 か所の変更に留まることもあるのだ。
●
変更の原因となりそうなものを予想して、そのような変更の効果を可能な限り少な
いソフトウェアコンポーネント中に隔離する。変更される可能性の最も高い原因と
なるのは、インターフェイス、コミュニケーション形式、出力形式のような機能で
ある。
●
ソフトウェアのハードウェア依存性を隔離して減少させる。たとえば本章のケース
スタディでレシピの探索のためのインターフェイスは、システムが稼働するハード
ウェアにある程度依存する。将来のリリースでは、異なるプラットフォームに移植
されるかもしれない。すぐれた設計とは、そのような変化を前もって考慮に入れて
おくものなのだ。
●
ソフトウェアコンポーネント間の連結性の減少は、コンポーネント間の依存性も減
少させるが、ほかへの影響を最小限に押さえつつ変更できる可能性は増加させる。
●
設計ドキュメント中に、設計プロセスやすべての主要な決定にいたる議論を記録し
て残しておく。ほとんどの場合、ソフトウェアの保守や将来のリリースに向けての
設計に責任を持つ人物は、最初のリリースを担当した人々のチームとまったく異な
るか、少なくとも部分的には異なる。設計ドキュメントは、将来のチームが、決定
事項の背後にある重要な要因を識別するのに役立つだけでなく、すでに解決された
問題を議論して時間を浪費することも予防する。
1.1.2 ソフトウェアコンポーネント
この節では、ソフトウェアコンポーネントの概念について詳しく調べるこ
とにする。最も簡単な概念以外すべてにあてはまることであるが、一見する
と単純な概念にも多くの異なる側面がある。
行動と状態
コンポーネントが、どのようにその動作―つまり何ができるかというこ
と―によって特徴付けられるかを見てきた。さらにコンポーネントは、そ
の中に何かしら情報を保持することもできる。IIKH のプロトタイプ的コン
ポーネントであるレシピ構造を見ることにしよう。コンポーネントを、行動
( behavior )と状態( state )の対としてとらえるのも 1 つの見方である。
●
コンポーネントの行動とは、コンポーネントが実行することのできる動作
の集合である。コンポーネントのすべての行動の完全な記述は、そのコン
ポーネントのプロトコル( protocol )と呼ばれることもある。レシピコン
ポーネントでは、レシピの調理手順を編集することとか、対話型の端末ス
クリーンにレシピを表示することとか、レシピのペーパーコピーを印刷す
るといった動作が含まれる。
1.1 責任駆動型設計
●
37
コンポーネントの状態とは、そのコンポーネントが保持するすべての情報
を表す。レシピコンポーネントでは、状態には、材料表や調理手順のリス
トが含まれる。状態は静的なものではなく、時間とともに変化するもので
あることに注目されたい。たとえばレシピの編集機能(行動)を用いてユー
ザーは、調理手順( 状態の一部)を変更することができる。
すべてのコンポーネントが、状態情報を保持する必要はない。たとえばグ
リーターコンポーネントは状態を持たないが、それは実行の途中で覚えてお
くべき情報がないからである。しかしほとんどのコンポーネントは、行動と
状態の組から構成される。
インスタンスとクラス
状態と行動の概念を分離することによって、かつての議論で避けた論点を
明確にすることができる。実際のアプリケーションでは、多くの異なったレ
シピがあることに注目されたい。しかしここで重要な点は、それらすべての
レシピが同じように実行されるということである。つまりレシピの行動それ
ぞれは同じなのだ。状態―材料や調理手順の個々のリスト―がレシピごとに
異なるだけなのである。開発の初期段階では、すべてのレシピに共通する行
動に目を向ければよく、特定のレシピの詳細は重要でないのだ。
レシピ
クラス
レシピ
インスタンス
レシピの動作
ストロベリー
ショートケーキ
野菜スープ
チーズ
エンチラーダ
クラス( class )という用語は、類似の行動を取るオブジェクトの集合を記
述するのに使用される。次章で詳しく述べるが、クラスのアイデアは、C++
言語中の機構としても使われている。クラスの個々のオブジェクトを表現す
るものは、インスタンスとして知られている。行動はクラスに関連付けられ
ていて、個々のオブジェクトに対してではないことに注目されたい。つまり
あるクラスのすべてのインスタンスは、同じ命令に応答して同じように動作
するのだ。一方状態は、個々のオブジェクトの属性である。これについては、
レシピクラスのさまざまなインスタンスで見ることになる。それらは、すべ
て同じ動作( 編集、表示、印刷)を実行するが、異なったデータ値を使用す
るのである。
38
第1章 エンジニアリングソフトウェア
連結と結合
ソフトウェアコンポーネントの設計に際して理解すべき 2 つの重要な概念
は、連結と結合である。結合とは、1 個のコンポーネントの役割がどれだけ
有意味な単位を形成しているかということの記述である。高い結合性は、1
個のコンポーネント中に相互に関連する作業を封じ込めることにより達成さ
れる。作業が相互に関連する最も頻繁な手段は、おそらく共通のデータ領域
をアクセスする必要性を通じてであろう。たとえばこれは、 レシピコンポー
ネントのさまざまな役割を結び付ける主要なテーマである。
ひるがえって連結は、ソフトウェアコンポーネント間の関係を記述するも
のである。一般に連結の量は、可能な限り少なくすることが望ましい。なぜ
ならソフトウェアコンポーネント間の結び付きは、開発や保守、再利用の容
易さを妨げるものだからである。
典型的にはあるソフトウェアが、ほかのコンポーネントが保持している
データ値―つまり状態―にアクセスしなければならないときに、連結が生じ
る。そのような状況はできるだけ避けるべきで、そのようなときは、必要な
データを保持しているコンポーネントの責任リスト中に、その作業を移すべ
きである。たとえば「レシピを編集する」という作業の責任をレシピデータ
ベースに割り当てようと考えたとする。なぜならこのコンポーネントに関連
した作業を実行するときに、レシピを編集する必要性が最初に生じるからで
ある。しかしそうしてしまうと、レシピデータベースエージェントが個々の
レシピの状態( 材料や調理手順のリストを表す内部的データ値)を直接操作
するようにしなければならなくなる。レシピ自身に自分を編集する責任を移
転することにより、このような緊密な結合を避けたほうがよい。
インターフェイスと実装
Parnas の原則
ソフトウェアコンポーネントの特徴を行動によって強調することは、あ
るきわめて重要な結論を導くことになる。すなわちプログラマは、ほかの
プログラマによって開発されたコンポーネントを、それがどのように実装
( implement )されたかを知ることなく、使い方( use )を知ることが可能にな
るのである。たとえば図 1-2 に示した 6 個のコンポーネントのそれぞれを異
なるプログラマに割り当てたとしよう。食事コンポーネントを開発するプロ
グラマは、IIKH のユーザーがレシピのデータベースを探索して、献立のた
めにレシピを選択できるようにする必要がある。そのため食事コンポーネン
1.1 責任駆動型設計
レシピの編集
テキストバッファ
新しいレシピの作成
ウィンドウポインタ
端末への表示
材料リスト
39
図 1-3 複雑なシステムへの 2 つの視点
トは、単にレシピデータベースコンポーネントに関連して、個々のレシピを
リターンするように定義されている探索( browse )行動を呼び出せるように
なっていなければならない。この記述は、 レシピデータベースコンポーネン
トが実際の探索動作を実行するのに使用する特定の実装にかかわらず正しい。
単純なインターフェイスの背後にある実装の詳細をわざと省略することは、
情報隠蔽( information hiding )として知られている。これをコンポーネント
が行動をカプセル化する( encapsulate )といい、動作の詳細ではなくどのよ
うにコンポーネントを使うかだけを示す。このことは、自然とソフトウェア
システムの 2 つの異なる視点を導く( 図 1-3 )
。インターフェイスの視点は、
ほかのプログラマが見る側面である。これは、ソフトウェアコンポーネント
が何を実行するかを記述する。ひるがえって実装の視点は、特定のコンポー
ネントを作成するプログラマが見る側面である。これは、コンポーネントが
作業を完遂するのにどのように行うかを記述する。
インターフェイスと実装の分離は、ソフトウェアエンジニアリングにおい
ておそらくもっとも重要な概念である。しかし同時に、学生にとってこの概
念を理解したり、そのための動機付けをすることはむずかしい。コラム「誰か
ら情報を隠蔽するのか( 40 ページ)」で議論するように情報隠蔽の概念は、複
数のプログラマが関与するプロジェクトの文脈でなければあまり意味がない
のだ。そのようなプロジェクトでは、限界要因はプロジェクトで要求される
コーディング量ではなく、さまざまなレベルのプログラマ間やそれに対応す
るソフトウェアシステム間で要求されるコミュニケーションの量である場合
が多い。これから述べていくように、ソフトウェアコンポーネントは、異な
るプログラマにより並行して開発されることが多いだけでなく、それらのプ
ログラマは地理的に離れていることが多い。さらに複数のプロジェクト内で
再利用できるソフトウェアコンポーネントの要請も増えてきている。このど
ちらの要求をも成功裡に満たすために、システムは、さまざまな部分間で最
小限のよく定義された結合だけを持つようにしなければならないのである。
これらのアイデアは、コンピュータサイエンティスト David Parnas によっ
て 2 つの規則にまとめられている。それらは、「 Parnas の原則」として知ら
40
第1章 エンジニアリングソフトウェア
れるものである。
●
ソフトウェアコンポーネントの開発者は、そのコンポーネントが提供する
サービスを効率的に使用するためのすべての情報をユーザーに提供しなけ
ればならない。そしてそれ以外の情報は提供してはならない。
●
ソフトウェアコンポーネントの開発者は、そのコンポーネントに割り当て
られた責任を果たすのに必要な情報をすべて与えられなければならない。
そしてそれ以外のものを受け取ってはならない。
インターフェイスを実装から分離することの帰結は、プログラマがほかの
ソフトウェアコンポーネントに影響を与えることなく、同じ構造を持つ異なっ
た実装を試してみることができるということである。これから多くのデータ
構造が、実にさまざまな実装法を持つことがわかるはずである。それぞれは、
異なる目的( 実効速度、空間利用の効率等)のために最適化されているのだ。
誰から情報を隠蔽するのか
プログラミングを始めたばかりの学生は、情報隠蔽を理解するのに苦労すること
が多い。その理由は存外簡単で、いったい誰から情報を隠蔽すればよいかが理解でき
ないのだ。1 人のプログラマで十分作成できるようなソフトウェアアプリケーション
の開発を考えてみよう。この状況では、2 人の「主人公」がいる。プログラマとコン
ピュータである。プログラマがプログラマ自身から情報を隠すことはないし、コン
ピュータはコンピュータで、実行可能なアプリケーションを生成するためにすべての
関連する詳細を知らなければならない。
この状況は、プログラマ、コンピュータ、そしてソフトウェアアプリケーションの
ユーザーという 3 人のグループを考えればわかりやすくなる。この 3 番目の人物で
あるアプリケーションのユーザーは、ソフトウェアシステムがどのような言語で書か
れているかさえ知る必要がないのだ。したがってこの場合、情報隠蔽は完成する。
このように考えると、情報隠蔽の概念は、複数の開発者からなるソフトウェアプロ
ジェクトの文脈で最も意味を持つということがわかる。そのようなシステムでは、各
プログラマは、ほかのプログラマによって開発されたソフトウェアコンポーネントの
「ユーザー」であると考えることができるのだ。ここでいう「ユーザー」とは、ソフト
ウェアアプリケーションの最終「ユーザー」とは異なるので、混同しないように注意
されたい。
1.1.3 インターフェイスを形式化する
IIKH 開発の話を続けることにしよう。引き続くステップを通じて、コン
ポーネントの記述は、徐々に詳細化を深めていく。このプロセスの最初の
ステップは、コミュニケーションのパターンとチャネルを形式化することで
ある。
1.1 責任駆動型設計
41
ここでの決定は、各コンポーネントを実装するのに使用できる一般的な構
造として下されなければならない。1 つの行動しか取らず、内部状態を持た
ないコンポーネントは関数にできる。そのようなコンポーネントの例は、テ
キストを取ってすべての大文字を小文字に変換するといったものである。多
くの作業をこなさなければならないコンポーネントは、クラスとして実装す
るほうがよいであろう。各コンポーネントに対応する CRC カード上で識別
された責任のおのおのに名前を与える。それらは最終的には、関数名なり手
続き名へと写像される。名前と並んで、関数に渡される引数の型も識別され
る。次にコンポーネント中に保持される情報も記述しなければならない。す
べての情報を列挙しておかなければならないのだ。コンポーネントが、ある
特定の作業を実行するのにあるデータを必要とするのであれば、そのデータ
がどこにあるか、つまり引数として渡されるのか、グローバル値なのか、あ
るいはコンポーネントにより内部的に保持されるのかといったことも、明確
に定義しておかなければならないのである。
さまざまな動作に関連付けられる名前には、よく気をつけなければならな
い。シェークスピアが、『ロメオとジュリエット』の中で述べているように、
オブジェクトの名前を変えたとしても、それが示すオブジェクトの性質が変
わるものではない。しかしすべての名前が、それを聞く人の中に同じイメー
ジを喚起するものでないことは確かである。行政官僚がよい例であるが、わ
けのわからない堅苦しい名前を付けることによって、きわめて単純な事柄も
何か空恐ろしいもののように見せかけることができる。したがって名前を使っ
て最終的設計が定式化されるのであるから、有効な名前を選び出すのは、き
わめて重要な作業なのである。名前は内部的に一貫していなければならない
し、有意味で、できれば短く、そして問題の文脈をよく表すものでなければ
ならない。実行される作業と操作されるオブジェクトを記述する適切な用語
群を決めるだけで、かなりの時間が費やされることもあるのだ。設計プロセ
スの初期段階で適切な名前を選定することは、不毛で不要なことどころか、
その後のステップを大幅に単純化し容易にするものなのである。
適切な名前選定のための一般的ガイドラインは、次のとおり。
●
発音できる名前を使用する。経験的にいって、声を出して読めなければ、
それはよい名前とはいえない。
●
名前の中の単語を区別するのに、それぞれを大文字なりアンダースコアで
始める。つまり cardreader でなく、CardReader や Card_reader のよう
にする。
●
省略は慎重に行う。ある人にとっては明白でも、その隣人は困惑している
かもしれない。TermProcess は、端末プロセス( terminal process )であ
ろうか、プロセスを終了させる( terminate process )ものであろうか、そ
れとも端末に関連する( associated with a terminal )プロセス( process )
であろうか。
42
第1章 エンジニアリングソフトウェア
日付
関連するコンポーネント
特定の日付に関する情報を保持する
プランマネージャ
Date(year,month,day)− 日付を生成する
食事
DisplayAndEdit() − ユーザーが編集できる
ウィンドウ中に日付情報を表示する
BuildGroceryList(List &) − すべての食事から
項目を買い物リストに加える。
図 1-4 改訂した日付コンポーネント用の CRC カード
●
複数に解釈できる名前は避ける。EMPTY という関数は、あるものが空
( empty )
かどうかを判定する関数であろうか、あるいはオブジェクトの値を空にす
る関数であろうか。
●
名前の中に数字を使わないようにする。数は、読み間違えることが多いの
だ( たとえば 0 と O 、1 と l 、2 と Z 、5 と S 等)
。
●
ブール値を生じる関数や変数の名前は、真と偽の値の解釈を明確に記述す
るようにする。たとえば PrinterIsReady が真なら、プリンタが動作中で
あることがわかる。ところが PrinterStatus では、明らかでない。
●
高価であまり頻繁に使われない操作のための名前の選択には、特別の注意
を払う必要がある。そうすることにより、誤った関数を使用することによ
るエラーを回避できる。
各動作の名前が決まったところで、その名前と関数の識別された行動を引
き起こすのに使用される仮引数を使って、各コンポーネント用の CRC カー
ドを書き直す。日付の CRC カードの例を図 1-4 に示す。まだ指定されてい
ないのは、各コンポーネントが、それぞれに関連付けられた作業をどのよう
に実行するかということである。
再びシナリオあるいはロールプレイングをより詳細なレベルで実行して、
すべての動作が列挙されて、すべての必要な情報が保持されて、それを担当
するコンポーネントに利用できるようになっていることを確認する。
1.1.4 コンポーネントの表現を設計する
まだ行われていなければこの時点で、設計チームをいくつかのグループに
分割することができる。それぞれのグループが、1 つ以上のソフトウェアコ
1.1 責任駆動型設計
43
ンポーネントを担当する。ここでの作業は、コンポーネントの記述をソフト
ウェアサブシステムの実装へと変換することである。このプロセスの主要な
部分は、割り当てられた責任を果たすのに必要な状態情報を各サブシステム
が保持するために、データ構造を設計することに当てられる。
本書の第 2 章以下すべてを費やして解説する古典的なデータ構造が、その
役割を担い始めるのはここなのだ。データ構造の選択はきわめて重要な作業
であり、ソフトウェア設計プロセスの中心的課題であるともいえる。データ
構造さえ決定できれば、責任を担当するコンポーネントによって使用される
プログラムコードは、ほとんど自明である。しかしデータ構造は、対象の作
業と密接に一致していなければならない。これから見ていくように、ある
データ構造は、特別な要素を探索するようなプロセスに向いているし、デー
タの集合を合併するのに適切なデータ構造もある。そうかと思うと値を順番
に並べるときに適切なものもあるのだ。データ構造を選び間違えると、複雑
で効率の悪いプログラムができあがってしまうが、スマートな選択では正反
対の結果を得る。
行動の記述をアルゴリズムへと変換するのも、やはりこの時点である。第
3 章では、アルゴリズムの機能について述べる。それらの中には、入力の正
確な指定、結果あるいは各プロセスの効果の記述が含まれる。そのうえでこ
れらの記述は、協調動作するものとして列挙された各コンポーネントの行う
べきことと照合して、責任が果たされかつ各プロセスを実行するのに必要な
データ項目が利用できるようになっていることを、確認しなければならない。
1.1.5 コンポーネントを実装する
各サブシステムの設計が終了したならば、次のステップは、各コンポーネ
ントに求められる行動を実装することである。これまでのステップの作業が
正しく行われていれば、それぞれの責任あるいは行動は、簡潔な記述で表さ
れているはずである。このステップにおける作業は、要求される動作をコン
ピュータ言語で実装することである。後の節で、このプロセスでよく用いら
れる経験について述べる。
すでに( つまりシステムの仕様の一部として)決定されていなければ、完
全に 1 つのコンポーネント中だけに関連する事柄について決定を下すのは、
この時点である。今回のケーススタディでわれわれが直面している典型的な
決定事項とは、どのようにすればユーザーがレシピのデータベースを探索す
るのが最も容易になるかということである。
複数のプログラマによるプログラミングプロジェクトが標準になるにつれ、
1 人のプログラマがシステムのすべての側面について携わることは希有のこ
ととなった。より頻繁にプログラマに求められる技能として、プログラム
コードのある部分がどのようにして大きなフレームワークに組み込まれるか
44
第1章 エンジニアリングソフトウェア
を理解することやチームのほかのメンバーとよく協調する能力が重要になっ
てきている。
1 つのコンポーネントを実装する中で、特定の情報や動作が、「舞台の背
後」にあるさらにほかのコンポーネントに割り当てられていて、ソフトウェ
ア抽象のユーザーにはほとんど見えないことが多いということも明らかにな
る。そのようなコンポーネントは、補助クラスとして知られることもある。
第 4 章で文字列を議論したり第 8 章でリストを議論する際に、補助クラスの
例を見ることになる。
この時点での分析とコーディングで重要な点は、作業を完遂するためにソ
フトウェアコンポーネントに要請される必要な前提条件を明らかにして文
書化することと、正しい入力値が与えられたときに、そのソフトウェアコン
ポーネントが正しく実行されることを検証することである。そうすることに
より、コンポーネントの実装で使用されたアルゴリズムの正確さの側面が確
立される。この件については、第 3 章で詳細に議論する。
1.1.6 コンポーネントの統合
ソフトウェアサブシステムを個々に設計して、テストを済ませたならば、
最終製品へと統合できる。これは 1 つのステップでなく、大きなプロセスの
部分であることも多い。単純な基盤から出発して、システム要素を少しずつ
加えて、まだ実装されていない部分用の行動がなかったり、ごく限られた行
動しか持たない単純なダミールーチンであるスタブ( stub )を使ってテスト
していくのだ。
たとえば IIKH の開発では、統合をグリーターコンポーネントから出発す
るのが理にかなっている。 グリーターコンポーネントを独立にテストする
ためには、レシピデータベースマネージャと日常献立マネージャのためのス
タブを書かなければならない。これらのスタブは、何らかの情報を持つメッ
セージを印字してリターンする以上のことを行う必要はない。これらのスタ
ブを使えば、コンポーネントの開発チームは、グリーターシステムのさまざ
まな側面( たとえば特定のボタンを押して正しい応答が表示される)をテス
トすることができる。個々のコンポーネントのテストは、単体テスト( unit
testing )と呼ばれることも多い。次にスタブを 1 つずつ本物のプログラム
コードへと置き換える。たとえばチームは、システムのほかの部分のスタブ
を保持しつつ、まずレシピデータベースコンポーネントのスタブを実際のシ
ステムで置き換えるであろう。システムが予定どおり動作するのを確認した
ら、さらなるテストを実行できる( 統合テスト( integration testing )と呼ば
れることもある)。すべてのスタブを実行可能なコンポーネントで置き換え
たときに、アプリケーションが完成するのだ。コンポーネントを独立にテス
トできるかどうかは、コンポーネント間の連結を減少させるという意識的な
1.1 責任駆動型設計
45
設計目標によって大幅に左右される。なぜならこの目標を達成することによ
り、多数のスタブを用意する必要性がなくなるからである。
統合の間に、あるソフトウェアシステム中でエラーが見つかって、しかし
それは、ほかのシステム中のコーディングミスによって引き起こされるとい
うことも珍しくない。つまり統合に際してのテストでエラーが発見される場
合も多く、そうするとコンポーネントのいくつかを変更しなければならなく
なる。そのような変更を実装した後には、そのソフトウェアを大きなシステ
ムへと統合する前に、そのコンポーネントだけを再び独立してテストし直さ
なければならない。ソフトウェアコンポーネントの変更後に、すでに開発済
みのテストケースを再実行することは、回帰テスト( regression testing )と
呼ばれることもある。
1.1.7 保守と進化
アプリケーションの製品版が引き渡されたならば、ソフトウェア開発チー
ムの作業は終了したものと考えがちである。残念なことに、そうなることは
ほとんどない。ソフトウェアシステムの最初の製品版の引き渡しに続いて発
生する作業を記述するのに、ソフトウェアの保守( software maintenance )
という用語が使用される。さまざまな広範囲な作業が、このカテゴリに含ま
れる。次のとおり。
●
引き渡しの済んだ製品の中にエラーあるいはバグ( bug )が発見された。こ
れらは、既存のリリースにパッチ( patch )をあてて訂正するか、引き続く
リリースの中で修正するかしなければならない。
●
法律の改正や類似の製品による実質的標準の変化により、要求仕様が変化
した。
●
ハードウェアが変化した。システムが、異なるプラットフォーム上に移植
されることもある。それまでなかったペンベースのシステムやタッチスク
リーンのような入力装置が利用できるようになるかもしれない。出力技術
も変化している。たとえばテキストベースのシステムからグラフィカルな
ウィンドウベースのシステムへの変更である。
●
ユーザーの希望が変化する。ユーザーは、より多くの機能、低い費用、使
いやすさを求め始めるかもしれない。これは、類似の製品との競合の結果、
生じることが多い。
●
よりよいドキュメンテーションが、ユーザーから要求されることもある。
優れた設計というものは、変更が生じる必然性をきちんと認識していて、
最初から変更にどう対処するかを計画に入れておくものである。
46
第1章 エンジニアリングソフトウェア
1.2 再利用可能なコンポーネントによるプログラミング
われわれの責任駆動型設計の話を貫いている 2 つのテーマは、行動による
ソフトウェアコンポーネントの特徴付けと、可能な限り相互に独立したコン
ポーネントの開発である。これらの目標の達成度によっては、1 つのソフト
ウェアプロジェクトで使用したソフトウェアをほかのプロジェクトでも使用
できるようになる。
汎用コンポーネントの再利用から生じる明白な技術的経済的利益は、次の
とおり。
●
再利用できるコンポーネントを書き直す必要がない。つまり 2 回目のプロ
ジェクトで同等のソフトウェアを開発するのに費やされるプログラミング
の時間を、ほかの目的のために振り分けられる。
●
コンポーネントの再利用にかかる費用は、コンポーネントを開発し直すの
に必要な費用に比べるとはるかに少ない。
●
標準的なコンポーネントを使用することによってプログラムは、可読性も
保守性も向上する。なぜなら複数のアプリケーションにわたって、プログ
ラムコードのかなりの部分が共通になるからである。
●
複数のプロジェクトで使用されるソフトウェアコンポーネントは、1 つの
プロジェクトにだけ使用されるプログラムコードに比べて、より徹底的に
テストとデバッグが行われる。すべてのアプリケーションでエラーが追跡
され修正されるのであれば、複数のプロジェクトが利益を享受することに
なる。
コンピュータプログラミングのコミュニティにおいて近年の再利用可能な
コンポーネントの開発が強調されるようになったのは、19 世紀の産業革命
における歴史的進歩と類似している。かつてコンピュータプログラムは、完
全に何もないところから 1 つずつ作成された。つまりプログラムコードの 1
行 1 行が、新しいアプリケーションごとに開発されたのだ。これは、かつて
工場で使用される機械が、鍛冶屋やその他の職人によって 1 つずつ手作りさ
れていたのに相当する。再利用可能で交換可能な部品の開発が、機械を製造
する方法に革命をもたらしたのと同様に、交換可能なコンポーネントの開発
は、ソフトウェアが作成される方法をも変化させているのだ。本書のかなり
の部分は、ほとんどすべてのコンピュータプログラムで部品となり得るよう
な、単純で再利用可能なソフトウェアコンポーネントの要約集として見るこ
ともできる。
1.3 問題解決における経験則
1.2.1 再利用の機構
47
合成と継承
汎用ソフトウェアコンポーネントは重要であるが、それらを特定のアプリ
ケーションのために使用する前に、ほとんど必ず変更なりカスタマイズを加
えなければならない。このカスタマイズのために最も頻繁に使用される 2 つ
の機能、合成と継承について述べよう。
合成とは、単にあるコンポーネントがそのデータ領域や状態の一部を定義
するのに、ほかのコンポーネントを使用することである。たとえばレシピコ
ンポーネントでは、材料のリストを保持するのに第 8 章で議論するリンクリ
ストを使うのがよい。同様にしてグリーターコンポーネントは、その状態の
一部としてレシピデータベース全体を保持するであろう。 継承の機構はより
重要であり、多くの面でより強力である。コンポーネントの 1 つの型が、別
の型の変形にすぎないと見えることもまれではない。そのような場合新しい
コンポーネントは、元の構造の特殊形として生成することができる。このと
き新しい形は、元の形を継承( inherit )しているという。継承関係とは、元の
構造で知られているものすべて―全データ領域、全行動―が新しいコンポー
ネントでも利用可能であることを意味する。加えて新しいコンポーネントは、
それ自身のデータフィールドや新しい行動を定義してもよい。
継承と生物の系統樹との間の類似性に言及されることも多い。動物クラス
は、すべての動物に共通の一般的行動( たとえば呼吸)によって記述するこ
とができる。哺乳類クラスは、動物の特殊形であり、動物で知られているこ
とはすべて哺乳動物でも真である。猫クラスは、哺乳類のさらに下位の分類
となる特殊形である。各レベルで新しい情報が付け加えられはするものの、
分類体系中の高レベルから導かれる特性は、新しい範疇でも適用できるので
ある。
第 6 章で合成と継承の問題に立ち戻り、これらの機構がソフトウェアシス
テムの開発において、どのように使用されるかを詳細に検討する。
1.3 問題解決における経験則*
1.1.5 項において、ソフトウェアの責任の非形式的な記述を取り、それを実
際のソフトウェアサブシステムへと変換する作業について議論した。この節
では、その作業を完遂するうえで頻繁に使用される、いくつかの経験則につ
いて検討を加える。
* アスタリスクのついた節は、オプションである。
48
第1章 エンジニアリングソフトウェア
もの
生物
非生物
動物
植物
爬虫類
ソックス
岩石
哺乳類
人間
医者
猫
犬
著者
学生
私
あなた
かものはし
図 1-5 典型的な継承階層
1.3.1 トップダウン設計と段階的詳細化
作業の一般的で抽象的な記述から出発して、実際のプログラムまで繰り
返し詳細化を深めていくプロセスのことを、トップダウン設計( top-down
design )と呼ぶことがある( なぜなら問題記述である最上位( top )から始め
て、実際のプログラムコードである底辺( bottom )に向かって作業を進める
からである)。このプロセスは、段階的詳細化( stepwise refinement )と呼
ばれることもあるが、それは、各段階において問題の一部が完全な記述へと
詳細化されていくからである。
以下の節で、詳細化が行われるいくつかの方法を示すことにする。
1.3.2 複数ステップに分割する
よくいわれることわざに、「長い道のりも一歩から」というのがある。複
雑な操作を含む多くの作業も、一連の小さな個々の作業へと分割することに
より単純化できる。たとえば第 4 章で、部分文字列の置換の問題を考える。
この作業は、1 つの文字列を取り、その一部を異なる文字列で置き換えた新
しい文字列を生成することである。置き換える文字列は、削除される文字列
よりも長いこともあれば短いこともある。
1.3 問題解決における経験則
接頭辞
中辞
接尾辞
ca
rol
ine
接頭辞
新しいテキスト
接尾辞
ca
ther
ine
新しいテキスト
ther
49
元の文字列
最終結果の文字列
置換する文字列
この作業は、次の 4 つのステップにより達成される。
1. 結果を入れるのに十分なサイズの新しい文字列領域を確保する。
2. この新しい文字列領域に、元の文字列で置換される部分に先行する接頭
辞をコピーする。
3. 新しい文字列領域に、置換するテキストをコピーする。
4. 新しい文字列領域に、元の文字列で置換される部分に続く接尾辞をコピー
する。
これらの各ステップは、そのうえで元の問題の完全な解が得られるまで、
さらに詳細化される問題となる。
1.3.3 場合に分ける
抽象化のあるレベルでは 1 つの作業のように見えることでも、抽象の詳細
なレベルで見ると、多くの特別な場合からの 1 つの選択として考えたほうが
ふさわしいこともある。第 3 章で整数の印刷を記述するところで、そのよう
なものの例を見る。クライアントにとっては、印字するという動作に関して、
すべての整数は同じであるものの、実際の実装に際しては、この作業は、次
の3つの場合に分けることが有効なのだ。実際の実装に際しては、この作業
は、次の 3 つの場合に分けるのが有効である。
●
値が、負数の場合
●
値が、ゼロ以上で 10 未満の数の場合
●
値が、10 以上の数の場合
こうすることにより大きな問題の解が、3 つの単純な問題の解へと簡約で
きる。このプロセスをたどる中で、1 つの場合の解がほかの場合の解を含む
ことがあるのがわかる。それも印刷例で見ることになるが、そこでは負の数
の印字が、まずマイナス記号を印字してから正の部分を印字することにより
達成される。したがって第 1 の場合が、ほかの 2 つの場合へと簡約できるの
だ。同様に第 3 の 10 以上の数の印字の場合も、第 2 の場合の解を繰り返し
表すことによって解決できる。このようにして、場合分けを行うことは、
(以
下で議論する)共通部分の探索をも含むことが多い。
50
第1章 エンジニアリングソフトウェア
1.3.4 分割統治
特別な場合を識別できないようなときでも、1 つの問題を 2 つのほぼ等し
い部分へと分割してそれぞれを独立に解けることがある。たとえば第 5 章で、
値のベクトルの整列の問題を調べる。マージソート( merge sort )として知
られるアプローチでは、n 個の値のリストを半分に分割して、n/2 個の値の
2 つのリストとする。そのうえで各リストを独立に整列して、その結果を合
併することで全体が整列したリストを得るのだ( 多様なマージソートについ
ては、第 17 章で議論する)
。
2971543806
29715
43806
12579
03468
0123456789
問題に対するこのような一般的アプローチ―サイズ n の問題をサイズ n/2
の 2 つの問題へと分割して、小さくなった問題を解いてから結果を組み合わ
せる―は、分割統治( divide and conquer )として知られている。
1.3.5 共通部分の探索
表面上は異なっているように見える状況も、別の光を当てると共通部分が
浮かび上がってくることも多い。たとえば IIKH の設計でユーザーは、個々
のレシピを見たり編集したりできることを指摘した。さらにユーザーが、新
しいレシピを生成できるようにしたいと表明したとする。「空のレシピ」を
生成することが可能であれば、後者のプロセスは、2 つのステップ、( 1 )新
しい空なレシピの生成と、( 2 )空なレシピの編集を組み合わせたもの、とみ
なすことができる。したがってレシピを編集する動作は、レシピオブジェク
トの異なる 2 つの行動に共通ということになり、1 つの関数へと合併するこ
とができるのだ。
1.4 まとめ
51
1.4 まとめ
▼キーワード
Key Concepts
責任駆動型設計
● ソフトウェアコンポー
ネント
● 行動によるソフトウ
ェアコンポーネント
の特徴付け
● 行動と状態
● インスタンスと
クラス
● インターフェイスと
実装
● 再利用可能な
コンポーネント
● 連結と結合
● Parnas の原則
● ソフトウェアの
テスト
● ソフトウェアの保守
● 合成と継承
本章の主な目的は、どのようにしてソフトウェアアプリケーションを構造
化するかということであり、それは 1 つの単一体としてでなく、緩く結合し
●
た相互作用するソフトウェアコンポーネントの集合として構造化するという
ことである。コンポーネントは、2 つの側面から構成されるものと見ること
ができる。すなわち行動(そのコンポーネントが実行するもの)と状態(その
コンポーネントが保持するデータ)である。すべてのコンポーネントは、コ
ンポーネントの行動の性質であるインターフェイスと、その行動を実装する
のに必要な動作を実際に実行するプログラムコードである実装部によって、
記述することができる。
本章では、コンポーネントを早期に識別することを強調し、行動によって
コンポーネントの性質を表すのを促す設計技法を紹介した。行動による性質
の表現は、オブジェクト指向プログラミングの基本的教義である。
現代のソフトウェア開発の重要な側面は、汎用ソフトウェアコンポーネン
トの使用を通じての既存のプログラムコードの再利用である。新しいコン
ポーネントは、既存のコンポーネントからさまざまな方法によって構築する
ことができる。最もありふれた方法は、合成である。そこではコンポーネン
トが、単にその状態の一部を保持するのにほかのコンポーネントを使用す
る。もう 1 つの方法は、継承である。継承を使用することによって新しいコ
ンポーネントは、既存のコンポーネントを特殊化したものとして記述するこ
とができる。
本章では、オブジェクト指向プログラミング技法の基礎となる哲学を記述
するように努めた。次の章では、C++を使ってソフトウェアコンポーネント
を生成する実際の機構について調べることにする。
52
第1章 エンジニアリングソフトウェア
■ 参考文献
オブジェクト指向プログラミングで使用される機構は、1960 年代初期の
言語 Simula[ Birtwistle 79, Kirkerud 89 ]にまでさかのぼれるが、そのア
イデアが広く受け入れられるようになったのは、Xerox PARC( Palo Alto
Research Center )における Alan Kay と Adele Goldberg のグループの研究
と言語 Smalltalk-80 によってである[ Goldberg 83 ]
。Smalltalk とオブジェ
クト指向プログラミングについて記述した論文を集めた『 Byte 』の 1981 年 8
月号は、多くの人々のイマジネーションを獲得するのに大きな役割を果たし
た。興味深いことに Kay に霊感を与えたもともとの問題―コンピュータの
非専門家が使用する強力なプログラミング言語の創造―は、今日では比較的
小さな業績とされている。
CRC カードは、Kent Beck と Ward Cunningham によって初めて記述さ
れた[ Beck 89 ]。責任駆動型設計という用語は Rebecca Wirfs-Brook ほか
によるもので、最初にそのアイデアを学会発表[ Wirfs-Brook 89 ]したうえ
で、拡張して書籍[ Wirfs-Brook 90 ]にまとめた。このアイデアは、著者に
よるオブジェクト指向プログラミングに関する書籍[ Budd 91 ]の中でも議論
されている。近年オブジェクト指向設計について記述した書籍も多数刊行さ
れている[ Coad 90, Coad 91, Mullin 89, Rumbaugh 91 ]。41 ページで議論
したオブジェクトの名前付けのガイドラインは、Keller[ Keller 90 ]による。
連結と結合の概念は、Stevens ほか[ Stevens 81 ]による。
David Parnas は、実際に[ Parnas 72 ]の中で彼の原則を初めてモジュー
ルを用いて述べた。それは最終的には、ソフトウェアシステムのすべての型
に拡張された。
再利用可能なソフトウェアコンポーネントと工業製品における交換可能な
部品開発の類似性は、Brad Cox によって指摘されている[ Cox 90, Cox 91 ]
。
1.4 まとめ
練 習 問 題
E x e r c i s e s
53
1. よく知っている組織について、コンポーネントと責任の観点から記述
せよ。各コンポーネントの CRC カードも作成せよ。
2. 1 組のカードを表現するソフトウェアコンポーネントを設計すると仮
定する。どのような操作を提供するべきであろうか。作業を 2 つのコ
ンポーネントに分割して、それぞれ組と個々のカードを表現するよう
にすることは理にかなっているだろうか。それぞれの責任が何である
か述べよ。
3. 対をなす以下の各用語を、それぞれ比較し説明せよ。( a )行動と状態、
( b )インスタンスとクラス、( c )インターフェイスと実装、( d )連結
と結合、( e )合成と継承。
4. IIKH の残りの部分の CRC カードを作成せよ。
5. あなたの会社が 2 種類の IIKH をサポートすると決定したとする。一
方はマウスベースで、他方はキーボードベースである。変更しなけれ
ばならない機能を含むコンポーネントはどれか。
6. あなたのソフトウェアチームは、ユーザーがレシピデータベースを探
索する方法を―たとえばユーザーがキーワードを指定できるように―
変更することに決定したと仮定する。このような変更の影響を考慮し
ておかなければならないソフトウェアコンポーネントはどれであるか
( つまりそのような変更に対応するために、変更する必要のあるコン
ポーネントはどれか)
。
7. レシピを人数に合わせて増量する問題を考える。卵のような材料は、
離散的にしか増やせない―つまり卵 2.7 個というような量は表せない。
増量を扱うソフトウェアにどのような困難が生じるか。そのような困
難に対応しなければならないコンポーネントはどれか。
8. 次のソフトウェアリリースでは、レシピの既存のファイルを取り込め
る機能を含めることを社長が提案した。そうすることによって会社は、
電子料理ブックを売ることができる。ユーザーは、レシピを電子ブッ
クのファイルからレシピデータベースへとコピーするのだ。この機能
をサポートするために、アプリケーションのどの部分を変更する必要
があるか。
54
第1章 エンジニアリングソフトウェア
9. ある日あなたのボスは、IIKH のユーザーが日付独立な献立を立案でき
たらよいと考えた。つまりユーザーは、とりたてて特定の日付の献立
に関連するものでない、仮想の祝日用ごちそうの集合を立案できるの
である。後にユーザーは、献立の集合を探索して、そこから特定の日
のためのものを選び出すのだ。この変更に対応するためには、アプリ
ケーションをどのように変更しなければならないかを述べよ。
10. 次の日またボスが来て、電子料理ブックの販売(演習問題 8 参照)がす
ぐれたアイデアならば、あらかじめ立案済みの献立を含めた電子料理
ブックを販売するのは、よりすぐれたアイデアであると宣言した。特
定の日付に縛られない献立を含める機能をすでに持っているので、こ
の新しい機能の追加はむずかしくないはずである。この機能を提供す
るために、アプリケーションをどのように変更すればよいか説明せよ。
11. CRC カードを用いて、銀行の ATM( Automated Teller Machine )を
サポートするソフトウェアシステムを設計せよ。
12. CRC カードを用いて、ビデオストア用のソフトウェアシステムを設計
せよ。そのソフトウェアシステムを用いてユーザーは、タイトルのリ
ストを探索できなければならないが、まず映画のカテゴリを選んで探
索範囲を狭めるような手法が取れなければならない。タイトルを選び
出したならば、ユーザーは、その映画の予告編を見ることができる。
ソフトウェアは、ユーザーの選択の記録を取り、トランザクションの
完了時に要約リストを印刷する。