Index: [Article Count Order] [Thread]

Date:  Wed, 20 Sep 2000 17:49:11 +0900
From:  tetsuya@....jp
Subject:  [XP-jp:00903] Re: VXP タス	ク3 --Config
To:  extremeprogramming-jp@....jp (extremeprogramming-jp ML)
Message-Id:  <97BA340C0480D411BDA800062939A1890607AA@....jp>
Posted:  Wed, 20 Sep 2000 17:47:37 +0900
X-Mail-Count: 00903

栗原です。

以下、花子さんモードに入ります。
「#」のコメントのところは素モードです:-)

えっと、誰それさんか分からないけど、こんな提案があったわね。

> それから、これは、ユーザーに有意義な情報ということで、例外も追加します。
>
>    class BadMLName extends Exception { }
>    class BadMemberListFileName extends Exception { }

有意義な情報を提供するという意見には賛成だわ。
でも、このまま例外のサブクラスを作成していってしまうと、新しく例外が作成
されるたびにインタフェースが変わってしまって、管理が大変になってしまうわね。

コンストラクタ部分
public Config(String nameOfConfigFile) throws xxxxx, xxxxx

みたいに、例外が追加されるとこのクラスを利用しているクラスも更新しないとい
けなくなるわ。
パッケージを作成して、そのパッケージの例外クラスを定義し、そのサブクラスを
利用するようにすれば、Configクラスを利用するクラスは、スーパークラスの例外
をキャッチするだけで良くなるわね。
# どっかで読んだ気がして頭の隅に残っていたのですが、読み返していくうちに
# 見つかりました。
# 「リファクタリング」P.64のインタフェースの変更です。

パッケージ名は、、、太郎さんがいないからとりあえず「system」としておくわ。
ConfigとConfigTestのファイルをsystemディレクトリに移動させて、パッケージ
指定も変更するわね。

package XP.jp.co.esm.wiki.extremedomo.system;

import java.util.*;
import java.io.*;

public class Config {
...略
}

package XP.jp.co.esm.wiki.extremedomo.system;

import junit.framework.*;

import java.io.*;
import java.util.*;

public class ConfigTest extends TestCase {
...略
}

それから、このパッケージ内で使用する例外のスーパークラスを定義した後、その
サブクラスを定義するわ。
例外クラスには、「Exception」を付加するんだったわね。

/*
 * $Id$
 */
package XP.jp.co.esm.wiki.extremedomo.system;

public class SystemException extends Exception {
}

/*
 * $Id$
 */
package XP.jp.co.esm.wiki.extremedomo.system;

public class BadMLNameException extends SystemException {
}

/*
 * $Id$
 */
package XP.jp.co.esm.wiki.extremedomo.system;

public class BadMemberListFileNameException extends SystemException {
}

これで良しと。
次は、ConfigTestだけど、今回はConfigクラスの方を先に変更してConfigTestの
テストが失敗することを確認することにするわ。
まずは、コンストラクタが投げる例外はスーパークラスを指定するから、

public Config(String nameOfConfigFile) throws SystemException {
...略
}

実際に例外を投げるところは、誰それさんのコードをそのまま書くわね。

    if (fileLine.startsWith("X-ML-Name:")) {
        name = fileLine.substring("X-ML-Name:".length()).trim();
        if (!isValidMLName(name))
            throw new BadMLNameException();
    }
    if (fileLine.startsWith("$-MemberList:")) {
        nameOfMemberListFile =
            fileLine.substring("$-MemberList:".length()).trim();
        if (!isValidMemberListFileName(nameOfMemberListFile))
            throw new BadMemberListFileNameException();
    }

こんな風になるわね。
ん。コンパイルも通ったわ。
それじゃあ、ConfigTestを実行、、、っと。
...やっぱりエラー
IllegalArgumentExceptionが投げられなくなって、変わりにチェックされる例外
を投げているので当然ね。
そういう動作をしているかどうか確認しながら修正をして
カタカタ....

テストケースはこんな感じかしら。

/*
 * $Id$
 */
package XP.jp.co.esm.wiki.extremedomo.system;

import junit.framework.*;

import java.io.*;
import java.util.*;

public class ConfigTest extends TestCase {

    public ConfigTest(String name) {
        super(name);
    }

    public static void main(String[] args) {
        junit.textui.TestRunner.run(ConfigTest.class);
    }
    private void createCorrectConfigFile(String filename) {
        FileOutputStream fo;
        try {
            fo = new FileOutputStream(filename);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("unable to open config file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("X-ML-Name: extremeprogramming-jp");
        pw.println("Y-ML-Name: extremeprogramming-false");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$-MemberList: memberlist");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try {
            fo.close();
        } catch (IOException e) {
            throw new RuntimeException("error on closing  config file");
        }
    }

    public void testCorrectConfig() throws SystemException {
        createCorrectConfigFile("config");

        Config aConfig = new Config("config");

        assertEquals("test1", "extremeprogramming-jp", aConfig.getName());

        Iterator iterator = aConfig.getXInfo();
        assertEquals("test2", "X-ML-Name: extremeprogramming-jp",
iterator.next());
        assertEquals("test3", "X-Mail-Count: 00796", iterator.next());
        assertEquals(
                "test4",
                "X-MLServer: fml [fml 2.1_GAMMA#185](distribute only mode)",
                iterator.next());
        assertEquals(
                "test5",
                "X-ML-Info: If you have a question,
                contact with me", iterator.next());
        assert("test6", !iterator.hasNext());

        assertEquals("test7", "memberlist",
aConfig.getMemberListFileName());
    }
    public void testNoConfigFile() throws SystemException {

        try{
            Config aConfig = new Config("nonconfig");
            fail("Should raise an IllegalArgumentException");
        }catch(IllegalArgumentException e){
            assert(true);
        }

    }
    public void testNoNameConfig1() throws SystemException {
        FileOutputStream fo;
        try{
            fo = new FileOutputStream("badconfig");
        }catch(FileNotFoundException e){
            throw new RuntimeException("unable to open badconfig file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("Y-ML-Name: extremeprogramming-false");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$-MemberList: memberlist");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try{
            fo.close();
        }catch(IOException e){
            throw new RuntimeException("error on closing badconfig file");
        }

        try{
            Config aConfig = new Config("badconfig");
            fail("Should raise a BadMLNameException");
        }catch(BadMLNameException e){
            assert(true);
        }

    }
    public void testNoNameConfig2() throws SystemException {
        FileOutputStream fo;
        try{
            fo = new FileOutputStream("badconfig");
        }catch(FileNotFoundException e){
            throw new RuntimeException("unable to open badconfig file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("X-ML-Name:");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$-MemberList: memberlist");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try{
            fo.close();
        }catch(IOException e){
            throw new RuntimeException("error on closing badconfig file");

        }

        try{
            Config aConfig = new Config("badconfig");
            fail("Should raise a BadMLNameException");
        }catch(BadMLNameException e){
            assert(true);
        }

    }
    public void testNoNameConfig3() throws SystemException {
        FileOutputStream fo;
        try{
            fo = new FileOutputStream("badconfig");
        }catch(FileNotFoundException e){
            throw new RuntimeException("unable to open badconfig file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("X-ML-Name:                              ");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$-MemberList: memberlist");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try{
            fo.close();
        }catch(IOException e){
            throw new RuntimeException("error on closing badconfig file");

        }

        try{
            Config aConfig = new Config("badconfig");
            fail("Should raise a BadMLNameException");
        }catch(BadMLNameException e){
            assert(true);
        }

    }
    public void testNoMemberListConfig1() throws SystemException {
        FileOutputStream fo;
        try{
            fo = new FileOutputStream("badconfig");
        }catch(FileNotFoundException e){
            throw new RuntimeException("unable to open badconfig file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("X-ML-Name: extremeprogramming-jp");
        pw.println("Y-ML-Name: extremeprogramming-false");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try{
            fo.close();
        }catch(IOException e){
            throw new RuntimeException("error on closing badconfig file");
        }

        try{
            Config aConfig = new Config("badconfig");
            fail("Should raise a BadMemberListFileNameException");
        }catch(BadMemberListFileNameException e){
            assert(true);
        }

    }
    public void testNoMemberListConfig2() throws SystemException {
        FileOutputStream fo;
        try{
            fo = new FileOutputStream("badconfig");
        }catch(FileNotFoundException e){
            throw new RuntimeException("unable to open badconfig file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("X-ML-Name: extremeprogramming-jp");
        pw.println("Y-ML-Name: extremeprogramming-false");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$-MemberList:");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try{
            fo.close();
        }catch(IOException e){
            throw new RuntimeException("error on closing badconfig file");
        }

        try{
            Config aConfig = new Config("badconfig");
            fail("Should raise a BadMemberListFileNameException");
        }catch(BadMemberListFileNameException e){
            assert(true);
        }

    }
    public void testNoMemberListConfig3() throws SystemException {
        FileOutputStream fo;
        try{
            fo = new FileOutputStream("badconfig");
        }catch(FileNotFoundException e){
            throw new RuntimeException("unable to open badconfig file");
        }
        PrintWriter pw = new PrintWriter(fo);
        pw.println("X-ML-Name: extremeprogramming-jp");
        pw.println("Y-ML-Name: extremeprogramming-false");
        pw.println("X-Mail-Count: 00796");
        pw.println("Y-Mail-Count: 00888");
        pw.println("$-MemberList:                       ");
        pw.println("$$-MemberList: memberrrlist");
        pw.println("X-MLServer: fml [fml 2.1_GAMMA#185](distribute only
mode)");
        pw.println("Y-MLServer: fml [fml 2.1_GAMMA#185](not distribute only
mode)");
        pw.println("X-ML-Info: If you have a question, contact with me");
        pw.println("Y-ML-Info: If you have a question, do not contact with
me");
        pw.close();
        try{
            fo.close();
        }catch(IOException e){
            throw new RuntimeException("error on closing badconfig file");
        }

        try{
            Config aConfig = new Config("badconfig");
            fail("Should raise a BadMemberListFileNameException");
        }catch(BadMemberListFileNameException e){
            assert(true);
        }

    }
    protected void setUp() {
    }

    protected void tearDown(){
    }
}

これで良いはず。コンパイルは、、、OKね。
じゃテストするわね、、、あれ、投げられるべき例外が投げられないわね。
X-ML-Nameなどがないときに投げられるべき例外が投げられてないみたい。
Configクラスを見直してみると、、、、

while (true) {
    fileLine = bufIn.readLine();
    if (fileLine == null)
        break;

    if (fileLine.startsWith("X-ML-Name:")) {
        name = fileLine.substring("X-ML-Name:".length()).trim();
        if (!isValidMLName(name))
            throw new BadMLNameException();
    }

    if (fileLine.startsWith("$-MemberList:")) {
        nameOfMemberListFile = fileLine.substring("$-MemberList:".length()).
trim();
        if (!isValidMemberListFileName(nameOfMemberListFile))
            throw new BadMemberListFileNameException();
    }
    if (fileLine.startsWith("X-"))
        xList.add(fileLine.trim());
}

なるほど。X-ML-Nameが見つかったら、その値に対してチェックをしているけれど、
これでは見つからなかったときはチェックが行われないわね。
ループが終了してから、フィールドに対してチェックするのがよさそうね。
まずは、明らかな名前をもつチェックメソッドを用意して、、、

    private void checkMLName() throws BadMLNameException {
        if (isEmptyString(name))
            throw new BadMLNameException();
    }
    private void checkMemberListFileName()
        throws BadMemberListFileNameException {

        if (isEmptyString(nameOfMemberListFile))
            throw new BadMemberListFileNameException();
    }

で、これをコンストラクタの最後で呼び出せばOKね。
isValidXXX関連は不要になったので消すわ。
コンパイルは、、、OKね。テストケースも通るようになってとりあえずは一段落ね。

/*
 * $Id$
 */
package XP.jp.co.esm.wiki.extremedomo.system;

import java.util.*;
import java.io.*;

public class Config {
    public Config(String nameOfConfigFile) throws SystemException {
        BufferedReader bufIn = null;
        try {
            bufIn = new BufferedReader(new FileReader(nameOfConfigFile));

            String fileLine;

            while (true) {
                fileLine = bufIn.readLine();
                if (fileLine == null)
                    break;

                if (fileLine.startsWith("X-ML-Name:")) {
                    name = fileLine.substring("X-ML-Name:".length()).trim();
                }
                if (fileLine.startsWith("$-MemberList:")) {
                    nameOfMemberListFile =
                        fileLine.substring("$-MemberList:".length()).trim();
                }
                if (fileLine.startsWith("X-"))
                    xList.add(fileLine.trim());
            }
        } catch (FileNotFoundException e) {
            // What is exception handling carried out?
            throw new IllegalArgumentException();

        } catch (IOException e) {
            // What is exception handling carried out?
        } finally {
            try {
                if (bufIn != null)
                    bufIn.close();
            } catch (Exception e) {
            }
        }
        checkMLName();
        checkMemberListFileName();
    }
    public String getName() {
        return name;
    }
    public Iterator getXInfo() {
        return xList.iterator();
    }
    public String getMemberListFileName(){
        return nameOfMemberListFile;
    }
    private boolean isEmptyString(String str) {
        return str == null || str.length() == 0;
    }
    private void checkMLName() throws BadMLNameException {
        if (isEmptyString(name))
            throw new BadMLNameException();
    }
    private void checkMemberListFileName()
        throws BadMemberListFileNameException {

        if (isEmptyString(nameOfMemberListFile))
            throw new BadMemberListFileNameException();
    }

    private String name = null;
    private List xList = new ArrayList();
    private String nameOfMemberListFile = null;
}

他には、name, nameOfMemberListFileのフィールドがデフォルトの初期化
に頼っていたので、明示的に初期化するようにしておいたわ。

それと、コンストラクタ内のIOExceptionとFileNotFoundExceptionの例外を
どうするかをきちんと決めてないわね。
IOExceptionのみ(FileNotFoundExceptionはIOExceptionのサブクラス)を
投げるようにしておくのもありかもしれないし、SystemExceptionでラップ
してしまってもいいかもしれないけど、ちょっと私だけでは決められないので
太郎さんと相談ね。

以外に手間取ったおかげで他のクラスの作成まで手が回らなかったけど、今日
はこの辺で終わりにしようかしら。。。

# 今まで作成したものを、CVSにチェックインするのは誰の担当なのでしょうか?
# とりあえずは、今までのコードが反映された最新版をあげておきたいと思います。

---
Tetsuya Kurihara
tetsuya@....jp