eclipse は CPL
で配布され,無償で利用することができるオープンソースの総合開発環境(IDE)である.
これは元々,IBM で開発されていた商用のアプリケーションプログラム開発ツールだったが,2001年に IBM から非営利団体 eclipse.org
にソースコードが寄贈され,現在も開発が進められている.
プラグインによって柔軟に機能拡張が可能なこと,CPL
はプログラムの商用利用が可能なライセンスであること,オープンソースであるから様々なベンダが参入したこと,などが eclipse
が短期間のうちに IDE のデファクトスタンダードとなった理由だと考えれられる.
本報告では,eclipse 本体と開発をサポートするのに有用なプラグインを利用した java プログラム開発の一つの事例を示す.
使用する機能は,UML 図を書くための eclipse UML プラグイン,単体テストを用意に行うことができる JUnit (標準プラグイン) の2つである.
なお,eclipse 本体,java プログラミング環境,eclipse UML プラグインのインストール方法などの説明は他の文献(例えば [1])にゆずる.また,eclipse
の使用方法等についても同様である.
eclipse 本体および UML プラグインは本学総合情報処理センター実習室の Windows 端末で利用可能である.
本報告では java プログラムの題材として,最短経路探索などのグラフ理論のアルゴリズムのための有向グラフの実装を取り上げる.
eclipse による java プログラム開発の例題として,下図のようなグラフ構造の実装を考える.
これは,工学部コンピュータ・メディア工学科1年生後期科目アルゴリズムとデータ構造I
の課題から引用させて頂いた.
同科目では,深さ優先探索,幅優先探索による木の生成,最短経路問題などのアルゴリズムを学習するためにこのようなグラフ構造をプログラミング言語
c++ で実装することを課している.
影付きの丸で示されているのはグラフの頂点であり,各頂点は重みと向きを持った辺で接続さ
れてい
る.
各頂点同士の接続状態を表現するために隣接行列または隣接リストが用いられるが,ここでは比較的単純なグラフに対して用いられる隣接リストを用いることに
する.
以下ではオブジェクト指向技術の基本的な概念である分散協調型計算モデルとしてグラフ構造
を分析する.
分散協調型計算モデルとは「複数個の自立的機能を持つオブジェクトが互いにメッセージをやり取りしながら強調して問題解決する計算モデル」とされている[4].
まず,辺の構造を考える.辺は接続先の頂点と重みの情報を持つ.辺を表すクラスを
Edge
とする.
Edge の構造
|
上図において,weight
は辺の重みを表し,点線の矢印は接続先の頂点へのポインタを表す.
次に,頂点の構造を考える.頂点を表す Vertex クラスは名前 name
と接続リストを持つ.接続リストは Edge
を要素に持つリスト構造で表現する.
Vertex の構造
|
グラフそのものを表す Graph
クラスは頂点の名前をキーとしたマップ構造で表される.
Graph の構造 (一部省略)
|
次に eclipse UMLプラグインを用いて Edge,Vertex,Graph
クラスのクラス図を作成する.UMLプラグインにおけるクラス図の作成方法の説明は他の文献(たとえば[1])にゆずる.
近年,UML
などのツールを用いたモデル駆動型プログラム開発がソフトウェア開発の分野で注目されている[5].
しかし,UML
の中でも特にクラス図はソースプログラムと密接に関わりを持つので,「概念」を取り扱う道具としてこれを用いることには筆者は疑問がある.
実装,または実装レベルのプログラムを表現するために使用することが適当だと考える.
クラス図
|
上は作成したクラス図である.
Edge クラスは double 型の weight と,Vertex クラスの vertex
をメンバーに持つ.これは,重みと接続先頂点を表す.
Vertex クラスは String 型の name と,Edge クラスのリスト adjlist
をメンバーに持つ.これは頂点名と隣接リストを表す.
Graph クラスは頂点名をキー,Vertex クラスを値としたマップ vmap をメンバーに持つ.
Graph クラスのコンストラクタのみを引数なしとし,Vertex
クラスでは頂点名を,Edge
クラスでは接続先頂点名と重みをそれぞれ引数とするコンストラクタを作成する.
Graph クラスは,辺を生成するメソッド AddEdge を持つ.AddEdge
の引数は「接続元頂点名」,「接続先頂点名」,「辺の重み」の3つとした.
また,頂点の数と辺の数を表すメンバー n_vertex,n_edge を追加している.
Vertex クラスには Graph クラスの AddEdge メソッドから呼び出される AddAdjacent
メソッドを作成する.このメソッドの引数は「接続先頂点」と「辺の重み」である.
プログラムの動作として,特にグラフの作成段階で,頂点と辺が必ずしも同時に作成される訳
ではない(インスタンスのライフサイクルが異なる)ので,上のクラス図では各クラス同士の
関係は「コンポジッション」でなく「集約」として表現した[2].
eclipse UML
プラグインでは,クラス図を描くことによって,記述されたメンバーとコンストラクタ,メソッドなどの宣言のみの以下のような java
ソースコードが自動的に生成される.
クラス図にメンバーやメソッドを追加するとソースコードに反映され,逆にソースコードへの変更がクラス図に反映される.
以下では,eclipse が生成するコメントを削除し,それ以外のコメントは追加したものである.
メンバーのアクセス修飾子は「なし」としている.
List や Map などの java API のための import 文は追加する必要がある.
public
class Edge { double weight; Vertex vertex; public Edge(Vertex v, double w) { } } |
import
java.util.*; public class Vertex { String name; List adjlist; public Vertex(String n) { } public void AddAdjacent(Vertex v, double w) { } } |
// 頂点 v へ重み w の辺を接続する |
import
java.util.*; public class Graph { int n_vertex; int n_edge; Map vmapMap; public Graph() { } public void AddEdge(String v1, String v2, double w) { } } |
// 頂点 v1 に,頂点 v2 を重み w で接続する |
ソフトウェア開発においてテストは欠かせない.構造化プログラミングにおいてはまず下位モジュールのプログラムを作成しそれをテストする.モジュー
ル単体のテストを単体テスト(ユニットテスト)と言う.
複数のモジュールを関連付けて行うテストは結合テストと呼ばれる.
その他にも実際のプログラム利用者によって行われる運用テストなどがある.
モジュールをプログラミングした後に単体テストプログラムを作成するのではなく,始めに単体テストプログラムを作成し,それを満足するようにモ
ジュールを作成するプログラミングスタイルをテスト駆動型(テストファースト)開発と言う[3].
つまり,モジュールが満たすべき仕様をテストコードとして記述しておき,それを満足するプログラムを作成すれば自動的に仕様も満足するということである.
正しく動作しているプログラムをより効率的に修正したり,他人に読みやすく変更を加えることをリファクタリングと言う.
はじめにテストプログラムを作成しておくことは,リファクタリングによるデグレードを防ぐためにも有効である.
テスト駆動型開発をするためには,分析や設計といったプログラム開発における上流工程[4]の作業により重点を置かねばならないことは言うまでもない.
eclipse でテストプログラムを作成するには,パッケージエクスプローラでテストしたいクラスを選択し,[新規]→[JUint
テスト・ケース] を
選択する.
テストするメソッドを選択すると,クラス名+test という名前のテストクラスが自動的に生成される.
このテストクラスには,選択したメソッドのテストメソッドのテンプレートが生成される.
各クラスのテストプログラムを以下に示す.
import
junit.framework.TestCase; public class EdgeTest extends TestCase { public void testEdge() { String n = "A"; double w = 2.0; Vertex v = new Vertex(n); Edge a = new Edge(v,w); assertEquals(w, a.weight, 0.0); assertEquals(v, a.vertex); assertEquals(n, a.vertex.name); } } |
// インスタンス生成 // weight フィールドのテスト // vertex フィールドのテスト |
import
java.util.*; import junit.framework.TestCase; public class VertexTest extends TestCase { public void testVertex() { String n = "A"; Vertex v = new Vertex(n); assertEquals(n, v.name); assertEquals(true,v.adjlist.isEmpty()); } public void testAddAdjacent() { String n1 = "A"; String n2 = "B"; String n3 = "C"; Vertex v1 = new Vertex(n1); Vertex v2 = new Vertex(n2); Vertex v3 = new Vertex(n3); assertEquals(n1, v1.name); assertEquals(n2, v2.name); assertEquals(n3, v3.name); double w12 = 2.0; v1.AddAdjacent(v2,w12); double w13 = 3.0; v1.AddAdjacent(v3,w13); Iterator it = v1.adjlist.iterator(); Edge ad = (Edge)it.next(); assertEquals(ad.vertex,v2); assertEquals(ad.weight,w12,0.0); ad = (Edge)it.next(); assertEquals(ad.vertex,v3); assertEquals(ad.weight,w13,0.0); } } |
// インスタンス生成 // name フィールドのテスト // adjlist は空のリストか? // name フィールドのテスト // v1 に v2 を重み w12 で接続 // v1 に v3 を重み w13 で接続 // リストの先頭が v2 か? // v2 との重みは w12 か? // 次の要素は v3 か? // v3 との重みは w13 か? |
import
java.util.*; import junit.framework.TestCase; public class GraphTest extends TestCase { public void testGraph() { Graph g = new Graph(); assertEquals(0, g.n_vertex); assertEquals(0, g.n_edge); assertEquals(true,g.vmapMap.isEmpty()); } public void testAddEdge() { Graph g = new Graph(); String n1 = "A", n2 = "B", n3 = "C"; double w12 = 2.0, w13 = 1.0, w23 = 3.0; g.AddEdge(n1,n2,w12); assertEquals(1,g.n_edge); assertEquals(2,g.n_vertex); g.AddEdge(n1,n3,w13); assertEquals(2,g.n_edge); assertEquals(3,g.n_vertex); g.AddEdge(n2,n3,w23); assertEquals(3,g.n_edge); assertEquals(3,g.n_vertex); assertEquals(false,g.vmapMap.isEmpty()); assertEquals(3,g.vmapMap.size()); assertEquals(true,g.vmapMap.containsKey(n1)); Vertex v = (Vertex)g.vmapMap.get(n1); assertEquals(n1,v.name); assertEquals(2,v.adjlist.size()); Iterator it = v.adjlist.iterator(); Edge ad = (Edge)it.next(); assertEquals(w12,ad.weight,0); ad = (Edge)it.next(); assertEquals(w13,ad.weight,0); assertEquals(true,g.vmapMap.containsKey(n2)); v = (Vertex)g.vmapMap.get(n2); assertEquals(n2,v.name); assertEquals(1,v.adjlist.size()); it = v.adjlist.iterator(); ad = (Edge)it.next(); assertEquals(w23,ad.weight,0); assertEquals(true,g.vmapMap.containsKey(n3)); v = (Vertex)g.vmapMap.get(n3); assertEquals(n3,v.name); assertEquals(0,v.adjlist.size()); } } |
// インスタンス生成 // n_vertex フィールドのチェック // n_edge フィールイドのチェック // vmapMap は空のマップか? // インスタンス生成 // "A" -> 2.0 -> "B" // カウンタのテスト // "A" -> 1.0 -> "C" // カウンタのテスト // "B" -> 3.0 -> "C" // カウンタのテスト // マップのテスト // key "A" に対応する value のテスト // key "B" に対応する value のテスト // key "C" に対応する value のテスト |
assertEquals メソッドは第一引数に期待する値,第二引数に実際の値を記述する.
ただし,第一,第二引数が float,double
の場合,通常二つの数が厳密に一致することはないので,第三引数として許容誤差を与えなければならない.
上のテストを満足するような java プログラム Edge.java,Vertex.java,Graph.java を作成する.
その後,実際にテストを実行するには,パッケージエクスプローラでテストクラスを選択し,[実行]→[JUint
テスト] を
選択する.
テストを実行すると JUnit ビューが開き,実行したテストメソッドの数,成功,失敗したテストの数などが表示される.
緑のインジゲータはすべてのテストが成功したことを表す.
上のテストプログラムは何れも結果が正しいことを想定しているが,実際のアプリケーションプログラム開発においては,不正な入力に対して正しくエラー処理
や割り込みを発生させることをテストすることが重要であろう.
以下は最終的な java プログラムである.
public
class Edge { double weight; Vertex vertex; public Edge(Vertex v, double w) { vertex = v; weight = w; } } |
// コンストラクタ |
import
java.util.*; public class Vertex { String name; List adjlist; public Vertex(String n) { name = n; adjlist = new ArrayList(); } public void AddAdjacent(Vertex v, double w) { adjlist.add(new Edge(v,w)); } } |
// コンストラクタ // インスタンス生成 // 頂点 v へ重み w の辺を接続する // リストに Edge のインスタンスを追加 |
import
java.util.*; public class Graph { int n_vertex; int n_edge; Map vmapMap; public Graph() { n_vertex = 0; n_edge = 0; vmapMap = new HashMap(); } public void AddEdge(String n1, String n2, double w) { if (vmapMap.containsKey(n1) == false) { vmapMap.put(n1, new Vertex(n1)); n_vertex = n_vertex + 1; } if (vmapMap.containsKey(n2) == false) { vmapMap.put(n2, new Vertex(n2)); n_vertex = n_vertex + 1; } v1 = (Vertex)vmapMap.get(n1); v2 = (Vertex)vmapMap.get(n2); v1.AddAdjacent(v2,w); n_edge = n_edge + 1; } } |
// コンストラクタ // カウンタの初期化 // インスタンス生成 // n1 に n2 を重み w で接続する // マップに n1 のエントリがなければ追加 // マップに n2 のエントリがなければ追加 // n1 の adjlist に頂点 n2 を重み w で追加 |
始めに示した重み付き有向グラフを作成するための操作は以下となる.
Graph g = new Graph(); |
総合開発環境 eclipse を使用した java
プログラム開発の手順の例題を報告した.
最近注目されているプログラム開発手法と eclipse
が提供する開発ツールを組み合わせたものであったが,取り扱った題材と報告した開発手順がよく適合したものであることの保証はない.
本報告が,プログラム開発を行う読者の参考になれば幸いである.