Back To Main

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ ◆Javaで珈琲ブレイク vol.006 前編◆
┃……………………………………………………………………………………………
┃ [不定期] まぐまぐ ID=0000088576 Melma! ID=m00061296
┃……………………………………………………………………………………………
┃ 今回からご覧になる方は、バックナンバーもご活用下さい
┃ http://www.melma.com/mag/96/m00061296/
┃ http://backno.mag2.com/reader/Back?id=0000088576
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

◆目次◆

■前回の解答
■単体テスト(Unit Test)超入門
■BankAccount.java


皆さん、こんにちは。

Mr.Hackです。

いかがお過ごしですか、Mr.HackはSpring semester(春期学期)のFinal(期末試験)の勉強で四苦八苦しています。皆さんはゴールデン・ゴールデンの後に五月病にならないようにがんばっていきましょう。

さっそく、宿題の解答からいってみましょう。

■前回の解答
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Question 5 の答え

【BankAccountTest.java】
━━━━━━━━━━━━ ここから ━━━━━━━━━━━━━━━━━━
/**
* BankAccountをテストするクラス
*
* @author Mr.Hack
*/
class BankAccountTest {
/**
* deposit()、withdraw(), getBlance()メソッドを
* それぞれテストする。
*/
public static void main(String[] args) throws Exception {

BankAccount account = new BankAccount();

// deposit()メソッドテスト
System.out.println("deposit()テスト");
/** マイナス引数テスト 引数-1で例外を発生させる */
try {
account.deposit(-1);
System.out.println("マイナス引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("マイナス引数テスト成功。例外:" + e.getMessage());
}
/** ゼロ引数テスト 引数0で例外を発生させる */
try {
account.deposit(0);
System.out.println("ゼロ引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("ゼロ引数テスト成功。例外:" + e.getMessage());
}
/** 上限金額超過引数テスト 引数100001で例外を発生させる */
try {
account.deposit(100001);
System.out.println("上限金額超過引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("上限金額超過引数テスト成功。例外:" + e.getMessage());
}

// withdraw()テスト
System.out.println("");
System.out.println("withdraw()テスト");
/** マイナス引数テスト 引数-1で例外を発生させる */
try {
account.withdraw(-1);
System.out.println("マイナス引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("マイナス引数テスト成功。例外:" + e.getMessage());
}
/** ゼロ引数テスト 引数0で例外を発生させる */
try {
account.withdraw(0);
System.out.println("ゼロ引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("ゼロ引数テスト成功。例外:" + e.getMessage());
}

/** 上限金額超過引数テスト 引数20001で例外を発生させる */
try {
account.withdraw(20001);
System.out.println("上限金額超過引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("上限金額超過引数テスト成功。例外:" + e.getMessage());
}
/** 残高超過引数テスト 1000円入金後、引数3000で例外を発生させる */
try {
account.deposit(1000);
account.withdraw(3000);
System.out.println("残高超過引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("残高超過引数テスト成功。例外:" + e.getMessage());
}


//getBlance()テスト
System.out.println("");
System.out.println("getBlance()テスト");
/** 残高1000円テスト 期待値1000円 */
account = new BankAccount(); //accountをリセット
double expectedAmount = 1000;
account.deposit(expectedAmount);
if (account.getBlance() == expectedAmount) {
System.out.println(expectedAmount + "円入金後テスト成功。Balance : " +
account.getBlance());
}
else {
System.out.println(expectedAmount + "円入金後テスト失敗。Expected Balance :" +
expectedAmount + " Actual Balance ; " + account.getBlance());
}

/** 残高399円テスト 期待値399円 */
account = new BankAccount(); //accountをリセット
expectedAmount = 399;
account.deposit(1000);
account.withdraw(601);

if (account.getBlance() == expectedAmount) {
System.out.println(expectedAmount + "円入金後テスト成功。Balance : " +
account.getBlance());
}
else {
System.out.println(expectedAmount + "円入金後テスト失敗。Expected Balance : " +
expectedAmount + " Actual Balance ; " + account.getBlance());
}
}
}
━━━━━━━━━━━━ ここまで ━━━━━━━━━━━━━━━━━━

前回は、BankAccount.classファイルと、そのクラスのAPI仕様をBankAccount.zipとしてダウンロードしてもらい、APIをみてBankAccount.classをテストするという内容でした。まだダウンロードされていない方は、

http://mrhack.hoops.ne.jp/zip/BankAccount.zip

からダウンロードして、BankAccount.classを自分で作った、BankAccountTest.javaと同じフォルダにおいてBankAccountTest.javaをコンパイル・実行してください。上記の解答のBankAccountTest.javaを実行すると

------------------------------------------------------------------------
D:\cvs\magmag_project\javaCode\ch5_try_catch>java BankAccountTest
deposit()テスト
マイナス引数テスト成功。例外:入力した金額は0かマイナスです。amount: -1.0
ゼロ引数テスト成功。例外:入力した金額は0かマイナスです。amount: 0.0
上限金額超過引数テスト成功。例外:一度に入金できる額の上限は100000.0です

withdraw()テスト
マイナス引数テスト成功。例外:入力した金額は0かマイナスです。amount: -1.0
ゼロ引数テスト成功。例外:入力した金額は0かマイナスです。amount: 0.0
上限金額超過引数テスト成功。例外:残高を超えています
残高超過引数テスト成功。例外:残高を超えています

getBlance()テスト
1000.0円入金後テスト成功。Balance : 1000.0
399.0円入金後テスト成功。Balance : 399.0
------------------------------------------------------------------------

となります。前回はtry&catch構文を習いましたが、それを少し応用して各BankAccountクラスのメソッドをテストしてみました。最初のtry&catch構文を見てみましょう。BankAccountクラスのインスタンスを初期化(残高ゼロ)した後、

BankAccount account = new BankAccount();
try {
account.deposit(-1);
System.out.println("マイナス引数テスト失敗。例外発生できず");
}
catch (Exception e) {
System.out.println("マイナス引数テスト成功。例外:" + e.getMessage());
}

テストとして、deposit()メソッドに-1をアーギュメント(実引数)として渡します。APIを見ると、

------------------------------------------------------------------------
java.lang.Exception - amountがゼロかマイナスの場合、引き出し額が残高を 上回った場合、 または、一度に引き出せる金額を上回った場合、例外発生
------------------------------------------------------------------------

amountがマイナスの場合はException classの例外が発生すると書いてあるので、APIのクライアント(client)であるBankAccountTestクラス側では、その発生した例外をキャッチしてあげなければなりません。テストでは、わざと-1で例外を発生させようとしているので、期待しているプログラムの流れは、

------------------------------------------------------------------------
account.deposit(-1);
------------------------------------------------------------------------

で例外が発生し、catch節に流れが移り、

------------------------------------------------------------------------
System.out.println("マイナス引数テスト成功。例外:" + e.getMessage());
------------------------------------------------------------------------

が実行されます。例外が発生したときは、次の文である、

------------------------------------------------------------------------
System.out.println("マイナス引数テスト失敗。例外発生できず");
------------------------------------------------------------------------

は実行されません。逆に、例外が思ったように(期待したように)例外が発生しなければ、すぐaccount.deposit(-1)に続き”マイナス引数テスト失敗”が実行され、catch節は実行されません。つまり、try&catch構文をうまく利用することによって、メソッドである、deposit()をテストすることができるのです。

それに続く各try&catch構文は基本的に同じ考えで同じことをしています。それでは、例外を発生させないgetBlance()はどうやってテストするのでしょうか?

getBlace()メソッドは戻り値としてdoubleを返します。そのため、戻ってきた実際の値が、論理上期待される値と同じかどうかを比較し違っていれば”テスト失敗”、同じであれば、”テスト成功”というメッセージをプリントすれば、良さそうです。

------------------------------------------------------------------------
account = new BankAccount(); //accountをリセット
double expectedAmount = 1000;
account.deposit(expectedAmount);
if (account.getBlance() == expectedAmount) {
System.out.println(expectedAmount + "円入金後テスト成功。Balance : " + account.getBlance());
}
else {
System.out.println(expectedAmount +
"円入金後テスト失敗。Expected Balance :" + expectedAmount +
" Actual Balance ; " + account.getBlance());
}
------------------------------------------------------------------------

accountの参照変数を新しくnew BankAccount()作られた新しいオブジェクトを参照し直します。つまり、残高はゼロになります。その後期待値である1000円を入金します。すると、そのBankAccountクラスのオブジェクトの残高は、論理上は、1000円になります。そして、getBlance()メソッドを使えば、論理上は、1000.00というdouble値が戻ってくるはず、です。その実際の戻ってきた実際値と期待値をif文を使って比較します。同じなら”テスト成功”、違うなら"
テスト失敗”となります。


■単体テスト(Unit Test)超入門
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
さて、既存のクラスをテストクラスを作り、対象クラスのメソッドをテストしました。このように、クラスのメソッドやメソッドの戻り値が正しい値かどうかを調べることをテストする、といいます。単体テストとは、ユニット(unit)であるクラス単位で、テストすることをいいます。何故このようなテストが必要なのでしょうか?

皆さんの中には、何で地道なテストなんてしないといけないの?それよりも、コマンドベースのソフトのような、ちゃんとメニューがあって、メニューを選ぶと預金や引出をするようにしたい、と思っている方もいらっしゃるでしょう。または、JSPやServletなる技術を早くつかってサイト構築をしてみたい、と思っている方もいるでしょう。そう思っている方からすれば、テストなんて、ホント苦痛でしかないことかもしれません。それより新しい技術であるJSP, Servletを体験してみたい、と思うかもしれません。

気持ちは、分かります。でも、もう少しお待ち下さい。はやる気持ちを抑えて、もう少しテストの仕方をじっくり学びましょう。これが、必ず、あなたのプログラム技術を向上してくれます。デバッグによるバグ発見までの時間を大幅に短縮してくれます。

ユニットテスト(単体テスト)とは、どういうものなのでしょうか?

伝言ゲームを想像してください。AさんがBさんに”Mr.Hack”と言いました。BさんはCさんに同じことをいいます。CさんはDさんに、DさんはEさんに、と伝言していきます。最後にZさんに伝言は何ですか?と聞くと”Mr.Maric”と答えました。伝言失敗です。Zさんはどこで、誰が間違えたか、分かりません。どうやって原因を確かめますか?Zさんは最初に、YさんにXさんはなんと言いましたかと聞きます。Xさんは”Mr.Hack”です、と答えてくれれば、ビンゴ!原因究明です。Yさんが間違えたのです。しかし、Xさんにきいても、WもVさんもMr.Maricと答えたらどうしましょう?さて困りました。Zさんは、アルファベットの逆順にひとりづつ聞いていくのが効率がいいでしょうか?それとも、真ん中あたりの、Lさんに聞くのがいいでしょうか?それとも、Aさんに?・・・。

この伝言ゲームなら最高でも25回(Xさんに1回と数えれば、Wさんが2回・・・Aさんが25回目)聞けば、原因を確かめられます。しかし、実際のプログラムだと、AさんからGさんにいきなり飛んだり、GさんがまたAさんに伝言を戻したりと、複雑な関係の上に成り立っています。

そこで登場するのがユニットテストです。まず、AさんがBさんに”Mr.Hack”とちゃんと言ったかを伝言時にテストします。確かに”Mr.Hack”と言いました。Bさんは、Cさんに”Mr.Hack”といったか伝言時にテストします。このようにして、全てのアルファベットを伝言時にテストしていきます。Gでテストすると、”Mr.Maric”と伝言していました。このように伝言時点で、一つのアルファベットをテストすることをユニットテスト(Javaではクラス、classがユニットにあたる)といいます。このように全てテストしておけば、事前にどこで間違えたかを確認でき、最終的にはZは正しい伝言を受けたと確認できます。後者のGで間違いだと分かったのはクラス単位でテストした段階での発見といえます。上記のZさんが伝言を受け取った時に間違いだと分かるのは、実際のプログラムで言えば、プログラムが完成し実際施行テストする段階での発見といえるでしょう。どちらが効率いいでしょう?クラスが2、3ぐらいしかない場合はあまり、ユニットテストのありがたみを味わえないかもしれませんが、これが、20も30も、はたまた、100クラスを超えたときのことを想像すると、原因究明に多大な時間を浪費するだろうことが容易に想像できます。

ところで、余談ですが、ソフトウェア工学にXP(eXtreme Programming、WindowsXPではない)と言うプログラミングスタイルがあります。そのスタイルの一つに、プログラムを書く前に、テストプログラムを書き、その期待しているテストに沿ったプログラムを書けば、エラーが起こらないという考え方があります。ちゃんとしたテストプログラムを書いて、それを満たすメソッドであれば、たしかにそのクラスはちゃんと作動しますね。この条件ではマイナスはダメというテストプログラムを書きテストメソッドがマイナスを出さないようにするまで、プログラムを書き直します。伝言ゲームで言えば、FさんがGさんに”Mr.Hack"と言わなければ、言うまでFさんをテストします。Fさんが”Mr.Hack”と発音出来なければ、発音の強制をします。発声練習をさせます。そしてFさんが”Mr.Hack”と伝言出来るようになれば、やっと次の段階に移ります。

でも、この考え方は、初心者の方にはとても、難しいのです。なぜなら、テストプログラムをつくっても、テストの対象になるクラスが存在しないと、テストプログラムを実行できないからです。Fさんの発声練習にはFさんが実際いないと出来ないのと同じです。前回の宿題でも、BankAccount.classファイルがないと、BankAccountTest.javaをコンパイル実行できませんね。

ためしに、BankAccount.classを削除するなり、他のフォルダに移すなりして、コンパイルしてみてください。テストの対象であるBankAccount.classがないので、もちろんコンパイル・実行はできません。

銀行口座を実現するクラスBankAccount.javaを作れといわれたときに、ふつうは、メンバフィールド(変数)やそのメンバフィールドにアクセスするメンバメソッド(メソッド)を考え、メソッドが戻り値を返すか返さないか、返すなら、どのデータ型かを考え、そして、メソッドの中身を考えますね。そしてメソッドに戻り値があるなら、ちゃんと望みの値を返すかどうかをチェックします。

しかし、XPでは、はじめに、銀行口座に必要なメンバ(変数・メソッド)を考えて、そのメソッドをテストするプログラム(BankAccountTest.java)を書きます。そして、テストプログラムで”テスト成功”となるように各メソッドのするべきことを決めるのです。これが、XPでいわれている実際のコードを書く前にテストプログラムを書くということなのです。

XPで言われているようにまで、皆さんは徹底する必要はないと思いますが、クラスを書いたらテストする、この習慣をぜひ身につけたいですね。

それでは実際に、BankAccount.javaのクラスを作って実感してみましょう。

■BankAccount.java
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

BankAccount.javaを書くにあたって、もう一度、クラスを確認します。クラスとはオブジェクトを作るに当たっての仕様書・設計図でしたね。たい焼きの例を思い出してください。たい焼きの例では、たい焼きにはあんこがありますね。たい焼きにあんこ(メンバフィールド、変数)を含めるということをクラス仕様書で定義してあげました。銀行口座というクラス仕様書を作るに当たって、どんなメンバーフィールドが必要ですか?もし、総合口座にすれば、

口座番号
氏名
定期預金残高
普通預金残高
...

等いろいろと考えられますね。この場合、例えば、

class BankAccount {

// メンバーフィールド
String accountNumber; //口座番号
String personName; //預金者氏名
double savingBalance; //定期預金残高
double checkingBalance; //普通預金残高

// メンバーメソッド
String getAccountNumber() {
do something
}

void setPersonName(String personeName) {
do something
}
....

}

のようになります。物事を簡潔にするために、BankAccount.javaクラスは、balanceフィールドだけ持つようにしましょう。つまり、定期預金、普通預金区別なく、単に残高一つだけ存在するだけにします。また、口座番号、預金者氏名等も省略します。

class BankAccount {

double balance; //預金残高
...
}

これで、BankAccountクラス仕様書にしたがって作られたオブジェクトには全て、balanceフィールドが含まれますね。たい焼きのあんこのように。

ところで、なぜint型でななくて、double型なのでしょう。お金の単位は1円からですね。1円が実際の日本社会でやりとりされる最小単位です。100円なら、100でいいですね。しかしながら、論理上は”銭”という単位が存在します。たとえば、為替相場を見てください。1ドルが130円56銭になります。実際銭という単位でお金をやりとりはできず、切り上げ・切り下げして扱われます。しかしながら、ドルを円に換算して、銭を切り下げして、引き出すにしても、銭という単位まで考えるか否かによって、実際の手元にくるお金はかなり変わってきます。たとえば、為替相場で、1ドル130円56銭ではなく、130円ならどうでしょう。もし1000ドルを円に換えると、1000 x 130 = 130,000円ですが、銭単位まで考えると、130,560円になり、560円も変わってきますね。そのため、銭単位まで考える必要は未だにあるとおもいます。あるいは、ビジネスルールとして、為替相場で銭単位の端数がでるにしろ切り捨て、切り捨てた後、その切り捨てた円を口座に預金する、とするのであれば、balanceはint型でもいいと思います。

オブジェクトを作るときに、初期化が必要になりますが、初期化の働きをになうのが、コンストラクタです。初期化するとは、この場合、どういう意味なのでしょう。口座預金の場合は、新しく口座を作る(BankAccountのインスタンスを作
る)、ということを意味しますね。そのときに、何をしなければならないですか?balanceである残高はゼロにしたいですね。つまり、

// コンストラクタ。初期化時残高をゼロにセット
BankAccount() {
balance = 0.0;
}

これで、初期化(Instantiate)するときに残高はゼロにセットされます。また、はじめて預金口座を開設したときに、ついでにお金も預金したい人もいるでしょう。そのために、パラメータ付きのコンストラクタも作りましょう。

// コンストラクタ。初期化時残高をamount分セット
BankAccount(double amount) {
balance = amount;
}

BankAccount(double amount)のコンストラクタを作ると、balanceをゼロとセットするデフォルトのBankAccount()コンストラクタもBankAccount(double amount)の一部と見ることができます。つまり、thisキーワードをつかって、デフォルトコンストラクタは

// コンストラクタ。初期化時残高をゼロにセット
BankAccount() {
this(0);
}

とすることができます。カッコ付きのthisキーワードは、コンストラクタを表します。初期化しようとしているインスタンスの、パラメータがdoubleである”この(BankAccount(double amount))”コンストラクタを使い、ゼロで初期化するということです。

初期化をしてBankAccountクラスのインスタンスを作った後に、口座開設者は預金をしますね。そのため、預金するためのメソッドが必要になります。預金するためのメソッドをdeposit(預金する)としましょう。depositするためには預金するお金(amount)が必要なので、そのお金をパラメータとして、

// amount分のお金を預金する
void deposit(double amount) {
balance = balance + amount;
}

というようになります。プリミティブ型(ここでは、double型)のときは、"="(イコール)は代入を意味します。つまり、いままでの残高(balance)に新しく預金するamountを足してその合計(balance + amount)を新たに、balance変数に代入します。もしここで、

balance = amount;

としたら、どうなりますか?これは、amountをそのまま、balanceに代入する(上書きする)ことを意味しますね。残高が0.0円の時は問題ないです。しかし、もし、前に残高があったらどうしましょう?例えば、すでに、100.00円という残高をbalance変数が保持していて、預金額500.00円をamountとしてdeposit(預金)するとどうなりますか?そう、100.00という値が、500.00という値で上書きされてしまいますね。そのため、balance + amountという形が必要なのです。

balance = balance + amount;

blance += amount;

と同じことを意味します。初心者の方は、この形になれるまで結構苦労なさると思いますが、プログラミングではよく使うスタイルなので、がんばってなれてみてください。

銀行口座から、ある金額(amount)を預金したら、引き出すメソッドが必要ですね。引き出すメソッドをwithdraw(引き出す)としましょう。引き出すときにいくら引き出すか問題なので、amount(お金)のパラメータが必要ですね。withdraw()メソッドは下記のようになります。

// amount分の金額を引き出す。amount分をbalanceから引く
void withdraw(double amount) {
balance -= amount;
}

ここでは、-=がでてきましたね。これは、+=と同じスタイルで、

balance = balance - amount;

と同じ意味です。

さて、最後に、現在の残高を得るメソッドを作りましょう。そのメソッドをgetBlanceとし、戻り値は、現在の残高を返すようにしてみましょう。現在の残高を返すには、

// 残高を返却
double getBlance() {
return balance;
}

となります。BankAccountクラスのメンバフィールドであるbalance変数が保持している値を返します。

ここまでの完成classが下記です。

【BankAccount.java】
━━━━━━━━━━━━ ここから ━━━━━━━━━━━━━━━━━━
/**
* バンク預金口座クラス。顧客の口座の出し入れ、残高
* 照会をおこなう(通貨単位:円)。
*
*
* @author Mr.Hack
*/
public class BankAccount {

/**
* 口座の残高
*/
private double balance;
/**
* デフォルトコンストラクタ。初期化時、
* 残高をゼロにセットする。
*/
public BankAccount() {
this(0);
}

/**
* 預金額付きコンストラクタ。初期化時(新しい口座作成時)、
* 預金額を残高としてセットする。
*
* @param amount 口座に預け入れる金額(預金)
*/
public BankAccount(double amount) {
this.balance = amount;
}

/**
* amount分を預金として口座に預ける。
*
* @param amount 口座に預け入れる金額(預金)
*/
public void deposit(double amount) {
this.balance += amount;
}

/**
* amount分の金額を引き出す。
*
* @param amount 引き出す金額
*/
public void withdraw(double amount) {
this.balance -= amount;
}

/**
* 現在の残高を戻り値として返す。
*
* @return balance 口座にある現在の残高
*/
public double getBlance() {
return this.balance;
}
}
━━━━━━━━━━━━ ここまで ━━━━━━━━━━━━━━━━━━

BankAccount.javaがあるフォルダに、Question 5の解答のBankAccountTest.javaがあることを確認(前回ダウンロードしたBankAccount.classはどこか違うフォルダに移してください。そうしないとコンパイル時に、上書きされます)し、コンパイル実行してみてください。コンパイルエラーはでませんが、実行すると、

------------------------------------------------------------------------
deposit()テスト
マイナス引数テスト失敗。例外発生できず
ゼロ引数テスト失敗。例外発生できず
上限金額超過引数テスト失敗。例外発生できず

withdraw()テスト
マイナス引数テスト失敗。例外発生できず
ゼロ引数テスト失敗。例外発生できず
上限金額超過引数テスト失敗。例外発生できず
残高超過引数テスト失敗。例外発生できず

getBlance()テスト
1000.0円入金後テスト成功。Balance : 1000.0
399.5円入金後テスト成功。Balance : 399.5
------------------------------------------------------------------------

となり、getBlance()テストは成功していますが、deposit()テストとwithdraw()テストは失敗です。何故でしょう?原因を探ってみましょう。

BankAccountTest.javaクラスを見てください。deposit()メソッドとwithdraw()メソッドはそれぞれ、try&catch構文を使って例外が発生すれば”テスト成功”、発生しなければ、通常処理で”テスト失敗”になりました。というのは、-1や0等は本来、預金金額や引出金額として望まれない値ですから、クライアントであるBankAcount.classでdeposit()メソッドやwithdraw()メソッドを使った(呼び出した)ときに例外が発生したのですね。それをチェックして、テストが成功したか否かをチェック(プリントアウト)したのです。つまり、上記のBankAccount.javaでは-1や0等をパラメータとして受け取っても、例外を発生させられなかったのですね。だから、BankAccountクラスのクライアントであるBankAccountTestクラスは例外をキャッチできず、通常の処理(例えば、account.deposit(-1)のあと、catch節に移らずに、System.out.println()メソッドで、”テスト成功”をプリントアウト)をしたわけです。

では、どうやって、例外を発生させることができるのでしょうか?

引き続き、後編をお楽しみ下さい。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆Javaで珈琲ブレイク◆
────────────────────────────────────
皆様からの激励・批判のメールをお待ちしております。
【発行者】 Mr.Hack - javacafebreak@hotmail.com
【掲示板】 http://fweb.midi.co.jp/~romanhikou/cafe_entrance.html
※質問は上記の掲示板からどうぞ。
【サイト】 http://mrhack.hoops.ne.jp/
【発行数】 まぐまぐ[810] Melma[108]
【解除】http://mrhack.hoops.ne.jp/
※解除は上記のサイトからいつでもできます。
────────────────────────────────────
(c)2002 MR.HACK ALL RIGHTS RESERVED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


© 2002 MR.HACK ALL RIGHTS RESERVED