谷本 心 in せろ部屋 このページをアンテナに追加 RSSフィード Twitter

2009-03-15

[]考えなしに肥大化する定数クラス。

よく定数クラスというものを見かける。

大体はXxxConstantsという名前で、public static finalなフィールドをたくさん持つクラス。


あるいは、定数クラス自身をinterfaceとして定義しておいて、

値を利用するクラスで、implementsするという手法も見かける。


初めて見た時には、便利な手法だと思ったけど、

その後、ひどい定数クラスを目にすることが少なくなかった。


定数クラスは、疎結合の考え方と全く合わないと僕は思う。


具体的に、悪い例を見ながら話していく。

public class XxxConstants {
	public static final String EOL = "\r\n";
	public static final String ENCODING = "UTF-8";
	public static final int HOGE_X = 480;
	public static final int HOGE_Y = 640;
	public static final int HOGE_INTERVAL = 20;
	public static final int FUGA_X = 40;
	public static final int FUGA_Y = 60;
	public static final int CONFIG_FILE = "/usr/xxx.properties";
	public static final int FOO_OPTION_HORIZONTAL = 1;
	public static final int FOO_OPTION_VERTICAL = 2;
	public static final int FOO_OPTION_LINEAR = 4;
	public static final int FOO_OPTION_DASHED = 8;
	public static final int MSG_ID_001 = "APP001";
	public static final int MSG_ID_002 = "APP002";
	public static final int MSG_ID_003 = "APP003";
}

なにか特別なアプリケーションを意識して書いたわけではないけど、

大体の定数クラスというものには、こんな定数が定義されている。


環境の定数や、ウィンドウのサイズ、ファイル名、形状のオプション、ログコード、

まさにごった煮だが、定数クラスは、得てしてこんな風に肥大化してしまいやすい。


まずこの肥大化が、最初の間違い。

クラスは「一言で表せるもの」ぐらいの粒度で作るべきだけど、

上の例だと「定数を定義するクラス」っていう曖昧な言葉でしか説明できない。

せめて、定数の種類ごとにクラスを分けるなどすることが必要だ。

環境の定数ならEnvironmentクラス、ログコードならLogCodeクラス、という風に。


定数クラスを作ってしまうと、その配下の多くのクラスは、定数クラスに依存する。

機能分割ができそうなのに、定数クラスに依存してしまうせいで、JARを分けられないこともある。

だから、機能や目的ごとに定数クラスを分割しておくべきだ。


ちなみにログコードを「MSG_ID_001 = "APP001"」みたいに定義するのは最悪だ。

利用する側では、XxxLogger.log(MSG_ID_001) となるが、何をロギングするのか全く分からない。

せめて、定数名を「MSG_USER_NOT_FOUND」とか「MSG_ID_001_USER_NOT_FOUND」など、

意味が推測できるものにした方が良い。


あと、たまに、特定の1クラス(仮にClassAとする)からしか使わない値も

定数クラスに定義することがあるけど、そんな場合、

大体は、ClassA自身の定数にしておいが方が、疎結合になって使いやすい。


「定数クラスとしてまとまっていた方が分かりやすい」っていう意見もあるけど、

だからといって全てをまとめるのは、設計を放棄していることに他ならない。


変更の頻度、変更のライフサイクル(稼動中か再起動時か)、変更する人は誰か、

をしっかり考えてから、設定ファイルか定数クラスか、各クラスに定義するかを

よく考えるべきだと思う。


次に、オプション。これはよく使われる手法で、SWTなんかでも、

Table table = new Table(shell,SWT.MULTI|SWT.FULL_SELECTION|SWT.BORDER);

こんな風に書いたりする。

これはこれで便利だと思ったんだけど、どのオプションが有効なのか分からず、そこで手が止まる。

某商用プロダクトでは、こんなオプションが数百個あって、

どれが実際に使えるのかを理解するために、随分と時間を掛けてしまった。


対策としては、Java5以降ならEnumと可変長引数を利用した方が良い。

要するに、コンストラクタの定義を、↓のようにする。

public Table(Composite shell, TableOptions... options) {
	// コンストラクタの処理
}

ここでTableOptionsとは、Tableのオプションを示すEnum

こうしておけば、開発環境の自動補完だけでサクサクと開発ができる。

まぁEnumはそのためにあるんだから、当たり前か。


では、Java1.4以前ならどうするか。

せめて、クラスに対応した定数クラスを作るべきだと思う。


コンストラクタは、変わらないままだけど、

public Table(Composite shell, int options) {
	// コンストラクタの処理
}

引数にはSWTという定数クラスを使わせるんじゃなくて、

Tableクラスの定数か、TableOptionsクラスの定数を使わせる

そうすることで、少なくとも利用できるオプションが明確になる。


もちろん、自前でEnum風なことをしても良い、

public Table(Composite shell, TableOptions[] options) {
	// コンストラクタの処理
}

けど、さすがにこれはちょっとヤリスギだと思うし、やったことはない。


最後に、定数クラスをinterfaceにするのは、やめて欲しい。

public static finalで意味は十分に通じるし、

インスタンスを作られたくないなら、privateコンストラクタを書けば良い。


interfaceでないものを、interfaceとして利用するのは、

余計な混乱を生むし、若手に対する教育としても決して良いものではないと思う。


つらつら書いてきたけど、要するに、僕は定数クラスが嫌いだってこと。

定数クラスを見かけたら、何とかして削除できないかを考えるようにしている。

nowokaynowokay 2009/03/19 03:11 定数クラスに環境設定は書かないほうがいいですねぇ。
識別子としての文字列定数は集めたりします。
import staticできる今となっては、interfaceにしてimplementsするというのは、全く不要ですね。

cero-tcero-t 2009/03/19 12:25 確かに、環境変数はそうですね。設定ファイルに書けと。
あと僕もLogCodeやErrorCodeは定数クラスにしています。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証