谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

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

よく定数クラスというものを見かける。
大体は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として利用するのは、
余計な混乱を生むし、若手に対する教育としても決して良いものではないと思う。


つらつら書いてきたけど、要するに、僕は定数クラスが嫌いだってこと。
定数クラスを見かけたら、何とかして削除できないかを考えるようにしている。