プログラム設計
はじめに
データモデルとアプリケーションルールの設計が終わったので,プログラム設計を始めます.プログラム設計で行わなければならないのは,以下の3点です.
- 抽象データ型の定義.
- 抽象データ型を操作するプログラムの定義.
- メインプログラムの定義.
これらは,使用するプログラム言語からは独立して行えます.言い換えれば,どのようなプログラム言語を使用しても,上記3点をプログラム設計として行わなければなりません.
この文書で注意してほしいことが上記の他にもう一つあります.それはER図やDFDの成果をプログラムへ反映させる方法です.せっかく作ったのですから,ER図やDFDの成果をそのままプログラム設計への反映させるべきです.ER図やDFDを書いたとしても,それだけで終ってしまうことも多々あります.つまり,せっかく書いたER図やDFDの結果をプログラム設計に活かしきれず,この間に断層が存在しているのです.これでは,せっかくのER図やDFDが台無しです.
抽象データ型の定義
抽象データ型を,ここでは以下のように定義します.
- 抽象データ型
- メインプログラムの中で一塊として扱うデータの単位.
抽象データ型はプログラム言語が本来備えている整数型や文字型といったデータ型と同様に,その内部構造を意識せずに使用できなければなりません.内部構造を意識しなくても使用できるように,操作プログラムを用意します.
多くの場合,抽象データ型は内部構造として局所変数を持ちます.局所変数は抽象データ型の内部でのみ有効で,これらへの操作も上述した操作プログラムでのみ行います.
抽象データ型の定義が変わった時には,操作プログラムのインタフェース(引数や返却値)は変えず,操作プログラムの内部や局所変数に変更を加えます.こうすることで,抽象データ型内部の変更がプログラム全体に与える影響を小さくします.
また,メインプログラムが変わった時には,抽象データ型や操作プログラムには手を入れず,メインプログラムのみを修正します.これで正しい結果が出ないとすれば,それは手を入れたメインプログラムに問題があることになります.こうすることで,テストの負荷を少なくできます.
それでは,これまで作成したER図とDFDを元に,具体的に抽象データ型を定義しましまう.作成したER図とDFDは以下のようなものでした.
![]() |
![]() |
![]() |
![]() |
まず,ER図のエンティティを抽象データ型として定義します.すなわち,エンティティ名を抽象データ型の名称とし,エンティティの属性を抽象データ型の局所変数とします.この時点では,局所変数のデータ型(文字とか数値とか)は考慮すべきではありません.また,特定のプログラミング言語で記述する必要もありません.ここでは擬似コードを使用しますが,特定のプログラミング言語で記述しても構いません.その際は,データ型はあくまでも目安であり,将来変るかもしれないという前提があることを忘れてはなりません.
名称 | 局所変数 |
---|---|
ライセンス | ライセンス番号 ソフトウェア名称 販売元名称 購入日時 価格 |
ソフトウェア | ソフトウェア名称 製造元名称 |
アップグレード | ライセンス番号 旧バージョン 新バージョン アップグレード日付 |
取引先 | 取引先名称 住所 |
社員 | 社員番号 氏名 |
管理者 | ライセンス番号 社員番号 担当開始日付 担当終了日付 |
導入機器 | ライセンス番号 機器ID 導入日付 削除日付 |
以下のものは,ER図には出てきませんが,抽象データ型として定義します.上記抽象データ型が個々のものを表したのに対し,これらは抽象データ型の集りを表現したものです.局所変数については考慮しません.
名称 |
---|
ライセンス群 |
ソフトウェア群 |
アップグレード履歴 |
取引先群 |
社員群 |
管理者群 |
導入機器群 |
抽象データ型を操作するプログラムの定義
定義した抽象データ型を操作するプログラムを定義します.これらの操作プログラムを使用して,抽象データ型の内部構造を意識せずに,メインプログラムが作成できなければならなりません. したがって,ここで定義するプログラム以外では抽象データ型の内部構造へのアクセスは行いません.抽象データ型内部へのアクセスをこれらのプログラムに限定することで,抽象データ型内部の値を保証します.すなわち,ここで定義したプログラム以外では抽象データ型を操作しないのですから,変な値が入っていれば,これらのプログラムに間違いがあるということです.
まず,「DFDでデータの保管元・保管先からデータを取り出したり,何らかの影響を与えている所」を抽象データ型操作プログラムとして定義します.以下にDFDを元に定義した操作プログラムを記述します.操作プログラムの記述書式は,Cの関数に似せ次の通りとします.
返却値 プログラム名(引数)
名称 | 局所変数 | 操作プログラム |
---|---|---|
ライセンス | ライセンス番号 ソフトウェア名称 販売元名称 購入日時 価格 |
ライセンス番号 getライセンス番号(ライセンス) ソフトウェア名称 getソフトウェア名称(ライセンス) 販売元名称 get販売元名称(ライセンス) 購入日時 get購入日時(ライセンス) 価格 get価格(ライセンス) ライセンス setライセンス番号(ライセンス,ライセンス番号) ライセンス setソフトウェア名称(ライセンス,ソフトウェア名称) ライセンス set販売元名称(ライセンス,販売元名称) ライセンス set購入日時(ライセンス,購入日時) ライセンス set価格(ライセンス,価格) |
ソフトウェア | ソフトウェア名称 製造元名称 |
ソフトウェア名称 getソフトウェア名称(ソフトウェア) 製造元名称 get製造元名称(ソフトウェア) ソフトウェア setソフトウェア名称(ソフトウェア,ソフトウェア名称) ソフトウェア set製造元名称(ソフトウェア,製造元名称) |
アップグレード | ライセンス番号 旧バージョン 新バージョン アップグレード日付 |
ライセンス番号 getライセンス番号(アップグレード) 旧バージョン get旧バージョン(アップグレード) 新バージョン get新バージョン(アップグレード) アップグレード日付 getアップグレード日付(アップグレード) アップグレード setライセンス番号(アップグレード,ライセンス番号) アップグレード set旧バージョン(アップグレード,旧バージョン) アップグレード set新バージョン(アップグレード,新バージョン) アップグレード setアップグレード日付(アップグレード,アップグレード日付) |
取引先 | 取引先名称 住所 |
取引先名称 get取引先名称(取引先) 住所 get住所(取引先) 取引先 set取引先名称(取引先,取引先名称) 取引先 set住所(取引先,住所) |
社員 | 社員番号 氏名 |
社員番号 get社員番号(社員) 氏名 get氏名(社員) 社員 set社員番号(社員,社員番号) 社員 set氏名(社員,氏名) |
管理者 | ライセンス番号 社員番号 担当開始日付 担当終了日付 |
ライセンス番号 getライセンス番号(管理者) 社員番号 get社員番号(管理者) 担当開始日付 get担当開始日付(管理者) 担当終了日付 get担当終了日付(管理者) ライセンス setライセンス番号(管理者,ライセンス番号) ライセンス set社員番号(管理者,社員番号) ライセンス set担当開始日付(管理者,担当開始日付) ライセンス set担当終了日付(管理者,担当終了日付) |
導入機器 | ライセンス番号 機器ID 導入日付 削除日付 |
ライセンス番号 getライセンス番号(導入機器) 機器ID get機器ID(導入機器) 導入日付 get導入日付(導入機器) 削除日付 get削除日付(導入機器) 導入機器 setライセンス番号(導入機器,ライセンス番号) 導入機器 set機器ID(導入機器,機器ID) 導入機器 set導入日付(導入機器,導入日付) 導入機器 set削除日付(導入機器,削除日付) |
抽象データ群には以下の操作プログラムを定義します.
名称 | 操作プログラム |
---|---|
ライセンス群 | ライセンス getFirst(ライセンス群) ライセンス getNext(ライセンス群,ライセンス) 真偽値 isLast(ライセンス群,ライセンス) ライセンス add(ライセンス群,ライセンス) ライセンス remove(ライセンス群,ライセンス) |
ソフトウェア群 | ソフトウェア getFirst(ソフトウェア群) ソフトウェア getNext(ソフトウェア群,ソフトウェア) 真偽値 isLast(ソフトウェア群,ソフトウェア) ソフトウェア add(ソフトウェア群,ソフトウェア) ソフトウェア remove(ソフトウェア群,ソフトウェア) |
アップグレード履歴 | アップグレード getFirst(アップグレード履歴) アップグレード getNext(アップグレード履歴,アップグレード) 真偽値 isLast(アップグレード履歴,アップグレード) アップグレード add(アップグレード履歴,アップグレード) アップグレード remove(アップグレード履歴,アップグレード) |
取引先群 | 取引先 getFirst(取引先郡) 取引先 getNext(取引先郡,取引先) 真偽値 isLast(取引先郡,取引先) 取引先 add(取引先郡,取引先) 取引先 remove(取引先郡,取引先) |
社員群 | 社員 getFirst(社員群) 社員 getNext(社員群,社員) 真偽値 isLast(社員群,社員) 社員 add(社員群,社員) 社員 remove(社員群,社員) |
管理者群 | 管理者 getFirst(管理者群) 管理者 getNext(管理者群,管理者) 真偽値 isLast(管理者群,管理者) 管理者 add(管理者群,管理者) 管理者 remove(管理者群,管理者) |
導入機器群 | 導入機器 getFirst(導入機器群) 導入機器 getNext(導入機器群,導入機器) 真偽値 isLast(導入機器群,導入機器) 導入機器 add(導入機器群,導入機器) 導入機器 remove(導入機器群,導入機器) |
プロセスの定義
ここまでで定義した抽象データ型と操作プログラムでDFD内のプロセスの大まかな処理を書いてみます.ここでも,具体的なプログラミング言語を使用する必要はなく,擬似コードで充分です.今回はc言語似の擬似コードを使用します.もちろん,特定のプログラミング言語を使用しても構いません.抽象データ型や操作プログラムの過不足の検討が主目標です.
対象となるプロセスは以下の通りです.
- (ライセンスの)購入
- (ライセンスの)破棄
- アップグレード
- 管理者登録
- 管理者抹消
- (機器への)導入
- (機器からの)削除
- (ライセンスの)使用状況
全てのプロセスについて記述すると,膨大な量になります.ここでは,プロセス「(ライセンスの)購入」と「(ライセンスの)破棄」について考えます.まず,プロセス「(ライセンスの)購入」についてです.まず,管理者が把握する必要のあるものは,以下の項目です.
- 購入したライセンスのライセンス番号
- 購入したライセンスのソフトウェア名
- 購入したライセンスの製造元
- 購入したライセンスの販売元
これらを入力(引数)としてプログラムを作成します.
void ライセンス購入(ライセンス番号 購入ライセンス番号, ソフトウェア名 購入ソフトウェア名, 取引先 購入ライセンス製造元名称, 取引先 購入ライセンス販売元名称){ ライセンス 購入ライセンス ソフトウェア 購入ソフトウェア 取引先 購入ライセンス製造元 取引先 購入ライセンス販売元 ライセンス群 保有ライセンス群 ソフトウェア群 保有ソフトウェア群 取引先郡 過去の取引先群 setライセンス番号(購入ライセンス,購入ライセンス番号) setソフトウェア名(購入ソフトウェア,購入ソフトウェア名) set取引先名称(購入ライセンス製造元,購入ライセンス製造元名称) set取引先名称(購入ライセンス販売元,購入ライセンス販売元名称) setソフトウェア名(購入ライセンス,購入ソフトウェア名) set販売元名称(購入ライセンス,購入ライセンス販売元名称) set販売元名称(購入ソフトウェア,購入ライセンス製造元) add(保有ライセンス群,購入ライセンス) add(保有ソフトウェア群,購入ソフトウェア) add(過去の取引先群,購入ライセンス製造元) add(過去の取引先群,購入ライセンス販売元) }
例えば,ライセンス番号は特定のアルゴリズムにより真偽を判定できるようになるかもしれません.この機能は抽象データ型「ライセンス」の操作プログラム「setライセンス番号(ライセンス,ライセンス番号)」に実装します.ライセンス番号の真偽について管理者は導入時に知る必要がないのであれば,この変更はプログラム「ライセンス購入」に影響を与えません.
データの保管元・保管先への保管や取出しは抽象データ群で行います.そうすれば,データ保管元・保管先の種類(ファイルやデータベース)を気にせずプログラムを作成できます.
また,既に同じソフトウェアや取引先が存在した場合,重複して登録はしません.これらの判定も抽象データ群で行います.すなわち,重複して登録する・しないはデータの保管元・保管先の都合であり,プロセスが判定することではありません.
続いて,プロセス「(ライセンスの)破棄」について考えます.管理者が把握する必要のある者は以下の項目です.
- 破棄するライセンス番号
これを入力(引数)としてプログラムを作成します.
result ライセンス破棄(ライセンス番号 破棄ライセンス番号){ ライセンス 対象ライセンス ソフトウェア 対象ソフトウェア 取引先 対象取引先 管理者 対象管理者 アップグレード 対象アップグレード ライセンス群 保有ライセンス群 ソフトウェア群 保有ソフトウェア群 導入機器群 現導入機器群 取引先群 現取引先群 アップグレード履歴 現アップグレード履歴 for(対象ライセンス=getFirst(保有ライセンス群);!isLast(保有ライセンス群,対象ライセンス);対象ライセンス=getNext(保有ライセンス群,対象ライセンス)){ if(getライセンス番号(対象ライセンス)==破棄ライセンス番号)&&(!is導入済み(現導入機器群,対象ライセンス)){ // (1)操作プログラム「is導入済み」については後述 remove(保有ライセンス群,対象ライセンス) break } else if(getライセンス番号(対象ライセンス)==破棄ライセンス番号)&&(is導入済み(現導入機器群,対象ライセンス)){ // (1)操作プログラム「is導入済み」については後述 return Error } } for(対象ソフトウェア=getFirst(保有ソフトウェア群);!isLast(保有ソフトウェア群,対象ソフトウェア);対象ソフトウェア=getNext(保有ソフトウェア群,対象ソフトウェア)){ if(getソフトウェア名称(対象ソフトウェア)==getソフトウェア名称(対象ライセンス)){ remove(保有ソフトウェア群,対象ソフトウェア) break } } for(対象取引先=getFirst(現取引先群);!isLast(現取引先群,対象取引先);対象取引先=getNext(現取引先群,対象取引先)){ if(get取引先名称(対象取引先)==get販売元名称(対象ライセンス)||get取引先名称(対象取引先)==get製造元名称(対象ソフトウェア)){ remove(現取引先群,対象取引先) } } for(対象管理者=getFirst(現管理者群);!isLast(現管理者群,対象管理者);対象管理者=getNext(現管理者群,対象管理者)){ if(getライセンス番号(対象管理者)==getライセンス番号(対象ライセンス)){ remove(現管理者群,対象管理者) //破棄するライセンスの管理者は複数いるかもしれないのでbreakはなし. } } for(対象アップグレード=getFirst(現アップグレード履歴);!isLast(現アップグレード履歴,対象アップグレード);対象アップグレード=getNext(現アップグレード履歴,対象アップグレード)){ if(getライセンス番号(対象アップグレード)==getライセンス番号(対象ライセンス)){ remove(現アップグレード履歴,対象アップグレード) //破棄するライセンスのアップグレード履歴は複数あるかもしれないのでbreakはなし. } } return OK }
(1)で使用した操作プログラム「is導入済み(導入機器群,ライセンス)」を,新しく抽象データ型「導入機器群」の操作プログラムとして追加します.
流れ図ではなくDFDを使った理由
データフローダイアグラムの書き方で後回しにした「流れ図ではなく DFD を使った理由」をここで明らかにします.
プログラムの制御の流れを描く流れ図では,プログラムを作るために必要な抽象データ型や,データ型へのアクセス関数を表現できません.ここで書いたような,抽象データ型,およびそれらを操作するプログラムの定義を行った後,メインプログラムを作成するという手順は,流れ図とには合わないのです.
まとめ
- プログラム設計は使用するプログラム言語とは独立して行える.
- プログラム設計で行わなければならないのは,以下のことである.
- ER図のエンティティ,DFDのデータ保管元・保管先より抽象データ型を定義する.
- 抽象データ型を操作するプログラムを定義する.
- DFDのプロセスを抽象データ型と操作プログラムを用いて作成する.
- 設計時に,プログラム寄りのこと(例えば,局所変数のデータ型)を決める必要はない.
付録
このプログラムをオブジェクト指向っぽく書くならこんな感じです.
クラス名称 | 属性 | メソッド |
---|---|---|
ライセンス | ライセンス番号 ソフトウェア 販売元 購入日時 価格 |
ライセンス番号 getライセンス番号() ソフトウェア getソフトウェア() 取引元 get販売元() 購入日時 get購入日時() 価格 get価格() ライセンス setライセンス番号(ライセンス番号) ライセンス setソフトウェア(ソフトウェア) ライセンス set販売元(販売元) ライセンス set購入日時(購入日時) ライセンス set価格(価格) |
ソフトウェア | ソフトウェア名称 製造元 |
ソフトウェア名称 getソフトウェア名称() 取引先 get製造元() ソフトウェア setソフトウェア名称(ソフトウェア名称) ソフトウェア set製造元(取引先) |
アップグレード | ライセンス 旧バージョン 新バージョン アップグレード日付 |
ライセンス getライセンス() 旧バージョン get旧バージョン() 新バージョン get新バージョン() アップグレード日付 getアップグレード日付() アップグレード setライセンス(ライセンス) アップグレード set旧バージョン(旧バージョン) アップグレード set新バージョン(新バージョン) アップグレード setアップグレード日付(アップグレード日付) |
取引先 | 取引先名称 住所 |
取引先名称 get取引先名称() 住所 get住所() 取引先 set取引先名称(取引先名称) 取引先 set住所(住所) |
社員 | 社員番号 氏名 |
社員番号 get社員番号() 氏名 get氏名() 社員 set社員番号(社員番号) 社員 set氏名(氏名) |
管理者 | ライセンス 社員 担当開始日付 担当終了日付 |
ライセンス getライセンス() 社員 get社員() 担当開始日付 get担当開始日付() 担当終了日付 get担当終了日付() ライセンス setライセンス(ライセンス) ライセンス set社員(社員) ライセンス set担当開始日付(担当開始日付) ライセンス set担当終了日付(担当終了日付) |
導入機器 | ライセンス 機器ID 導入日付 削除日付 |
ライセンス getライセンス() 機器ID get機器ID() 導入日付 get導入日付() 削除日付 get削除日付() 導入機器 setライセンス(ライセンス) 導入機器 set機器ID(機器ID) 導入機器 set導入日付(導入日付) 導入機器 set削除日付(削除日付) |
ライセンス群 | ライセンス getFirst() ライセンス getNext(ライセンス) 真偽値 isLast(ライセンス) ライセンス add(ライセンス) ライセンス remove(ライセンス) |
|
ソフトウェア群 | ソフトウェア getFirst() ソフトウェア getNext(ソフトウェア) 真偽値 isLast(ソフトウェア) ソフトウェア add(ソフトウェア) ソフトウェア remove(ソフトウェア) |
|
アップグレード履歴 | アップグレード getFirst() アップグレード getNext(アップグレード) 真偽値 isLast(アップグレード) アップグレード add(アップグレード) アップグレード remove(アップグレード) |
|
取引先群 | 取引先 getFirst() 取引先 getNext(取引先) 真偽値 isLast(取引先) 取引先 add(取引先) 取引先 remove(取引先) |
|
社員群 | 社員 getFirst() 社員 getNext(社員) 真偽値 isLast(社員) 社員 add(社員) 社員 remove(社員) |
|
管理者群 | 管理者 getFirst() 管理者 getNext(管理者) 真偽値 isLast(管理者) 管理者 add(管理者) 管理者 remove(管理者) |
|
導入機器群 | 導入機器 getFirst() 導入機器 getNext(導入機器) 真偽値 isLast(導入機器) 導入機器 add(導入機器) 導入機器 remove(導入機器) 真偽値 is導入済み(ライセンス) |
void ライセンス購入(ライセンス番号 購入ライセンス番号,ソフトウェア名 購入ソフトウェア名,取引先 購入ライセンス製造元名称,取引先 購入ライセンス販売元名称){ ライセンス 購入ライセンス=new ライセンス() ソフトウェア 購入ソフトウェア=new ソフトウェア() 取引先 購入ライセンス製造元=new 取引先() 取引先 購入ライセンス販売元=new 取引先() ライセンス群 保有ライセンス群=new ライセンス群() ソフトウェア群 保有ソフトウェア群=new ソフトウェア群() 取引先郡 過去の取引先群=new 取引先群() 購入ライセンス.setライセンス番号(購入ライセンス番号) 購入ソフトウェア.setソフトウェア名(購入ソフトウェア名) 購入ライセンス製造元.set取引先名称(購入ライセンス製造元名称) 購入ライセンス販売元.set取引先名称(購入ライセンス販売元名称) 購入ライセンス.setソフトウェア名(購入ソフトウェア) 購入ライセンス.set販売元(購入ライセンス販売元) 購入ソフトウェア.set製造元(購入ライセンス製造元) 保有ライセンス群.add(購入ライセンス) 保有ソフトウェア群.add(購入ソフトウェア) 過去の取引先群.add(購入ライセンス製造元) 過去の取引先群.add(購入ライセンス販売元) }
result ライセンス破棄(ライセンス番号 破棄ライセンス番号){ ライセンス 対象ライセンス ソフトウェア 対象ソフトウェア 取引先 対象取引先 管理者 対象管理者 アップグレード 対象アップグレード ライセンス群 保有ライセンス群=new ライセンス群() ソフトウェア群 保有ソフトウェア群=new ソフトウェア群() 導入機器群 現導入機器群=new 導入機器群() 取引先群 現取引先群=new 取引先群() アップグレード履歴 現アップグレード履歴=new アップグレード履歴() for(対象ライセンス=保有ライセンス群.getFirst();!保有ライセンス群.isLast(対象ライセンス);対象ライセンス=保有ライセンス群.getNext(対象ライセンス)){ if(対象ライセンス.getライセンス番号()==破棄ライセンス番号)&&(!現導入機器群.is導入済み(対象ライセンス)){ 保有ライセンス群.remove(対象ライセンス) break } else if(対象ライセンス.getライセンス番号()==破棄ライセンス番号)&&(現導入機器群.is導入済み(対象ライセンス)){ return Error } } for(対象ソフトウェア=保有ソフトウェア群.getFirst();!保有ソフトウェア群.isLast(対象ソフトウェア);対象ソフトウェア=保有ソフトウェア群.getNext(対象ソフトウェア)){ if(対象ソフトウェア.getソフトウェア名称()==対象ライセンス.getソフトウェア().getソフトウェア名称()){ 保有ソフトウェア群.remove(対象ソフトウェア) break } } for(対象取引先=現取引先群.getFirst();!現取引先群.isLast(対象取引先);対象取引先=現取引先群.getNext(対象取引先)){ if(対象取引先.get取引先名称()==対象ライセンス.get販売元().get取引先名称()||対象取引先.get取引先名称()==対象ソフトウェア.get製造元().get取引先名称()){ 現取引先群.remove(対象取引先) } } for(対象管理者=現管理者群.getFirst();!現管理者群.isLast(対象管理者);対象管理者=現管理者群.getNext(対象管理者)){ if(対象管理者.getライセンス().getライセンス番号()==対象ライセンス.getライセンス番号()){ 現管理者群.remove(対象管理者) //破棄するライセンスの管理者は複数いるかもしれないのでbreakはなし. } } for(対象アップグレード=現アップグレード履歴.getFirst();!現アップグレード履歴.isLast(対象アップグレード);対象アップグレード=現アップグレード履歴.getNext(対象アップグレード)){ if(対象アップグレード.getライセンス().getライセンス番号()==対象ライセンス.getライセンス番号()){ 現アップグレード履歴.remove(対象アップグレード) //破棄するライセンスのアップグレード履歴は複数あるかもしれないのでbreakはなし. } } return OK }
参考文献
Alfred V. Aho, John E. Hopcroft, Jeffrey D. Ullman "Data Structures and Algorithms" Addison-Wesley Publishing, 1983
(大野義夫,データ構造とアルゴリズム,培風館,1987)
Niklaus Wirth, "ALGORITHMS AND DATA STRUCTURES", Prentice-Hall, 1986
(浦昭二 國府方久史,アルゴリズムとデータ構造,近代科学社,1990)
近藤嘉雪,Cプログラマのためのアルゴリズムとデータ構造,ソフトバンク,1992
Roger Sessions, "Class Construction in C and C++", Prentice-Hall, 1992
(石川克己,C/C++クラスの構築法,トッパン,1993)