ケアレスミスを無くす章

1.6. 列挙型の定義

『目的』

いわゆる「列挙型」を使用しましょう。

『Before』

メソッドのパラメータとして定数を使うケースはよくありますが、 定数をオブジェクトとして用意することで事前に設定ミスに気づくことができます。

一般的によく見かける定数の表現は以下のようなものです。

public class Organization {
    // 総務部
    public static final int GENERAL_AFFAIRS = 0;
    // 第1事業部
    public static final int ORG_1 = 1;
    // 第2事業部
    public static final int ORG_2 = 2;
    // 第3事業部
    public static final int ORG_3 = 3;
    // 技術支援グループ
    public static final int TECH_SUPPORT = 4;
}

public static final で定数を表現しています。
public なのでどこからでもアクセスできます。
static なのでインスタンス化せず使用できます。
final なので初期化後に値の変更はできません。

例えば、こんなメソッドがあったとします。

/**
 * 所属を設定し初期化します。
 *
 * @param org 所属。
 *               Organization.GENERAL_AFFAIRS、
 *               Organization.ORG_1、
 *               Organization.ORG_2、
 *               Organization.ORG_3、
 *               Organization.TECH_SUPPORT
 *            のいずれかを指定してください。
 */
public void setOrganization(int org) {
    //    :
}

このメソッドにはどのような値を入れることができるか考えてみましょう。

「その1.用意されている定数を使う」
定数が Organization にて定義されているのでおとなしくそれを使います。
普通はこうしますね。
定数を使うときはこのようにして使います。

Organization.TECH_SUPPORT

/* 「技術支援グループ」とする。 */
field.setOrganization(Organization.TECH_SUPPORT);

「その2.定数を使わない」 定数を使わなくても、int値をそのまま放り込むことが可能ですね。
  ・Organization.TECH_SUPPORT は 4 と同等だから問題ないと考える。
  ・Javadoc を読まず、定数が用意されていることに気づかず数値をそのまま指定した。
という状況下ならあり得そうなケースです。

/* 「技術支援グループ」とする。 */
field.setOrganization(4);

「その3.定数を使わない」
そもそもint値ならば何でも放り込むことが可能です。
何が起こるかわかりませんが。

field.setOrganization(-1);
field.setOrganization(100);

「その1」から「その3」のどのケースにおいても、intの範囲内である有効な整数ですから エラーなくコンパイルは通ります。けれども、コンパイルできたから問題はないかというと実はそうとも言えません。

まず、「その2」は必ずしもうまくいく保証はありません。
後日、定数の定義が変更された場合に正しく動作しなくなる可能性が大です。

定数の定義を変更してみましょう。

public class Organization {
    public static final int GENERAL_AFFAIRS = 0;
    public static final int ORG_1 = 1;
    public static final int ORG_2 = 2;
    public static final int ORG_3 = 3;
    // 「第4事業部」誕生!値は 4。
    public static final int ORG_4 = 4;
    // 押し出されるように 4 -> 5 へ。
    public static final int TECH_SUPPORT = 5;
}

するとどうでしょう。

/* 「技術支援グループ」とする。 */
field.setOrganization(4);
// でも、「第4事業部」が誕生したおかげで、「第4事業部」にすり替わってしまう!

このままでは、コメントに書いてあるような期待した動作にはなりませんよね。 正しくするにはこのコードも変更する必要があるわけです。 定数値を使わなかったことによって変更に対して新たな変更を強いられてしまうわけです。
メソッドの呼び出し元を全て確認することになってしまうので面倒ですね。 保守が大変です。

「その3」のケースは明らかに無効な値です。

setOrganization メソッド内で、 GENERAL_AFFAIRS ~ TECH_SUPPORT が渡されることを想定していたらどうなるでしょうか。

public void setOrganization(int org) {
    if (org == Organization.GENERAL_AFFAIRS) {
        // 総務部用の初期化処理
    } else if (org == Organization.ORG_1) {
        // 第1事業部用の初期化処理
    } else if (org == Organization.ORG_2) {
        // 第2事業部用の初期化処理
    } else if (org == Organization.ORG_3) {
        // 第3事業部用の初期化処理
    } else if (org == Organization.TECH_SUPPORT) {
        // 技術支援グループ用の初期化処理
    }
    // -1 や 100 のような無効値では初期化されない。
    // 後々おかしくなってしまうでしょう。
}

無効な値が渡ってきたら、正常な動作は期待できずに実行時にエラーが出てしまう恐れがあります。 それでは、メソッド内に範囲チェックを実装して無効な値を防ぐようにするとどうなるでしょうか。

public void setOrganization(int org) {
    if (org == Organization.GENERAL_AFFAIRS) {
        // 総務部用の初期化処理
    } else if (org == Organization.ORG_1) {
        // 第1事業部用の初期化処理
    } else if (org == Organization.ORG_2) {
        // 第2事業部用の初期化処理
    } else if (org == Organization.ORG_3) {
        // 第3事業部用の初期化処理
    } else if (org == Organization.TECH_SUPPORT) {
        // 技術支援グループ用の初期化処理
    } else {
        /* 不正な設定値。 */
        throw new IllegalArgumentException("無効な所属です。");
    }
}

こうすることで、無効な値が渡されても何食わぬ顔をして実行されることはなくなるでしょう。
しかし、依然として実行時にIllegalArgumentException例外が送出されることに代わりありません。

つまり、「その2」や「その3」ともに「実行するまで」ミスに気づきません。
なるべくなら未然に防ぎたいものです。

『After』

このように一連の組になった値は列挙型(enum)を使用して表現しましょう。

public enum Organization {
    // 総務部
    GENERAL_AFFAIRS,
    // 第1事業部
    ORG_1,
    // 第2事業部
    ORG_2,
    // 第3事業部
    ORG_3,
    // 技術支援グループ
    TECH_SUPPORT,
    ;
}

enumは通常のクラスと違い、プログラマーがインスタンスを生成することはできません。

それでは、メソッドのパラメータの型を int から Organization に変更します。

public void setOrganization(Organization org) {
    //    :
}

こうすれば、int ではなくて Organization という型を入れなければならないと 具体的にわかりやすくなりますし、「その2」や「その3」のような暴挙は コンパイラがエラーとして叱ってくれます。

また、Organization のインスタンス(GENERAL_AFFAIRS ~ TECH_SUPPORT)しか パラメータとして渡すことを許さないので、setOrganization メソッド内では 範囲チェックする必要がなくなりますね。

『まとめ』

定数をenumとして用意すると以下のような効果があります。
メソッド内における範囲チェックの煩わしさから解放される。
無効な値の設定をコンパイル時に発見できる。
なので、パラメータの設定ミスを抑止することができますね。

< 前のページへ
お問い合わせ
  • ようかん
  • SG Labs(エスジー ラボ)
  • ITERACY
  • RiceLog(ライスログ)
  • Salesforce導入コンサルティング
  • SES営業向け用語講座

Pagetop