DSL / Embedment Helper


DSL

埋め込みヘルパー

一言要約

テンプレートから実行される複雑なコードを実行するためのヘルパー

要約

  • 多くのテンプレートシステムでは、汎用コードを実行するための機能拡張を有している。
  • このことにより、埋め込みヘルパーに表現に関する多くの機能を追加でき、テンプレートの表現をシンプルに保津ことができる。
  • ただし、外部コードが密接に関係し、埋め込まれる表現を不明瞭にする結果ともなりうる。

どのように働くか

  • 「埋め込みヘルパー」の基本的な考えは、リファクタリング
  • 「埋め込みヘルパー」を作り、「埋め込みヘルパー」をホスト表現から見えるようにし、ホスト表現の全てのコードを「埋め込みヘルパー」に移動し、後にはメソッド呼出のみを残す。
  • 問題提起:「埋め込みヘルパー」中のどのコードが実行されているかを明確にするにはどうしたら良いか。
  • 回答:抽象化において、これに対する鍵となるのは、呼出に注意深く名前を付けること     それにより、その実装を明らかにすることなく、呼び出されたコードの意図を明確に述べることになる。    (担当者の補足:要は共通関数とかライブラリ作るのと一緒と認識しました)
  • テーマ:「埋め込みヘルパー」が出力を生成すべきであるか
  • しばしば、「ヘルパーは出力を生成してはならない。 」と聞くことがあり、これは確かにそのような出力はテンプレートから可視ではないため問題である。
  • テンプレートに出力を残すことによる複雑性とのトレード関係であるはず、
  • 私は「埋め込みヘルパー」からの生成を避けることは良いことであるとは述べるが、常にこれが他の選択肢より優れているとは同意したくない。

    (担当者の意見:ヘルパー出力は基本的にやめるべき。副作用のあるコードを書かないように共通ライブラリなどで設計するのと一緒に思います)

いつ使用するか

  • 「埋め込みヘルパー」は、本当に些細な場合以外、常に使用を奨める。
  • 「埋め込みヘルパー」が存在するコードはテンプレート表現は見にくいため、代替表現を使用する目的が全く無駄になっている。  (例)文法ファイルはアクションの中に多くの外部コードを持つが、そのために文法の基本的な流れが見にくくなっている。
  • その他、投稿開発環境の色づけ機能はメリット

引用:「埋め込みヘルパー」を必要としない状況が1つある。これは、この種類の情報それ自体を与えるための自然なホームとして動作するクラスを使用している場合である。これの例は、セマンティック(意味論)モデルを用いて「テンプレートからの生成」を実行している場合である。この場合、「埋め込みヘルパー」に期待する挙動の大部分は、合理的に意味論モデル自体の一部になることができる。但し、意味論モデルがあまり複雑にならないようにすればだが。   (担当者の意見:それはないと思う。埋め込みヘルパーはプレゼンテーションを扱うロジックが含まれる。「合理的に意味論モデル自体の一部」とできるはずがない。)


例:秘密のパネル状態(Secret Panel States) (Java と Antlr)

  • 埋め込みヘルパーなし
machine   : eventList resetEventList commandList state*;
eventList : 'events' event* 'end';

event     : name=ID code=ID {
                             events.put($name.getText(), 
                                        new Event($name.getText(), $code.getText()));
                           };

state : 'state' name=ID {
                          obtainState($name);
                          if (null == machine)
                            machine = new  StateMachine(states.get($name.getText()));
            }
       actionList[$name]? 
       transition[$name]* 
       'end';

transition [Token sourceState]
 : trigger = ID '=>' target = ID 
{
  states.get($sourceState.getText()).addTransition(
    events.get($trigger.getText()), 
    obtainState($target)
  );
};
  • 埋め込みヘルパーあり
machine :  eventList resetEventList commandList state*;

eventList : 'events' event* 'end';

event : name=ID code=ID {helper.addEvent($name, $code);};

state : 'state' name=ID {helper.addState($name);}
        actionList[$name]? 
        transition[$name]* 
        'end';

transition [Token sourceState]
  : trigger = ID '=>' target = ID {helper.addTransition($sourceState, $trigger, $target);};

違いは、コードをヘルパーに移動していること。

こうするためにヘルパーオブジェクトを生成されたパーサーに入れなければならない。 Antlrは、メンバーセクションにフィールドを宣言することにより、これを実行してくれる。

@members {
StateMachineLoader helper;
//...
  • ローダークラス
class StateMachineLoader...
  private Reader input;
  private StateMachine machine;
 
  public StateMachineLoader(Reader input) {
    this.input = input;
  }
class StateMachineLoader...
  public StateMachine run() {
    try {
      stateMachineLexer lexer = new stateMachineLexer(new
ANTLRReaderStream(input));
     stateMachineParser parser = new stateMachineParser(new CommonTokenStream(lexer));
      parser.helper = this;
      parser.machine();
      machine.addResetEvents(resetEvents.toArray(new Event[0]));
      return machine;
    } catch (IOException e) {
      throw new RuntimeException(e);
    } catch (RecognitionException e) {
      throw new RuntimeException(e);
    }
  }

antlrパースは、parser.machineの行で起動される。その前の行でヘルパーを設定していることが分かるだろう。この場合、ローダークラスもヘルパーとして動作する。ローダーは非常に単純なため、別々のクラスとしてそれらを試して作ろうとするより、ヘルパー動作をローダーに加える方が良いようである。

(担当者の意見:ローダが簡単なのはわかるが、ローダとヘルパーを一緒にすることそれ自体に理由はないのでは?役割分担から単純であっても分離したほうがよいと思う)

  • 様々な呼出をハンドルするために、ヘルパー上にメソッドをおいた。
class StateMachineLoader...
  void addEvent(Token name, Token code) {
    events.put(name.getText(), new Event(name.getText(), code.getText()));
  }

「埋め込みヘルパー」にイベント指向の名前付けか、あるいはコマンド指向の名前付けを使用すべきか。

  • イベント指向の名前付けではeventRecognized、stateNameRecognized?等となる。
  • イベント指向の名前付けについての賛成点となるのは、それがヘルパーのアクションを意味しておらず、何をするのかの決定をヘルパーに委ねていること

(担当者のファウラーへの意見:「しばしば、「ヘルパーは出力を生成してはならない。 」と聞くことがあり、これは確かにそのような出力はテンプレートから可視ではないため問題である」との話があったはず、テンプレートからの可視化を阻害することは問題であったはずなのに、この件について賛成できるようになったのはなぜなのか説明してほしい。)

  • これは、同じパーサーに別のヘルパー - パースに対する応答において別のことを実行する - を使用する場合、特に便利である。

例:ヘルパーはHTMLを生成すべきか?(JavaとVelocity)

  • 私が聞いた一般規則は、「埋め込みヘルパー」は出力を一切、生成すべきではないというものだった。
  • これが有用な規則だと思わないが、例を示すことは、もう少しトレードオフを検討する良いやり方だと感じた。

Personオブジェクトのコレクションを持っており、無秩序に並んだリストからそれらの名前を印刷したい場合を考えてみる。

それぞれの人は電子メールアドレスかURLを持っているかもしれない。 もし、URLを持っているなら、名前にURLを指し示すリンクが欲しく、 電子メールアドレスならmailtoリンクが欲しいが、どちらも無ければリンクはない。

これには多くのロジックが含まれてしまっている。

一方、出力の生成に「埋め込みヘルパー」に出力を持たせた場合。

<ul>
#foreach($person in $book.getPeople())
 <li>$helper.render($person)</li>
#end
</ul>


class PageHelper...
  public boolean hasLink(Person person) {
     return (null != person.getEmail()) || (null != person.getUrl());
   }
   public String getHref(Person person) {
     if (null != person.getUrl()) return person.getUrl().toString();
     if (null != person.getEmail()) return "mailto:" + person.getEmail().toString();
     throw new IllegalArgumentException("Person has no link");
 }

ここでの要点は、出力生成のいくらかを「埋め込みヘルパー」に入れることは、合理的な選択であるということである。

(担当者の意見:このファウラーの言う中間地点は、個人的には納得のコード。あまりに納得しすぎてしまって、   どうして「出力生成のいくらかを「埋め込みヘルパー」に入れることは、合理的な選択である」のかということがわかりませんでした。)

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

担当者のつぶやき

  • 私は一般的意見に賛成です。 [#a8384bc0]
  • 寝不足で、指摘内容に余裕がなく厳しめです。 [#r25a852a]

みんなの突っ込み