アノテーションは難しい

Javaアノテーションはインタフェース扱いでクラスとして扱えない、継承ができない等、Java言語の型としては不完全なため、扱いに苦労する。また、いろいろな書き方ができるため、自己流になりがちだ。
何日か前のエントリでも書いたが、やりがちなのはアノテーションの種類が増えることを嫌がって、複数の論理的な意味を一つのアノテーションに詰め込もうとすることだ。

public @interface Constraint {
    String type() default "String";
    boolean notNull() default false;
    int min() default 0;
    int max() default Integer.MAX_VALUE;
    String regexp();
}

このようにどんどん意味を詰め込んで行きがち(というか自分がそう)だが、これはよほど注意して設計された場合以外はお奨めできない。(フルアノテーション)

アノテーションメタデータでありタグであるため、自己記述的であるべきである。また、アノテーションが保持する属性はできるだけ少ないほうが好ましい。可能であれば0であるべき。(マーカーアノテーション)

ということで、上のバリデーション制約のアノテーションは、今の私ならば以下のように4つに分割する。

public @interface NotNull {
}
public @interface DataType {
    Strig value() default "String";
}
public @interface Length {
    int min() default 0;
    int max() default Integer.MAX_VALUE;
}
public @interface Pattern {
    Strig[] value();
}

最初に分割したNotNullアノテーションはそれを記述したエレメントに対して「非Null」の制約を課す典型的なマーカブル・アノテーションだが、分割したことで属性が不要になった訳だ。
仮にこのアノテーションをフィールドに適用するとすれば、以下のように最も多い場合4つのアノテーションが付加される。

@NotNull
@DataType("String")
@Length(min=1, max=10)
@Pattern("[A-Za-z0-9]")
Object field1;

ちょっとごちゃごちゃして見えるだろうか。
ちなみに元々のConstraintアノテーションではこうなる。

@Constraint(type="String", notNull=true, min=1, max=10, regexp="[A-Za-z0-9]")
Object field1;

一見こちらの方がすっきり見えるのだが、
分割した側もこのように書けばそう変わらない。

@NotNull @DataType("String") @Length(min=1, max=10) @Pattern("[A-Za-z0-9]")
Object field1;

変更に強く保守性に優れているのはもちろん分割した方だ。