[研究報告の目次へ戻る]

総合開発環境 eclipse による java プログラム開発

医学工学総合研究部 工学学域
鈴木智博
stomo[atmark]yamanashi.ac.jp


はじめに

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++ で実装することを課している.

graph
重み付き有向グラフ

影付きの丸で示されているのはグラフの頂点であり,各頂点は重みと向きを持った辺で接続さ れてい る.
各頂点同士の接続状態を表現するために隣接行列または隣接リストが用いられるが,ここでは比較的単純なグラフに対して用いられる隣接リストを用いることに する.

以下ではオブジェクト指向技術の基本的な概念である分散協調型計算モデルとしてグラフ構造 を分析する.
分散協調型計算モデルとは「複数個の自立的機能を持つオブジェクトが互いにメッセージをやり取りしながら強調して問題解決する計算モデル」とされている[4]

まず,辺の構造を考える.辺は接続先の頂点と重みの情報を持つ.辺を表すクラスを Edge とする.

adjacent
Edge の構造

上図において,weight は辺の重みを表し,点線の矢印は接続先の頂点へのポインタを表す.

次に,頂点の構造を考える.頂点を表す Vertex クラスは名前 name と接続リストを持つ.接続リストは Edge を要素に持つリスト構造で表現する.

vertex
Vertex の構造

グラフそのものを表す Graph クラスは頂点の名前をキーとしたマップ構造で表される.

map
Graph の構造 (一部省略)

次に eclipse UMLプラグインを用いて Edge,Vertex,Graph クラスのクラス図を作成する.UMLプラグインにおけるクラス図の作成方法の説明は他の文献(たとえば[1])にゆずる.

近年,UML などのツールを用いたモデル駆動型プログラム開発がソフトウェア開発の分野で注目されている[5]
しかし,UML の中でも特にクラス図はソースプログラムと密接に関わりを持つので,「概念」を取り扱う道具としてこれを用いることには筆者は疑問がある.
実装,または実装レベルのプログラムを表現するために使用することが適当だと考える.

ClassDiagram
クラス図

上は作成したクラス図である.
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 文は追加する必要がある.

Adjacent.java
public class Edge {

    double weight;
    Vertex vertex;

    public Edge(Vertex v, double w) {
    }

}


Vertex.java
import java.util.*;

public class Vertex {

    String name;

    List adjlist;

    public Vertex(String n) {
    }

    public void AddAdjacent(Vertex v, double w) {
    }

}











// 頂点 v へ重み w の辺を接続する

Graph.java
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 で接続する

JUnit による単体テスト

ソフトウェア開発においてテストは欠かせない.構造化プログラミングにおいてはまず下位モジュールのプログラムを作成しそれをテストする.モジュー ル単体のテストを単体テスト(ユニットテスト)と言う.
複数のモジュールを関連付けて行うテストは結合テストと呼ばれる.
その他にも実際のプログラム利用者によって行われる運用テストなどがある.

モジュールをプログラミングした後に単体テストプログラムを作成するのではなく,始めに単体テストプログラムを作成し,それを満足するようにモ ジュールを作成するプログラミングスタイルをテスト駆動型(テストファースト)開発と言う[3]
つまり,モジュールが満たすべき仕様をテストコードとして記述しておき,それを満足するプログラムを作成すれば自動的に仕様も満足するということである.
正しく動作しているプログラムをより効率的に修正したり,他人に読みやすく変更を加えることをリファクタリングと言う.
はじめにテストプログラムを作成しておくことは,リファクタリングによるデグレードを防ぐためにも有効である.
テスト駆動型開発をするためには,分析や設計といったプログラム開発における上流工程[4]の作業により重点を置かねばならないことは言うまでもない.

eclipse でテストプログラムを作成するには,パッケージエクスプローラでテストしたいクラスを選択し,[新規][JUint テスト・ケース] を 選択する.
テストするメソッドを選択すると,クラス名+test という名前のテストクラスが自動的に生成される.
このテストクラスには,選択したメソッドのテストメソッドのテンプレートが生成される.

各クラスのテストプログラムを以下に示す.

AdjacentTest.java
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 フィールドのテスト

VertexTest.java
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 か?

GraphTest.java
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 ビューが開き,実行したテストメソッドの数,成功,失敗したテストの数などが表示される.
緑のインジゲータはすべてのテストが成功したことを表す.

JUnitVirw


上のテストプログラムは何れも結果が正しいことを想定しているが,実際のアプリケーションプログラム開発においては,不正な入力に対して正しくエラー処理 や割り込みを発生させることをテストすることが重要であろう.

以下は最終的な java プログラムである.

Edge.java
public class Edge {

    double weight;
    Vertex vertex;

    public Edge(Vertex v, double w) {            
        vertex = v;
        weight = w;
    }

}





// コンストラクタ

Vertex.java
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 のインスタンスを追加

Graph.java
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();

g.AddEdge( "A", "B", 2.0 );
g.AddEdge( "C", "A", 4.0 );
g.AddEdge( "A", "D", 1.0 );
g.AddEdge( "B", "D", 3.0 );
g.AddEdge( "B", "E", 10.0 );
g.AddEdge( "C", "F", 5.0 );
g.AddEdge( "D", "C", 2.0 );
g.AddEdge( "D", "E", 2.0 );
g.AddEdge( "D", "F", 8.0 );
g.AddEdge( "D", "G", 4.0 );
g.AddEdge( "E", "G", 6.0 );
g.AddEdge( "G", "F", 1.0 );


まとめ

総合開発環境 eclipse を使用した java プログラム開発の手順の例題を報告した.
最近注目されているプログラム開発手法と eclipse が提供する開発ツールを組み合わせたものであったが,取り扱った題材と報告した開発手順がよく適合したものであることの保証はない.
本報告が,プログラム開発を行う読者の参考になれば幸いである.

参考文献

  1. 宮本信二,Eclipse 3 完全攻略,ソフトバンクパブリッシング,(2004),ISBN4-7973-2778-2
  2. 児玉公信,UMLモデリングの本質,日経BP社,(2004),ISBN4-8222-2118-0
  3. 今野睦監修,サイバービーンズ株式会社著,JUnit によるテストファースト開発入門,ソフトバンクパブリッシング,(2004),ISBN4-7973-2572-0
  4. 中所武司,ソフトウェア工学,朝倉書店,(1997),ISBN4-254-12707-3
  5. 情報処理学会,情報処理学会論文誌,Vol. 45,No. 1,(2004)

[研究報告の目次へ戻る]