DSL / Annotation


DSL

注釈

一言要約

クラスやメソッドといったプログラミングの要素に関するデータ。コンパイルまたは実行時に処理される。

要約

注釈(Annotation)はプログラムの要素に関する情報。実行時またはコンパイル時に操作される。

Javaよりも.NETの方が先に'属性(attribute)'で仕組みを提供した。でも言葉が混同してしまうので、ここではJavaの注釈を使う

仕組み

注釈は内部DSLとして働く。プログラムのランタイムモデルにデータを付け加えてセマンティックモデルを構築し、後続の処理時にセマンティックモデルを実行される。

注釈を定義する

Javaのテストメソッドの例。

@test public void softwareAlwaysWorks()

C#の例。

[Test] public void SoftwareAlwaysWorks()

変数をつけることも可能。

class PatientVisit...
  @ValidRange(lower = 1, upper = 1000)
  private int weight; // in lb
  @ValidRange(lower = 1, upper = 120)
  private int height; // in inches

用意された構文を使うのが最も簡単だが、他にもクラスメソッドを使う方法もある。

valid_range :@height, 1..120
valid_range :@weight, 1..1000

valid_rangeに対応したクラスメソッドが存在する。簡単だが、要素の名前が必要になるのが問題。要素から離れた場所に注釈を定義できてしまう。

実行時に注釈データを格納する方法としてクラス変数に格納する方法があるが、クラス/サブクラスで共通なため問題になるかもしれない。

オブジェクト指向以外でも同じことが出来る。たとえばタグ付けられたlisp構造。

マーカーインターフェースも注釈のひとつの方法。

命名規則も注釈の簡単な方法。XUnitでの'test'で始まるテストメソッド。

注釈は言語に備え付けられた構造の中で処理される。なので、内部DSLと同様に言語の構文の制限を受ける。

注釈の処理

定義された注釈が実行されるのは、コンパイル中、プログラム読み込み時、実行動作中のいずれか。

実行動作中に注釈が実行されるのがもっとも一般的。その中でアスペクトの制御として動作する方法もある。実行動作中の例としてXUnitやデータベースマッピングがある。

プログラムの読み込み時に検証用オブジェクトが生成され、プログラム実行中に検証が行われる。これはDSLの一般的なアプローチと同じ。

コード生成のやり方もある。動的言語ならプログラミング実行時にコード生成される。コンパイル言語の実行中のコード生成もっと複雑。Javaでは注釈用のフックがコンパイラに用意されている。

コンパイル前にコード生成することも出来る。けれども書かれたコードとごちゃまぜになるので分かりにくくなる。

コンパイルで生成されたバイトコードにステップ行を追加する方法もある。

一つの注釈の定義から複数の場所での注釈の実行もできる。たとえば検証の定義を一箇所でして、javascriptとサーバ側の両方で検証を実行できる。

いつ使用すべきか

注釈はまだ広く広まってるわけではなく、発展途上。

重要な特徴は、定義を処理から切り離せること。制約の定義を独立させることでコードを見やすくできる。

対して欠点は、バラバラなので定義と実装を両方理解するには別々に見なければならない。処理フローに依存してはならず、注釈定義は宣言的にすべき。

例:実行時処理でのカスタム構文(Java)

Javaのアノテーションを使った例。

class PatientVisit...
  @ValidRange(lower = 1, upper = 1000)
  private int weight; // in lb
  @ValidRange(lower = 1, upper = 120)
  private int height; // in inches
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidRange {
  int lower() default Integer.MIN_VALUE;
  int upper() default Integer.MAX_VALUE;
}

例:クラスメソッドを利用する(Ruby)

Rubyには注釈の構文を持っていないが、注釈機能は広く使われている。

class PatientVisit...
  valid_range :@height, 1..120
  valid_range :@weight, 1..1000
class DomainObject...
  @@validations = {}

  def self.valid_range name, range
    @@validations[self] ||= [] 
    v = lambda do |obj|
      range.include?(obj.instance_variable_get(name))
    end
    @@validations[self] << v
  end

例:動的なコード生成 (Ruby)

個々のフィールドを検証するメソッドを自動生成する例。

class PatientVisit...
  not_nil :height, :weight
  valid_range :height, 1..120
  valid_range :weight, 1..1000
class DomainObject...
  class << self; attr_accessor :validations; end

  def valid?
    return self.class.validations.all? {|v| v.call(self)}
  end

class FieldValidator
  attr_reader :field_name
  def initialize field_name, &code
    @field_name = field_name
    @code = code
  end
  def call target
    @code.call target
  end

define_methodで動的にメソッドを生成。

class DomainObject...
  def self.define_field_validation_method field_name
    method_name = "valid_#{field_name}?"
    return if self.respond_to? method_name
    self.class_eval do 
      define_method(method_name) do
        return self.class.validations.
          select{|v| v.field_name == field_name}.
          all? {|v| v.call(self)}
      end
    end
  end

ファウラーへのフィードバック


担当者のつぶやき

  • Javaだとアノテーション、.NETだとAttributes、Pythonだとデコレータ。
  • ネタBliki
  • アノテーション(Java)作ってみたい

みんなの突っ込み