言語が思考を制限する
Javaのアノテーションは元々インタフェースの糖衣構文であり、例えば@Hogeというアノテーションは以下のようなJavaソースコードとしてコンパイルされる。
interface Hoge extends Annotation {}
であればアノテーションも継承により型階層を構成できれば良いというか、それが自然だと思うのだがそうはなっておらず、extendsが書けない。
@interface Hogehoge extends Hoge × コンパイルエラー
{}
アノテーション同士の親子関係を意識させることは殆ど無いため普通は問題にならないんだが、以下のようにある型activityTypeからBindアノテーションを収集するとしよう。
Annotation[] bindAnnos = FinderUtil.findAll(activityType.getAnnotations(), ※ new IPredicate() { @Override public boolean evaluate(Annotation input) { return (input instanceof Bind) }});
次に、Bindと似ているが少し違う、論理的には階層構造にある10種類のアノテーションを収集しようとする。インタフェースのように継承が使えるのであれば上のコードとなんら変わりないのだが、アノテーションは継承関係を記述できないので実際には
Annotation[] bindAnnos = FinderUtil.findAll(activityType.getAnnotations(), ※1 new IPredicate() { @Override public boolean evaluate(Annotation input) { return (input instanceof Bind) || (input instanceof Bind2) || (input instanceof Bind3) || (input instanceof Bind4) : × 10繰り返し }});
以降、収集のアノテーションの種類が増える度にこのコードの判定も増える。こんなの嫌だがどうしようも無い。
このようにしか書けない記述が増えてくるとプログラマはどうするか? 全てがそうだとは言わないが、上で言うとBindアノテーションにBind2〜の細かい違いを全て含むBind"大"アノテーションを作ってしまうのだ。
public @interface Bind { String name() default ""; } public @interface Bind2 { String name2() default ""; } public @interface Bind3 { String name3() default ""; } : : 面倒なのでまとめる ↓ public @interface MegaBind { String name() default ""; String name2() default ""; String name3() default ""; : }
普通は忌避されるべき設計をしているが、これは言語の制限が実装だけでなく設計にまで影響してしまう例だ。
新しい機能を実装するのに安易に糖衣構文を使ってはいけないという見本、それがJavaのアノテーションだ。※2
※1: 述語論理IPredicateを使って条件に合致したものだけを抜き出すファインダ、本日記中で何度か採りあげている。
※2: 逆に拡張for文などは糖衣構文でも良いと思う。