Defineアレルギー

 baristaはDefineアレルギーである。新しいプロジェクトに参画して、既存モジュールの中にDefine.javaを発見した瞬間、全身に発疹ができる。これはbarista体内のDefineに対するIgE抗体が肥満細胞を活性化、ヒスタミン,ロイトコリエンなどの化学伝達物質が体内に放出されるためである。酷い場合にはアナフィラキシーショックを引き起こし、血圧の低下と上気道の浮腫による呼吸困難に苦しむ羽目になる。この場合、エピネフリン(アドレナリン)の皮下注がfirst aidだ。ここで静注を行なってはいけない。不整脈の原因になることがあるからだ。緊急時には気管挿管を行ない....しつこいですか?
 抗ヒスタミン剤を服用しつつ説明するが、C言語における.hファイルの感覚でpublic static finalな定数値を定義しまくった、巨大なDefineクラスを作ってはならない。これは有名なアンチパターンであるが、誰もがやってしまいがちなパターンでもある。にもかかわらずDefine.javaが存在すると言うことは、javaに明るくないCプログラマが作ったソースをこれから読む羽目になる、と言うことなので、baristaは必ずアレルギー症状を起こすのである。

 物事を集中管理すると便利なように感じる。しかしそれは大いなる過ちだ。例を挙げよう。今baristaはガンダムのプラモデルを作ろうとしている。プラカラーを買いに行く時には箱の中の説明書を見る。
胸部=インディコブルー
腹部=シャインレッド
それを見て10色程度のプラカラーを買いに行く。実にシンプルだ。さすがはバンダイだ、と納得するわけである。
 ここでパンダイという新規参入のプラモデル会社が登場する(もちろん社章はパンダ印だ)。パンダイでは「いちいちガンダムの色は、って言うとガンダムの説明書を探さなきゃいけないのは面倒でしょ?全作品の全モビルスーツのプロパティを一元管理した方が便利じゃん」ってことでdefine型説明書を作成した。baristaはパンダイ製のガンダムを購入した。RX78-2ガンダム(テレビ版:1/144)だ。箱は8kgあって説明書は8,000ページに渡る。丁寧にコメントがつけてあるので「このあたり胸部関係の色を定義」ってあたりを見ると、
 :
RX78-2ガンダム(テレビ版)1/144胸部=インディコブルー
RX78-2ガンダム(0083モデル)1/144胸部=インディコブルー+マリンブルー
RX78-2ガンダム(ポケットの中の戦争モデル)1/144胸部=ミディアムブルー
RX78-2ガンダム(映画:哀戦士版)1/144胸部=インディコブルー+ネイビーブルー
 :
RX78-2ガンダム(テレビ版)1/60胸部=インディコブルー
RX78-2ガンダム(0083モデル)1/60胸部=インディコブルー+マリンブルー
RX78-2ガンダム(ポケットの中の戦争モデル)1/60胸部=ミディアムブルー
RX78-2ガンダム(映画:哀戦士版)1/60胸部=インディコブルー+ネイビーブルー
 :
RX78-2ガンダム(テレビ版)1/32胸部=インディコブルー
RX78-2ガンダム(テレビ版・後期)1/32胸部=インディコブルー+コバルトブルー
RX78-2ガンダム(0083モデル)1/32胸部=インディコブルー+マリンブルー
RX78-2ガンダム(ポケットの中の戦争モデル)1/32胸部=ミディアムブルー
RX78-2ガンダム(映画:哀戦士版)1/32胸部=インディコブルー+ネイビーブルー
 :
 「凄い、全作品の全モビルスーツの全パーツの色が一望できるぞ!」と最初は感動したものの、半日かかってまだ6色。あと4色探せば...と思っていると、パンダイから郵便が届いた。「define型説明書のバージョンが20.23.12.98にupdateしたから、今後こちらを見てね。」ひょっとしたら間違いがあるかもとbaristaはその後2日かけて新しい方で色を探しなおし、プラカラーを買いに行ったのだった。さすがはパンダイ。
 お判りになっただろうか。define型説明書には、重い、探すのが大変、各プロパティ名が長くなる、メンテナンスを入れると全プラモデルの説明書を差し替えなければならない、などの欠点が存在するのである。やはり説明書は、箱の中で使う分だけの情報を記載しておくべきなのだ。ザクのプラモデルを作っていて、ジオラマ作成の都合上、ガンダムの色が見たい、そういう時にガンダムの説明書を探すのがそんなに面倒だろうか?define型説明書よりはずっと楽なはずである。

 あるアプリケーションが3つのパッケージで出来ていたとして、あるDefineクラスを作って全クラスがそれを参照する。必然的にDefineクラスは3つのパッケージより上位のパッケージになるだろう。するとパッケージ内のクラスは上位モジュールを参照することになる。一般的にオブジェクト指向において、歯車は自分がどこでまわっているのかを知らない設計が望ましい。下位が上位を参照するとが分離度が低くなるからだ。この例だと各パッケージはDefineクラス無しにはコンパイルすることすら出来ない。タイヤが無いと車が作れない、これはあたりまえだ。しかし車がないと作れないタイヤは不良品ではないだろうか?

 定数は最小限の範囲で、つまり関連するオブジェクトに定義されるべきなのである。「どのオブジェクトの持ち物かわからない変数が....」と言う事もあるが、大抵の場合、そもそものオブジェクト分割がおかしい。無論例外は多く存在するが、全てオブジェクト分割が悪いのだと思った方が、勉強にはなるだろう。

 思想による論理展開には必ず反駁がつき物なので、実装上の欠点を挙げて締めくくっておこう。javaのコンパイラは、オプションにもよるが、public static finalな定数を静的リンクしてしまう。判りやすく言うと定数名ではなく、中の値をclassファイルに直接書き込んでしまう。したがって、例を挙げると
public class Define {
    public static final int GREETING = 0;
    public static final int CONFESSION = 1;
}

public class Robot {
    public void talk(int messageID) {
        if (messageID == Define.GREETING) {
            System.out.println("Nice to see you!");
        } else if (messageID == Define.CONFESSION) {
            System.out.println("I love you!");
        }
    }
}

public class User {
    public static void main(String[] argv) {
        new Robot().talk(Define.GREETING);
    }
}
 こういうアプリを作ったとする。Userを実行すると必ずRobotが挨拶をするわけだ。ここでDefineをこんな風に改造する。
public class Define {
    public static final int GREETING = 1;
    public static final int CONFESSION = 0;
}
 Robotクラスだけを再コンパイルして実行すると、常に愛の告白をする羽目になってしまう。Userクラスの中ではtalkに渡す引数は0のままだが、Robotクラスの中ではGREETINGが1, CONFESSIONが0に書き換わったからだ。したがって意識的にUserクラスもコンパイルしなおさないといけない。Defineクラスを作ると、常に全ファイルをコンパイルしなおさないと安心できなくなる。仮にRobotとUserが別の人の開発する別パッケージだった場合、「Defineが新しくなったから二人とも一緒に再コンパイルしようね」と連絡を密にしないと、結合試験が通らなくなってしまうのである。
 このような事態を防ぐ唯一にして最適の方法は、GREETINGとCONFESSIONをif文判定に用いるRobotクラス内に定義することだ。
public class Robot {
    public static final int GREETING = 0;
    public static final int CONFESSION = 1;

    public void talk(int messageID) {
        if (messageID == GREETING) {
            System.out.println("Nice to see you!");
        } else if (messageID == CONFESSION) {
            System.out.println("I love you!");
        }
    }
}
public class User {
    public static void main(String[] argv) {
        new Robot().talk(Robot.GREETING);
    }
}
 こうすれば定数をいつ書き換えても安心である。必ずif文は正しい評価を下す。またロボットクラスは今後Defineクラスの無い所でも生きていける。自由の身になった。

 このようにpublic static finalな定数は、その定数を評価するクラスの中で定義するべきであり、その定数はそのクラスへの引数以外に利用してはいけない、と言うことがコンパイラの仕様から明らかだ。無論、多くの例外は存在するが、少なくとも別packageから参照しあうような設計が愚なのは判っていただけたとも思う。そろそろDefineを使う気力も、長い文章を読む気力も尽きたのではないだろうか。

謝罪&謝辞
 昔あるじゃあのん氏にDefineクラスが何故いけないのかを質問された時に、うまく解答をまとめることが出来ずあやふやな解答をしてしまいました。漸く文書化できたので、ここで謝罪とお礼を述べておきます。こんな感じです、多分。

戻る

Copyright (c) 2004 barista All rights reserved.