[ Java Project Exampleページへ戻る ]

イテレーション No.1

時刻プログラム開発のイテレーションNo.1である。


コンテンツ


目標を定める

最初のイテレーションなので、時刻プログラムのもっとも本質かつ最低限の機能だけを開発する。そこで、以下の機能を実現する。

なんとたったこれだけ。こんなの開発する意味ないって思えるくらい単純なもの。この僅かな仕様を見ただけで、設計なんて不要でコードまで頭に浮かぶかもしれない。けど、ソフトウェア開発プロジェクトには、仕様以外に考えたり決めておかねばならないことがいろいろある。今ぱっと頭に浮かぶのは、プロジェクト全体から見ればほんの一部分だけ。次節から、その考えねばならぬ事柄を一つ一つ見ていく。


設計

設計といっても何をするのか。必要最低限の設計をするとしたら、何を決めればよいのだろうか。考えても堂堂巡りになりそうなので、まずは気の向くまま設計に手をつけてみよう。そこで問題点が出れば、そのとき考えればいいということで進めていく。

設計のインフラ

設計ではどんなインフラを使用するのか決める必要がある。構想においてUMLを使用することは決めたが、ツールは決めていなかった。このイテレーションでは非常にシンプルな設計モデルしか作成しないので、ツール選定をするのには不十分であろう。そこで、もう少しモデルが複雑になってからツールを導入することにして、今回は手書き(Web化する際はWindows OS付属のペイントで描いているが)でいくことにした。

パッケージを決める

いきなりだがパッケージを決める。なぜか?あまり前向きではない理由に、Javaではコードの最初にpackage文を記述するから、という点がある。前向きな理由としては、今後パッケージが再利用やJAR化、テスト、などといった作業をするときに扱う単位となるから、というのもある。ま、それは後の話だけど。

問題領域のパッケージを決める

時刻プログラムが扱う問題領域は時刻なので、まずこれをパッケージとして切り出した。名前はとりあえず"clock"としておく。

パッケージ図Ver.1.1
パッケージ図Ver.1.1

パッケージの依存関係を決める(1)

clockパッケージは、Java 2 Standard Edition(J2SE), ver 1.4で動作する。追加パッケージは不要とする。J2SEの中のどのパッケージに依存するかは、今後開発が進んで分かり次第記述することにする。

パッケージ図Ver.1.2
パッケージ図Ver.1.2

パッケージの依存関係を決める(2)

clockパッケージは、利用者に時刻に関するサービスを提供するため、利用者から依存されている。これをパッケージ図に反映する。

パッケージ図Ver.1.3
パッケージ図Ver.1.3

クラスを決める

今回提供するクラスは時、分、秒の保持と取り出しである。まずはクラス図を書いてみた。

Clock
-hour: int
-minute: int
-second: int
+getHour(): int
+setHour(hour:int):void
+getMinute(): int
+setMinute(minute:int):void
+getSecond(): int
+setSecond(second: int):void
クラス図 Ver.1.1

classかinterfaceか

ここで記述したClockクラスであるが、今後を考えるとinterfaceを設けておいた方が望ましい気もする。しかし、Peter Coadの戦略に基づけば、繰り返し表れる共通したシグネチャをinterfaceとして括り出すので、まだ最初の段階ではinterfaceとして有用かは判断がつかない。また、今後clockパッケージが複雑になるにつれ、ファサード、ファクトリ、オブザーバといった設計が導入されるだろう。そのときに今考えたinterfaceが変わらずに残りつづけるとは思えない。
であれば、まずはclassとして設計を進めていく。

クラス図

クラス図Ver.1.2(詳細省略)
クラス図Ver.1.2

シーケンス図

ここで、シーケンス図を作成すると、実装するプログラムの動作イメージが見えてくるが、今回のイテレーションではシーケンス図に記すほど複雑ではないので省略。

実装に入る前に・・・

今回のClockクラスでは、時、分、秒という概念をJavaにおけるintとして設計した。果たしてこれでよいのだろうか?

検討のために、時、分、秒とintの値の範囲を比べてみよう。

項目 最小値 最大値
0 23
0 59
0 59
int -2147483648 2147483647

時、分、秒というデータにとっては不正な値であってもint型のデータとしては取り得る値である。ということは、利用者が誤って範囲外の値を設定してしまうことが十分想定される。

そこで、Clockクラスは常に適正な値を保持するように設計を行う。このような場合に有効な設計手法として、「契約による設計(Design by Contract)」がある。

 「契約による設計(Design by Contract)」は、ルーチンの呼び出し側と実装側の間に契約条件を指定し、これを守る責任を明示的に割り当てる設計アプローチ。契約条件は、不変条件と事前条件、事後条件の3つから成る。契約が守られていなければ、それはシステムの欠陥(バグ)となる。

メソッド 事前条件 事後条件 不変条件
setHour 引数が0-23である 引数の値でhourフィールドを更新 -
setMinute 引数が0-59である 引数の値でminuteフィールドを更新 -
setSecond 引数が0-59である 引数の値でsecondフィールドを更新 -

事前条件が守られていなければ、例外を発生させる。契約が守られていなければプログラムを続行する意味がないため、通常Runtime例外またはErrorを使用する。今回は、事前条件が守られていない場合IllegalArgumentExceptionを使うことにする。事後条件の判定は省略する。

例外を使うとき、独自の例外を定義するか出来合いの例外を使うか悩むことがあるけど、どうなんだろうか。今回の場合独自に例外を定義するなら、HourOutOfRangeException、MinuteOutOfRangeException、、、となるだろうけど、これはちょっとやり過ぎな気がする。やっぱりIllegalArgumentExceptionが適しているようだ。


実装

コーディング標準を定める

今回のプロジェクトで使用するコーディング標準をまず決めよう。プロジェクト毎に一から標準を決めてもいいけど、世の中にあるものは使った方がよい。それに、標準はバグを減らし、後で変更しやすいようにするためのノウハウを表記したものだから、なるべく多くの知恵がつまった標準を使うに如くはなし。

ということで、下記のコーディング標準を使用する。

ディレクトリ構造を決める

パッケージを指定しない小さなプログラムならディレクトリを分けなくてもよいが、長期間メンテナンスし、また再利用をすることも考えると最初にちゃんとディレクトリ構造を決めないといけない。

<Project Root>
      |
      +---- classes
      |
      +---- doc
      |
      +---- src
      |
      +---- README.txt

とりあえずこんなところから始めてみる。<Project Root>は任意の場所におけるようにしたい。

コーディング

今回記述するファイルは以下のとおり。

プログラミング環境

コーディング・コンパイル・デバッグ作業を行う環境を用意する必要がある。何でもGUIで操作できる統合開発環境がはやっているけど、これを使ったから生産性や品質があがったと本当に言えるだろうか。UNIX上の開発環境ではエディタ+コマンドライン環境が主流だが、Windows向けのプログラムより生産性や品質が劣っていることはない。

当面のイテレーションでは、エディタ+コマンドライン環境を使う。とりあえずエディタは最近馴染んできたEmacs(Windows上ならMeadow)を使い、コンパイラはJava 2 SDK 1.4を使う。デバッグは必要が生じたら考えることにしよう。

ユニットテスト

テストファーストするなら、それぞれのクラスのテストケースも書かねばならない。しかし今回のイテレーションでは2つしかクラスを作らないし、ClockClientはほとんどテスト用ドライバみたいなものだ。であるから、今回のイテレーションでは「作って動かして確認する」。ただし、今後はテストファーストするべきなので、近いうちに取り入れることとする。

ロギング

ロギングは是非欲しいところだが、今回は複雑なシーケンスがないので省略する。今後のイテレーションの中で、ロギングを取り込むことにする。ロギングの設計はけっこう大変であるから、それだけで1つのイテレーションを回してもよいと思う。(ネットワーク分散した各プログラムのログをどこに集めるか、その場合タイムスタンプをどこで打つか、ノード同士で時刻同期が必要になるだろう、既存のロギングAPIを使うなら、どれを選定するか、などなど)

ソースファイルの記述(Clock.java)

ではさっそくClock.javaの記述を行っていく。ソースコードはソースファイルに記述する。Javaではpublicなclassは1つのファイルに書くことになっている。以下にソースファイルに記述することを挙げた。

  1. ファイル先頭コメント
  2. パッケージ文
  3. import文
  4. クラスコメント
  5. クラス宣言
  6. フィールドの定義
  7. インスタンス生成メソッド(コンストラクタ)の定義
  8. サービスメソッドの定義
ファイル先頭コメント
プロジェクト名と著作権を記述した。これは他のファイルと共通させる。
/*
 * Project Clock
 * Copyright 2002 Toru TAKAHASHI. All rights reserved.
 */
パッケージ文
Javaではパッケージ名が重ならないように、開発組織(個人)のインターネット・ドメイン名を逆に並べた文字列をプレフィックスとして付ける。組織ならドメイン名を持っていることが多いが、個人の場合はメールアドレスを使用する方法がある。(例:torutk@02.246.ne.jpだったら、jp.ne.246.02.torutkをパッケージ名に付ける)
また、インターネット協会Java研究部会のPackage BOFにおいて、パッケージ名登録サービスが提供されているので、これを利用してドメイン名を持つ方法もある。今回は、Package BOFで取得した"jp.gr.java_conf.torutk"に、時刻プログラム開発プロジェクトの略名"pje"を付加し、これを時刻プロジェクトのパッケージ階層とした。
package jp.gr.java_conf.torutk.jpe.clock;
import文
今回はjava.langパッケージ以外のクラスを使用していないのでimport文はなし
クラスコメント
クラスの役割、クラス不変条件、著者、バージョンを記述した。あとでJavadoc APIドキュメントを生成する際にきれいに見えるよういくつかのHTMLタグを入れている。すべてのHTMLタグが使えるわけではない。バージョンは、最初は1.1から始め、大きな区切りの都度コンマ1ずつ上げていく。
/**
 * <code>Clock</code>クラスは時分秒を保持する。
 * <p>
 * 時分秒を設定し、取り出すことができる。
 * <p>
 * 不変条件<br>
 * 時:0-23<br>
 * 分:0-59<br>
 * 秒:0-59<br>
 * 
 * @author <a href="mailto:torutk@alles.or.jp">Toru TAKAHASHI</a>
 * @version 1.1
 */
クラス宣言
clientパッケージから利用されるので、publicとした。明示的なクラスの継承、interfaceの実装はなし。
public class Clock {
フィールドの定義
カプセル化のためprivateとする。また、ドキュメント化コメントを記述した。その際不変条件として変数の有効範囲を記述した。変数名は時、分、秒の英語表記でhour、minute、secondと命名した。フィールドとローカル変数を区別する記号は使用しない。同じクラスの中でもフィールドを直接参照できるのはアクセッサ・メソッドだけとする。
    /** 時を保持する。有効範囲は 0-23 */
    private int hour;
    /** 分を保持する。有効範囲は 0-59 */
    private int minute;
    /** 秒を保持する。有効範囲は 0-59 */
    private int second;
インスタンス生成メソッド(コンストラクタ)
staticなファクトリメソッドの提供を考慮したが、今回の仕様では具体的なメリットが見出せなかったので、publicなコンストラクタを提供した。コンストラクタは引数なしと時分秒を引数に指定する2種類を用意した。引数なしのコンストラクタは、明示的に0時0分0秒で初期化するためコンストラクタ内部で引数ありのコンストラクタを呼んでいる。引数は事前条件として有効範囲内の値であることを要求し、事前条件が満たされていない場合は例外をスローする。
    /**
     * <code>Clock</code>オブジェクトを生成する。
     * <p>
     * 0時0分0秒に初期化される。
     */
    public Clock() {
        this(0, 0, 0);
    }

    /**
     * <code>Clock</code>オブジェクトを生成する。
     * <p>
     * 引数で指定した時分秒に初期化される。
     * @param anHour 時:0-23
     * @param aMinute 分:0-59
     * @param aSecond 秒:0-59
     * @throws IllegalArgumentException 引数が有効範囲にないとき
     */
    public Clock(int anHour, int aMinute, int aSecond) {
        if (anHour < 0 || 23 < anHour) {
            throw new IllegalArgumentException("out of range: hour");
        }
        if (aMinute < 0 || 59 < aMinute ) {
            throw new IllegalArgumentException("out of range: minute");
        }
        if (aSecond < 0 || 59 < aSecond) {
            throw new IllegalArgumentException("out of range: second");
        }

        hour = anHour;
        minute = aMinute;
        second = aSecond;
    }
サービスメソッド
時分秒のアクセッサメソッドを定義した。引数は事前条件として有効範囲内の値であることを要求し、満たされない場合は例外をスローする。
    /**
     * 時の値を返却する。
     * @return 時:0-23
     */
    public int getHour() {
        return hour;
    }

    /**
     * 時の値を更新する。
     * @param anHour 時:0-23
     * @throws IllegalArgumentException 引数が時の有効範囲にないとき
     */
    public void setHour(final int anHour) {
        if (anHour < 0 || 23 < anHour) {
            throw new IllegalArgumentException();
        }
        hour = anHour;
    }
※分、秒のサービスメソッドのコードは省略

Javaソースファイルは、パッケージ名と対応したディレクトリ構造に置く。そこで、Clock.javaは次の場所に置かれる。

<Project Root>
    :
    +---src
    :   +---jp
             +---gr
                  +---java_conf
                            +---torutk
                                    +---jpe
                                         +---clock
                                                +---Clock.java

ソースファイルの記述(ClockClient.java)

Clockクラスを利用してみるクラスで、イテレーションNo.1では動けばよい程度のものである。今後のイテレーションにおいて拡張する可能性は低い。そこで、手抜きをして作成する。

package jp.gr.java_conf.torutk.jpe.client;

import jp.gr.java_conf.torutk.jpe.clock.Clock;

public class ClockClient{
    
    public static void main(String[] args) {
        testDefaultConstractor();
        testHmsConstractor();
        testSetGet();
    }

    static void printClock(Clock aClock) {
        System.out.println("Clock = " + aClock.getHour() + ":" +
                   aClock.getMinute() + ":" + aClock.getSecond()
        );
    }

    static void testDefaultConstractor() {
        Clock clock = new Clock();
        printClock(clock);
    }

    static void testHmsConstractor() {
        Clock clock = new Clock(12, 34, 56);
        printClock(clock);
    }

    static void testSetGet() {
        Clock clock = new Clock();
        clock.setHour(21);
        clock.setMinute(9);
        clock.setSecond(5);
        printClock(clock);
    }
} // ClockClient

パッケージは、Clock.javaとは別の場所とする。

<Project Root>
    :
    +---src
    :   +---jp
            +---gr
                +---java_conf
                        +---torutk
                               +---jpe
                                   +---clock
                                   |     +---Clock.java
                                   +---client
                                         +---ClockClient.java

コンパイル

コンパイルを行う。今回は、コマンドラインでコンパイルを行う。コンパイルの条件としては、ソースファイルとは別ディレクトリにクラスファイルを出力する。コンパイルを実行する場所は<Project Root>ディレクトリとする。

E:\ClockProject>javac -d .\classes -sourcepath .\src 
src\jp\gr\java_conf\torutk\jpe\client\ClockClient.java
E:\ClockProject>
-dオプション
コンパイルによって生成されるクラスファイルをどのディレクトリに書き出すかを指定する。省略するとソースファイルと同じディレクトリになってしまう。ここでは先に決定したプロジェクトのディレクトリ構造に従うために.\classesと指定している。絶対パスで指定してもよいが、<Project Root>のディレクトリが変わる度に変更しなくてはならないし、違うマシンで開発するときは絶対パスを同じにできないこともある。
-sourcepathオプション
ソースファイルが置かれるディレクトリ階層の基点パスを指定する。コンパイルさせるクラスが他のクラスに依存(コード中で利用)している場合、この中からソースファイルを見つけて一緒にコンパイルしてくれる。分割コンパイル(1つのプログラムが複数のソースファイルで構成される)のときはこのオプションを指定すると便利。C/C++だとMakefileに自分で依存関係を書かねばならない。

ClockClientクラスはClockクラスに依存しているため、ClockClientソースファイルをコンパイルすれば、Clockクラスもコンパイルされる。コンパイル結果は<Project Root>\classes以下に生成される。上記のコンパイルオプションを指定した場合、下図のようにクラスファイルが生成されるはずだ。

<Project Root>
    :
    +---classes
    |      +---jp
    |          +---gr
    |              +---java_conf
    |                     +---torutk
    |                           +---jpe
    +---src                         +---clock
    :   +---jp                      |     +---Clock.class
    |        +---gr                 +---client
    |            +---java_conf            +---ClockClient.class
                        +---torutk
                               +---jpe
                                   +---clock
                                   |     +---Clock.java
                                   +---client
                                         +---ClockClient.java

今後の改善点

毎回コマンドを打たなくてはならない、毎回全てのクラスがコンパイルされてしまう、リフレクション等で動的にロードされるクラスはコンパイルされない、などの問題がある。これは今後のイテレーションにおいて解決していく。

実行

コマンドラインから起動する。

E:\ClockProject>java -classpath .\classes 
 jp.gr.java_conf.torutk.jpe.client.ClockClient
Clock = 0:0:0
Clock = 12:34:56
Clock = 21:9:5

E:\ClockProject>
-classpath
時刻プログラムのクラスファイルが格納されるディレクトリの基点である<Project Root>\classesを相対パス指定している。コマンドを実行する際はカレントディレクトリを<Project Root>に移動しておく。

今後の課題


試験

試験の種類にはいくつかある。ここでは開発の段階順に試験の種類を挙げてみた。試験のやり方にはいろいろな考え方があるので、ここで挙げたものは一例にすぎない。

また、試験とは別にレビューを行う。試験だけで全てのバグは見つけられないので、レビューと組み合わせてなるべく多くのバグを見つけて修正するとよい。

が、今回は試験は省略し、ClockClientが動けばよいものとする(とはいえそこには僅かだが試験の要素が含まれている)。その代わり、レビューをしてみた(独りレビューだが・・・)。

コードレビュー

試験の前に、今回作成したClock.javaをコードレビューして問題がないか検査を行ってみる。といっても書いた本人が見直したら問題があっても気付かない。第三者がすべきところである。また、レビューの際には問題点を指摘するにとどめ、解決策を検討しないことが重要。

レビュー指摘事項

  1. アクセッサ以外の場所でフィールドを直接変更している(コンストラクタの中)
    コンストラクタ中でのフィールドを直接参照して値を設定している。フィールドはアクセッサを必ず使用して読み書きする規約に反している。なお、この問題に対処する際の注意点を挙げておく。コンストラクタ中でアクセッサを使用する場合、アクセッサメソッドをfinal宣言しておかないと、アクセッサがサブクラスでオーバーライドされてしまったときに意図しない挙動が生じる。
  2. 即値を使用している(コンストラクタ、およびSetterメソッドの中)
    引数に指定された値が事前条件を満たしているか判定するロジックにおいて、複数箇所で値の範囲を即値で記述している。値が変更になった場合修正漏れが生じる危険性がある。
  3. 同じコードが繰り返し現れている(コンストラクタ、およびSetterメソッドの中)
    引数に指定された値が事前条件を満たしているか判定するロジックが、繰り返し登場している。このような場合、繰り返し表れる部分をメソッドとして独立されることが一般的である。

レビュー指摘事項の対処

  1. 規約違反だが、今回のイテレーションではそのままとする。設計上コンストラクタの使用は暫定的なものであり、今後のイテレーションの中で変わることが想定されるためである。
  2. 次のイテレーションで対処する。
  3. 次のイテレーションで対処する。

今後の課題


ドキュメント

ドキュメントとして必要なのは下記であろう。

READMEファイル

今回のイテレーションでは、ユーザーズ・マニュアルとプログラマーズ・マニュアルの内容をごく簡単に記したものを、READEファイルに記述することにした。

APIドキュメント

ClockパッケージのAPIドキュメントを作成する。

E:\ClockProject>javadoc -sourcepath src -d doc -author
 -version jp.gr.java_conf.torutk.jpe.clock
パッケージ jp.gr.java_conf.torutk.jpe.clock のソースファイルを
読み込んでいます...
Javadoc 情報を構築しています...
    :

<Project Root>\docディレクトリの中に、index.htmlを始め複数のHTMLファイルが生成された。

-sourcepath
コンパイル時と同様、ソースファイルが置かれているディレクトリの基点を指定する。
-d
javadocが生成するドキュメントファイルを書き出すディレクトリを指定する。
-author
コメント中に記述した@authorタグの内容をドキュメントに反映させる。デフォルトでは反映されない。
-version
コメント中に記述した@versionタグの内容をドキュメントに反映させる。デフォルトでは反映されない。

今後の改善点

ドキュメント一覧

<Project Root>
    |
    +---README.txt
    |
    +---doc
         +--- (APIドキュメント群)

配布

<Project Root>ディレクトリ以下を全てコピーする方法となる。もしソースコードを配布したくないなら、配布するディレクトリを選抜してコピーする必要がある。コピーを簡便にするには、圧縮解凍ツールを使うとよさそうである。LZHやZIP等で書庫を作ってそのファイルを配布し、配布先で解凍し展開する方法となる。


構成管理

構成管理には、いわゆるバージョン管理の他に、ソフトウェアを構成する物の特定、変更管理などがあるらしい。

配布を行った場合、次のバージョンの開発に移行するからといって配布したバージョンの内容を残しておかないとバグレポートの再現やバグフィックスが行えない。そこで、今回は安直であるが、配布した内容をソースも含めて保存しておくことにする。保存方法としては開発用のHDD上に、<Project Root>ディレクトリにそれとわかるような名前を付けることにする。よく使用されるのが年月日を付ける方法によって、バージョンの管理とする。構成はディレクトリ構成とその中のファイルで一応把握できるとし、変更があれば都度ディレクトリをコピーし、変更日をつけることで変更管理とする。また、ソースファイルのクラスコメントに@versionで数値を指定し、ファイル毎のバージョンがわかるようにする。

E:\ClockProject>cd ..
E:\>ren ClockProject ClockProject-20020603
E:\>

今後の課題


まとめ

イテレーションNo.1で実施したことは以下のとおり

イテレーションの初回なので、プログラムは非常に簡単な機能だけにとどめた。プログラムだけならたぶん仕様をちょっとみればすぐに作ってしまえる程度であろう。しかし、今後のイテレーションで繰り返すアクティビティ(作業)を確立する意味もあって、設計から試験(レビュー)、ドキュメント、配布(リリース)、構成管理を行った。プログラム開発に必要な作業が浅くではあるが、一通り見渡せたことになる。だが、一度のイテレーションですべて完璧に行うことはできない。理想は理想として持ちつづけ、初回のイテレーションをすみやかに実施することを最大の優先として、できる範囲で各アクティビティを行った。理想に近づけるのは、次回以降のイテレーションでもよいので、まずはすみやかにできることを行った。


This page is written by Toru TAKAHASHI.(torutk@02.246.ne.jp)