アノテーションは難しい
Javaのアノテーションはインタフェース扱いでクラスとして扱えない、継承ができない等、Java言語の型としては不完全なため、扱いに苦労する。また、いろいろな書き方ができるため、自己流になりがちだ。
何日か前のエントリでも書いたが、やりがちなのはアノテーションの種類が増えることを嫌がって、複数の論理的な意味を一つのアノテーションに詰め込もうとすることだ。
-
- Constraint.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;
変更に強く保守性に優れているのはもちろん分割した方だ。