[ Java Project Exampleページへ戻る ]
時刻プログラム開発のイテレーションNo.1である。
最初のイテレーションなので、時刻プログラムのもっとも本質かつ最低限の機能だけを開発する。そこで、以下の機能を実現する。
なんとたったこれだけ。こんなの開発する意味ないって思えるくらい単純なもの。この僅かな仕様を見ただけで、設計なんて不要でコードまで頭に浮かぶかもしれない。けど、ソフトウェア開発プロジェクトには、仕様以外に考えたり決めておかねばならないことがいろいろある。今ぱっと頭に浮かぶのは、プロジェクト全体から見ればほんの一部分だけ。次節から、その考えねばならぬ事柄を一つ一つ見ていく。
設計といっても何をするのか。必要最低限の設計をするとしたら、何を決めればよいのだろうか。考えても堂堂巡りになりそうなので、まずは気の向くまま設計に手をつけてみよう。そこで問題点が出れば、そのとき考えればいいということで進めていく。
設計ではどんなインフラを使用するのか決める必要がある。構想においてUMLを使用することは決めたが、ツールは決めていなかった。このイテレーションでは非常にシンプルな設計モデルしか作成しないので、ツール選定をするのには不十分であろう。そこで、もう少しモデルが複雑になってからツールを導入することにして、今回は手書き(Web化する際はWindows OS付属のペイントで描いているが)でいくことにした。
いきなりだがパッケージを決める。なぜか?あまり前向きではない理由に、Javaではコードの最初にpackage文を記述するから、という点がある。前向きな理由としては、今後パッケージが再利用やJAR化、テスト、などといった作業をするときに扱う単位となるから、というのもある。ま、それは後の話だけど。
時刻プログラムが扱う問題領域は時刻なので、まずこれをパッケージとして切り出した。名前はとりあえず"clock"としておく。
![]() |
clockパッケージは、Java 2 Standard Edition(J2SE), ver 1.4で動作する。追加パッケージは不要とする。J2SEの中のどのパッケージに依存するかは、今後開発が進んで分かり次第記述することにする。
![]() |
clockパッケージは、利用者に時刻に関するサービスを提供するため、利用者から依存されている。これをパッケージ図に反映する。
![]() |
今回提供するクラスは時、分、秒の保持と取り出しである。まずはクラス図を書いてみた。
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 |
ここで記述したClockクラスであるが、今後を考えるとinterfaceを設けておいた方が望ましい気もする。しかし、Peter
Coadの戦略に基づけば、繰り返し表れる共通したシグネチャをinterfaceとして括り出すので、まだ最初の段階ではinterfaceとして有用かは判断がつかない。また、今後clockパッケージが複雑になるにつれ、ファサード、ファクトリ、オブザーバといった設計が導入されるだろう。そのときに今考えたinterfaceが変わらずに残りつづけるとは思えない。
であれば、まずはclassとして設計を進めていく。
![]() |
ここで、シーケンス図を作成すると、実装するプログラムの動作イメージが見えてくるが、今回のイテレーションではシーケンス図に記すほど複雑ではないので省略。
今回の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の記述を行っていく。ソースコードはソースファイルに記述する。Javaではpublicなclassは1つのファイルに書くことになっている。以下にソースファイルに記述することを挙げた。
/* * Project Clock * Copyright 2002 Toru TAKAHASHI. All rights reserved. */
package jp.gr.java_conf.torutk.jpe.clock;
/** * <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 */
public class Clock {
/** 時を保持する。有効範囲は 0-23 */ private int hour; /** 分を保持する。有効範囲は 0-59 */ private int minute; /** 秒を保持する。有効範囲は 0-59 */ private int second;
/** * <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 |
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>
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>
試験の種類にはいくつかある。ここでは開発の段階順に試験の種類を挙げてみた。試験のやり方にはいろいろな考え方があるので、ここで挙げたものは一例にすぎない。
また、試験とは別にレビューを行う。試験だけで全てのバグは見つけられないので、レビューと組み合わせてなるべく多くのバグを見つけて修正するとよい。
が、今回は試験は省略し、ClockClientが動けばよいものとする(とはいえそこには僅かだが試験の要素が含まれている)。その代わり、レビューをしてみた(独りレビューだが・・・)。
試験の前に、今回作成したClock.javaをコードレビューして問題がないか検査を行ってみる。といっても書いた本人が見直したら問題があっても気付かない。第三者がすべきところである。また、レビューの際には問題点を指摘するにとどめ、解決策を検討しないことが重要。
ドキュメントとして必要なのは下記であろう。
今回のイテレーションでは、ユーザーズ・マニュアルとプログラマーズ・マニュアルの内容をごく簡単に記したものを、READEファイルに記述することにした。
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ファイルが生成された。
<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で実施したことは以下のとおり
イテレーションの初回なので、プログラムは非常に簡単な機能だけにとどめた。プログラムだけならたぶん仕様をちょっとみればすぐに作ってしまえる程度であろう。しかし、今後のイテレーションで繰り返すアクティビティ(作業)を確立する意味もあって、設計から試験(レビュー)、ドキュメント、配布(リリース)、構成管理を行った。プログラム開発に必要な作業が浅くではあるが、一通り見渡せたことになる。だが、一度のイテレーションですべて完璧に行うことはできない。理想は理想として持ちつづけ、初回のイテレーションをすみやかに実施することを最大の優先として、できる範囲で各アクティビティを行った。理想に近づけるのは、次回以降のイテレーションでもよいので、まずはすみやかにできることを行った。