テンプレートシステム入門 (2) 基礎編

はじめに

テンプレートシステムは、動的に HTML ページを生成する方法のひとつです*1。 具体的には、テンプレートとなる HTML ファイルをメインプログラムから読み込み、必要な箇所を書き換えて出力するという仕組みのことです。 またこれを実現してくれる具体的なライブラリをテンプレートエンジンといいます。

前回は、動的に HTML ページを生成する方法について、テンプレートシステムも含めて 3 つ紹介しました。 またそれぞれの方法において、テキスト汎用型と HTML 専用型の 2 つのタイプがあることも紹介しました。

今回は、テンプレートシステムについて詳しく見ていきます。 具体的には、次のことについて説明します。

  • ビジネス層とプレゼンテーション層について
  • テンプレートシステムの分類について
  • デザイナーとの協業について

なお本稿の対象読者は、Web アプリケーション開発に興味のある方です。 特に、テンプレートシステムを自作しようと考えている方や、Web デザイナーとの協業を考えておられる方には、とりわけ参考になると思います。

ビジネス層とプレゼンテーション層

何らかの表示を伴うアプリケーションは、大きく「ビジネス層」と「プレゼンテーション層」の 2 つに分けることができます*2

ビジネス層
何 (What) を表示するか? を司る層であり、メインプログラムが担当します。
プレゼンテーション層
どう (How) 表示するか? を司る層であり、テンプレートシステムが担当します。

仕組みとしては、ビジネス層であるメインプログラムがデータを用意し、それをプレゼンテーション層であるテンプレートシステムに渡すことでデータが表示されます (図 1)。

図 1. ビジネス層とプレゼンテーション層

template-fig1.png

またビジネス層とプレゼンテーション層それぞれに、ロジックとデータが必要です (表 1)。 これらはそれぞれ、ビジネスロジック、ビジネスデータ、プレゼンテーションロジック、プレゼンテーションデータといいます。

表 1: ビジネス層とプレゼンテーション層のそれぞれにロジックとデータがある

ビジネス層 プレゼンテーション層
ロジック ビジネスロジック プレゼンテーションロジック
データ ビジネスデータ プレゼンテーションデータ

たとえば、「営業部員の売り上げ成績から上位 20 名を選出し、奇数行と偶数行で背景色を変えて <table> タグで表示する」というアプリケーションを考えると、これは次のように分解できます。

  • 「営業部員の売り上げ成績」… ビジネスデータ
  • 「上位 20 名を選出する」… ビジネスロジック
  • 「奇数行と偶数行で背景色を変える」… プレゼンテーションロジック
  • 「(使用する) 背景色」と「<table> タグ」… プレゼンテーションデータ

ここで大事なのは、プレゼンテーション層にもロジックが必要であるということです。 この例でいうと、「奇数行と偶数行で背景色を変える」というのは表示のためだけに必要なロジックであり、明らかにプレゼンテーション層に含まれます。 これをビジネス層であるメインプログラムに含めてはいけません。

よく「ロジックとプレゼンテーションを分離する」という人がいますが、これは誤りです。 なぜなら、プレゼンテーション層にもロジックが含まれるからです。 「ビジネス層とプレゼンテーション層を分離する」や「プレゼンテーション層をプレゼンテーションロジックとプレゼンテーションデータに分離する」というなら正しいですが、「ロジックとプレゼンテーションを分離する」は明らかに間違いですので注意してください。

プレゼンテーションロジックをどこに記述するか?

プレゼンテーション層が、プレゼンテーションロジックとプレゼンテーションデータに分けられることはわかりました。 また HTML がプレゼンテーションデータであることは明白です。 では、プレゼンテーションロジックはどこに記述すべきでしょうか。

実は、これこそがテンプレートシステムにおける重要ポイントです。 プレゼンテーションロジックをどこに記述するかで、テンプレートの HTML デザインが崩れるか否か、またデザイナーとプログラマーとが協業しやすいかどうかが決まります

プレゼンテーションロジックを記述する場所は、3 つ考えられます (図 2)。

  • HTML テンプレートの中
  • メインプログラムの中
  • 独立した別ファイルの中

図 2. プレゼンテーションロジックを記述する場所

template-fig2.png

これらを詳しく見ていきます。

HTML テンプレート中に記述する

現在ほとんどのテンプレートエンジンは、プレゼンテーションロジックを HTML テンプレート中に記述しています。例を挙げると、Ruby でいえば eRuby、TempuraTenjin など、Ruby 以外でいえば Velocity(Java)、Smarty(PHP)、Template-Toolkit(Perl)、Zope PageTemplate(Python) などが挙げられます。

プレゼンテーションロジックを HTML テンプレートの中に記述するのは、方法としては分かりやすいのですが、HTML の中に HTML ではない要素 (= プレゼンテーションロジック) が混じるため、記法を工夫しないと HTML デザインが崩れてしまいます*3。 また Web デザイナーと協業する場合、デザイナーとプログラマーが同じファイルを編集するため、同時作業がしづらく、またデザイナーが誤ってロジックを変更してしまう危険性もあり、協業するには都合が悪いです。

例として、Tenjin のサンプルをリスト 1 と リスト 2 に掲載します。 ビジネス層 (メインプログラム) とプレゼンテーション層 (テンプレート) はきれいに分離していますが、テンプレートの中にプレゼンテーションデータとプレゼンテーションロジックが混在していることが分かります。 そのため、デザイナーが誤ってプレゼンテーションロジックを変更してしまう可能性は十分にあります。

また Tenjin のように処理命令を使ったり、TempuraZope PageTemplate のようにタグの属性に記述する場合は HTML デザインが崩れませんが、たとえば eRuby のように「<% %>」を使ったり、Velocity のように独自記法を採用している場合は、テンプレートの HTML デザインが崩れてしまうため、デザイナーには不評です。

リスト 1: ex-tenjin.rbhtml (テンプレート)

<table>
<?rb odd = false ?>
<?rb for item in @list ?>
<?rb   odd = !odd ?>
<?rb   klass = odd ? 'odd' : 'even' ?>
  <tr class="#{klass}">
    <td>${item}</td>
  </tr>
<?rb end ?>
</table>

リスト 2: ex-tenjin.rb (メインプログラム)

require 'tenjin'

## 表示したいデータを用意する
list = ['<AAA>', 'B&B', '"CCC"']

## データをプレゼンテーション層に渡して表示する
engine = Tenjin::Engine.new
output = engine.render('ex-tenjin.rbhtml', :list=>list)
print output

メインプログラムの中に記述する

プレゼンテーションロジックをメインプログラム中に記述することで、HTML テンプレートに余計なものが含まれないようにしたテンプレートシステムがあります。例を挙げると、Ruby なら Amrita2misen、Ruby 以外だと Enhydra XMLC(Java) や Wicket*4(Java) が挙げられます。

プレゼンテーションロジックをメインプログラムの中に書く方法は、HTML テンプレートの中に余計なものが混じらないので HTML デザインが崩れません。 またデザイナーが編集するファイル (= HTML テンプレート) と、プログラマーが編集するファイル (= メインプログラム) が別になるので、同時作業がしやすく、デザイナーがロジックを触ることができないので、デザイナーと協業する上でも好都合です。

しかし、本来プレゼンテーション層に含まれるべきプレゼンテーションロジックが、ビジネス層を担当するメインプログラムに現れるわけですから、ビジネス層とプレゼンテーション層が分離できません。 アプリケーションのアーキテクチャとしては良くないです。

例として、Amrita2 のサンプルをリスト 3 とリスト 4 に掲載します。 テンプレート中に HTML 以外の要素 (= プレゼンテーションロジック) が現れておらず、テンプレートのデザインがまったく崩れないことがわかります。

しかしプレゼンテーションロジックがメインプログラム中に現れるため、ビジネス層とプレゼンテーション層とが分離できていないこともわかります (Amrita2 ではロジックをデータで表現するため、Amrita2 用にデータを用意することがプレゼンテーションロジックの記述になります)。

リスト 3: ex-amrita2.html (テンプレート)

<table>
  <tr id="list" class="odd">
    <td id="item">ITEM</td>
  </tr>
  <tr id="dummy" class="even">
    <td>ITEM</td>
  </tr>
</table>

リスト 4: ex-amrita2.rb (メインプログラム)

require 'rubygems'
require 'amrita2/template'
include Amrita2

## 表示したいデータ (Amrita2 用に加工済み) を用意する
list = [
  a(:class=>'odd')  { {:item=>'<AAA>'} },
  a(:class=>'even') { {:item=>'B&B'  } },
  a(:class=>'odd')  { {:item=>'"CCC"'} },
]
context = { :list=>list }

## テンプレートを読み込んで表示する
template = TemplateFile.new('ex-amrita2.html')
template.expand(output='', context)
print output

ただこのタイプの場合、プレゼンテーションロジックをメインプログラムから追い出して別ファイルにすることはさほど難しくないため、あまり問題とはならないようです。 たとえば Amrita2 の場合、リスト 5 と リスト 6 のようにすれば、メインプログラムとプレゼンテーションロジックとを分離することができます。

リスト 5: ex-amrita2-view.rb (プレゼンテーションロジック)

require 'rubygems'
require 'amrita2/template'
include Amrita2

def ex_amrita2_view(list)
  ## Amrita2 用にデータを加工する
  arr = []
  list.each_with_index do |item, i|
    klass = i % 2 == 0 ? 'odd' : 'even'
    arr << a(:class=>klass) { {:item=>item} }
  end
  context = { :list=>arr }
  ## テンプレートを読み込んで表示する
  template = TemplateFile.new("ex-amrita2.html")
  template.expand(output='', context)
  return output
end

リスト 6: ex-amrita2-main.rb (メインプログラム)

## 表示したいデータを用意する
list = ['<AAA>', 'B&B', '"CCC"']

## データをプレゼンテーション層に渡して表示する
require 'ex-amrita2-view'
output = ex_amrita2_view(list)
print output

なお筆者の印象だと、このタイプのテンプレートシステムはどれも、複雑なプレゼンテーションロジックが記述しにくい (または分かりづらい) です。

独立したファイルに記述する

プレゼンテーションロジックをテンプレート中に記述するのも、またメインプログラム中に記述するのも、どちらも問題があることが分かりました。 ではどうすればいいのでしょうか。

いちばん理想的なのは、プレゼンテーションロジックだけを別ファイルにすることです。 つまり、プレゼンテーション層を「HTML テンプレートファイル」と「プレゼンテーションロジックファイル」の 2 つに分離するわけです。

これによる利点は次の通りです。

  • テンプレート中に HTML 以外の要素 (= プレゼンテーションロジック) が現れないため、テンプレートのデザインがまったく崩れません。
  • デザイナーとプログラマーとで編集するファイルが別なため、同時作業がしやすく、またデザイナーが誤ってロジックを変更してしまうこともありません。
  • テンプレートを変更しても、それがメインプログラムやプレゼンテーションロジックに影響を与えませんし、その逆も成り立ちます。
  • ビジネス層 (メインプログラム) とプレゼンテーション層 (テンプレート + プレゼンテーションロジック) がきれいに分離できます。

このように、今まであった問題点がきれいに解決されていることがわかります。 また、特にデザイナーとプログラマーとの協業に向いていることも分かります。 欠点があるとすれば、ファイルの数が増えるので管理の手間が少し増えることぐらいでしょうか。

このタイプの例として、Kwartz のサンプルをリスト 7、8、9 に掲載します。 これをみると、テンプレートの HTML デザインがまったく崩れないことと、ビジネス層であるメインプログラムとプレゼンテーション層とがきれいに分離できていることがわかります。

Kwartz では、プレゼンテーションロジックをあたかも CSS のように記述します。 ちょうど、CSS によって HTML からデザインに関する事項を別ファイルに分離したように、Kwartz を使うと HTML からプレゼンテーションロジックを別ファイルに分離できます。 しかも、Kwartz はHTML 専用型のように見えて実はテキスト汎用型であり、HTML や XML 以外でも使用できます。

リスト 7 : ex-kwartz.html (テンプレート)

<table>
  <tr id="mark:list" class="odd">
    <td id="mark:item">ITEM</td>
  </tr>
  <tr id="dummy:d1" class="even">
    <td>ITEM</td>
  </tr>
</table>

リスト 8 : ex-kwartz.plogic (プレゼンテーションロジック)

/* id="mark:list" (または id="list") がついた要素 */
#list {
  /* class 属性の値として変数 klass の値を使う */
  attrs: 'class' klass;
  /* 要素全体を繰り返す */
  logic: {
    odd = false
    for item in list
      odd = !odd
      klass = odd ? 'odd' : 'even'
      _stag      # 開始タグ
      _cont      # 内容
      _etag      # 終了タグ
    end
  }
}

/* id="mark:item" (または id="item") がついた要素 */
#item {
  /* 内容のかわりに変数 item の値を HTML エスケープして表示する */
  Value: item;
}

/* id="dummy:xxx" がついた要素は自動的に削除される */

リスト 9 : (メインプログラム)

## あらかじめコマンドラインで
## $ kwartz -l eruby -p ex-kwartz.plogic ex-kwartz.html > ex-kwartz.rhtml
## を実行しておく

## 表示するデータを用意する
list = ['<AAA>', 'B&B', '"CCC"']

## テンプレートを読み込んで表示する
def create_binding(list)
  return binding()
end
require 'erb'
include ERB::Util
filename = 'ex-kwartz.rhtml'
eruby = ERB.new(File.read(filename), nil, trim_mode=1)
print eruby.result(create_binding(list))

このタイプのテンプレートシステムは数が少ないですが、Ruby なら KwartzCGIKit、PHP なら Kwartz-php、Java なら Tapestry(Java)、Mayaa などがあります。

テンプレートシステムの分類

テンプレートシステムの特徴を考えるうえで、それらを分類してみるのはいい方法です。 分類方法はいくつも考えられますが、その中から筆者が重要だと思う分類方法を紹介します。

プレゼンテーションロジックを書く場所による分類

前のセクションで述べたように、プレゼンテーションロジックをどこに書くかによって、テンプレートシステムは次の 3 つに分類できます。

  • HTML テンプレート (Tempura, Tenjin, Velocity, Smarty, Template-Toolkit, Zope PagetTemplate, Kid など)
  • メインプログラム (Amrita, XMLC, Wicket など)
  • 独立したファイル (CGIKit, Tapestry, Mayaa, Kwartz など)

前のセクションと重複しますが、それぞれの特徴を以下にまとめます。

  • プレゼンテーションロジックをテンプレート中に書くタイプは、HTML 中に余計なものが混じるうえにデザイナーとプログラマーとが同じファイルを編集することになるため、協業するにはあまり向きません。だだ仕組みとしては分かりやすく学習コストも低いので、協業する必要がなければこのタイプでも問題ありません。
  • プレゼンテーションロジックをメインプログラムに書くタイプは、HTML に余計なものが混じることがなく、またデザイナーとプログラマーとで編集するファイルが異なるため、協業するには大変便利です。ビジネス層とプレゼンテーション層の分離という点では問題がありますが、プレゼンテーションロジックをメインプログラムから追い出すのは難しくないため、深刻な問題とはならないでしょう。それよりも、テンプレート中に書くタイプと比べて仕組みがわかりづらく、学習コストが大きい点のほうが問題となるでしょう。
  • プレゼンテーションロジックを独立したファイルに書くタイプは、デザイナーとプログラマーとが協業する上でも、またビジネス層とプレゼンテーション層の分離という点からも、いちばん理想的です。仕組みがわかりづらく学習コストがかかる点は、メインプログラムに書くタイプと同じです。このタイプの先駆けとなったのは WebObjects であり、WebObjects がコンポーネント指向であったためか、このタイプにはコンポーネント指向のフレームワークが多いです。

対象とする形式による分類

テンプレートシステムはまた、どんな形式を対象とするかによって、大きく以下の 2 種類に分類できます。

  • テキスト汎用型 (Tenjin, Kwartz, Velocity, Smarty, Template-Toolkit など)
  • HTML 専用型 (Tempura, Amrita, XMLC, Mayaa, Zope PageTemplate, Kid など)

両者の特徴は互いに対の関係になっており、一方の利点は他方の欠点となっています。 以下にそれを紹介します。

  • テキスト汎用型は、テンプレート中に書く「目印」の記法が独自形式であることが多く、そのためテンプレートの HTML デザインが崩れます (本当は HTML のコメントや処理命令を「目印」として使ったり、Kwartz のように HTML タグを認識する独自のパーサを使うことで HTML デザインを崩さないようにできるのですが、そこまで考慮されたものは少ないです)。また HTML パーサや DOM ツリーを使わないので、HTML 専用型と比べて高速かつ軽量です。
  • HTML 専用型では、「目印」としてタグや属性を使うため、テンプレートの HTML デザインが崩れません。Amrita2 や XMLC のように id 属性で目印をつけるだけのものと、Tempura や Zope PageTemplate のように独自属性を使ってロジックを記述するものに分かれます*5。また foreach ~ end や if ~ end のようなブロックは、HTML タグの構造をそのまま使います。内部で HTML パーサや DOM ツリーを使うため、一般的にはテキスト汎用型と比べて遅くかつ重いです。

実際のテンプレートシステムの分類

以上のことから、テンプレートシステムは、

  • プレゼンテーションロジックを書く場所 (テンプレートか、メインプログラムか、独立したファイルか) で 3 種類
  • 対象とする形式 (テキスト汎用型か、HTML 専用型か) で 2 種類

に分類できることが分かりました。 実際にはこれらの組み合わせになるので、3 * 2 = 6 種類に分類できます。

表 2 に、いくつかのテンプレートシステムを分類してみました。 テンプレートエンジンを選ぶときの参考にしてください。

表 2 : テンプレートシステムの分類

プレゼンテーションロジックを書く場所 テキスト汎用型 HTML 専用型
テンプレート Tenjin, Velocity, Smarty, Template-Toolkit, Cheetah Tempura, Zope PageTemplate, Kid, Genshi
メインプログラム - Amrita2, XMLC, Wicket
独立した別ファイル Kwartz CGIKit, Tapestry*6, Mayaa

 

デザイナーとプログラマーが協業するために必要なこと

テンプレートシステム導入の効果として、デザイナーとプログラマーとの協業がしやすくなることが挙げられます。 しかし、実際にはうまく協業できている例はほとんどありません。

このセクションでは、デザイナーとプログラマーが協業するために何が必要か、筆者の見解を述べてみます。

技術的な観点から

デザイナーとプログラマーが本当の意味で協業するためには、HTML テンプレート中にプレゼンテーションロジックを埋め込まないことが重要です。

一般的に、デザイナーとプログラマーが協業するためには HTML テンプレートのデザインが崩れないことが大事だと言われます。 しかし筆者は、HTML テンプレートのデザインが崩れないことは協業のための必要条件であって十分条件ではないと考えています。 それよりも、HTML テンプレート中にプレゼンテーションロジックを埋め込まないことのほうが重要です。

リスト 10 をご覧下さい。 これは HTML デザインを崩さないことを特徴とする Kid という Python 用テンプレートエンジンのサンプルです。 これを見ると、プレゼンテーションロジックを属性に記述しているため、HTML デザインが崩れないことがわかります。

リスト 10 : Kid のテンプレート例 (HTML デザインが崩れない)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
      xmlns:py="http://purl.org/kid/ns#">
 <body>
  <h1>Stock Prices</h1>
  <table>
   <tbody>
    <tr {{*py:for="i, item in enumerate(items)"*}}
        class="{{*${i % 2 == 1 and 'even' or 'odd'}*}}">
     <td style="text-align: center" {{*py:content="i + 1"*}}>1</td>
     <td>
      <a href="{{*${item.url}*}}" {{*py:content="item.name"*}}>AAPL</a>
     </td>
     <td>
      <strong {{*py:content="item.price"*}}>64.23</strong>
     </td>
<span {{*py:if="item.change &lt; 0"*}} {{*py:strip=""*}}>
     <td class="minus" {{*py:content="item.change"*}}>-2.20</td>
</span>
<span {{*py:if="item.change &gt;= 0"*}} {{*py:strip=""*}}>
     <td {{*py:content="item.change"*}}>2.20</td>
</span>
    </tr>
   </tbody>
  </table>
 </body>
</html>

しかしこれだと、確かに HTML デザインは崩れていませんが、テンプレート中にプレゼンテーションロジックが埋め込まれているため、次のような問題が発生します。

  • デザイナーがプレゼンテーションロジックにアクセスできる。
    • → デザイナーがプレゼンテーションロジックを誤って変更したり、間違ったロジックを埋め込んでしまう可能性がある。
  • プレゼンテーションロジックを変更するには、HTML テンプレートを修正しなくてはいけない。
    • → プログラマーとデザイナーとが同じファイルを編集するため、競合が発生する*7

大事なのは、これらの問題が、HTML テンプレートのデザインが崩れる崩れないに関わらず、プレゼンテーションロジックをテンプレートに埋め込むタイプのものであれば必ず発生するということです。

これらの問題を解決するには、プレゼンテーションロジックを HTML テンプレートから分離して別ファイルにするのがいい方法です。 つまり、Amrita2 のようにプレゼンテーションロジックをメインプログラムに書くタイプか、Kwartz のように完全な別ファイルに分離するタイプでないと、本当の意味での「プログラマーとデザイナーとの協業」は実現できません。

技術以外の観点から

ただし、以上のことはあくまで技術的な観点からの見解です。 実際にデザイナーと協業するうえで本当に大事なのは、デザイナーに対するリスペクトではないでしょうか。

本来であれば、デザイナーと協業しやすい仕組みを用意するのはプログラマーの責任です。 しかしその責任を果たさないくせに、デザイナーに「この書き方で書け」「ロジックぐらい書けるようになれ」と押しつける傲慢なプログラマーのなんと多いことでしょう。 ひどいのになると、「デザイナーって、HTML と絵を書いてるだけでしょ? あんなの誰でもできるよ」と言い出すプログラマーやプロマネもいます。

これはとんでもない暴言です。 素人が書く HTML とデザイナーが書く HTML とを同じだと思ってはいけません。 これは素人が書くプログラムと、お金をもらっているプログラマーが書くプログラムとを、「結局は同じプログラムでしょ?」といってしまうようなものであり、到底許されるものではありません。

プログラマーがコードに込める思いと同じだけのものを、デザイナーは HTML や CSS に込めています。 デザイナーが 1 ピクセルに見せるこだわりは、プログラマーがインデント幅にみせるこだわりよりもずっと尊いものです。 いくつもあるブラウザの互換性問題やバグを回避し、どのブラウザでも同じように表示されるように HTML や CSS を書くのは、素人では絶対に出来ない職人技です。 自分でいい仕事をしている人間は、他人のいい仕事もわかります。 他人の仕事のよさがわからないのは、自分がろくな仕事をしていない証拠です。

またプログラマーの皆さんが vi や Emacs や IDE ではなくメモ帳を使ってプログラムを書けと言われたらどう感じますか? デザイナーが Dreamweaver や GoLive を使えずエディタでテンプレートを書くことを強制されるのは、皆さんがメモ帳でプログラムを書くのを強制されるのと同じことなのです。 職人には職人の道具があります。 デザイナーという職人に対して、Dreamweaver や GoLive が使えないような、デザインが崩れっぱなしのテンプレートを使うよう強制するのはよくないことです。 プログラマーの都合をデザイナーに押し付けるべきではありません。

繰り返しますが、デザイナーと協業しやすい仕組みを用意するのはプログラマーの責任です。 もしみなさんがデザイナーとの協業を望むなら、まずはデザイナーの仕事を認めることが先であり、そのあとに彼らが作業しやすいテンプレートシステムを選びましょう。 テンプレートシステムが進化した今なら、それが可能なはずです。

その他

テンプレートシステムに関する雑多な話題を紹介します。 本当に雑多ではありますが、自分でテンプレートシステムを自作しようと考えている方はぜひお読み下さい。損はさせません。

独自のテンプレート言語は避ける

テンプレートシステムが、プレゼンテーションロジックを記述するための独自の言語を持つことがあります。 しかし、これは速度の面からも、また学習コストの面からも、やめたほうがいいです。

たとえば、Smarty や Template-Toolkit や Django や Velocity は、プレゼンテーションロジックを記述するために、独自の言語を用意しています。 しかし、これらは PHP や Perl や Python や Java をそのまま使うテンプレートシステムと比べて、以下のような欠点があります。

  • 動作速度が非常に遅い (少なくとも 数倍の違いがある)
  • 学習コストがかかる (新しい言語を覚えなければならない)
  • 実装が大変でコードサイズが大きくなる (読み込みに時間がかかるので CGI では特に深刻)
  • 言語の機能が乏しい (プラグインで補っても足りない)

特に動作速度の問題は、皆さんが思っている以上に深刻です。 たとえば Perl でよく使われている Template-Toolkit はプレゼンテーションロジックを記述するのに独自言語を使いますが、C 言語で実装されているにも関わらず、pure Perl で実装された Tenjn と比べて動作は約 5 倍遅いです (Perl 用の Tenjin では Perl そのものをテンプレート言語として使います)。

このように、テンプレートシステム独自の言語を実装するのは、実装する側にも使う側にもメリットがありませんので、やめたほうがいいでしょう*8

テンプレートを安全に編集する

HTML テンプレートをプログラマー以外の人が安全に編集できるようにしたいなら、プレゼンテーションロジックを HTML テンプレートから分離するタイプのテンプレートシステムを選ぶべきです。

テンプレートシステムの利点として、「テンプレート中に書けるロジックを制限できること」をあげる人がよくいます。 たとえばこちら:

> Smartyでも十分危険なコードはかけます

でも、制御することも可能なんですよね。(というか、それがすごく便利です) PHP直だと、「俺、HTMLコーダだけど、PHPも使えるぜ!PGに依頼せずに、自分で解決しておいてあげよう」 なんて、お節介な事されて、致命的なバグが発生したりするので、 別にSmartyに固執しなくてもいいのですが、HTMLコーダさんが勝手なことしないように制限かけて起きたいってのが 本音なんです。

しかし、これは大きな間違いです。 HTML にロジックを埋め込めるのであれば、どんなテンプレートシステムでも危険なことにかわりはありません。 独自言語を使うことで多少制限はできますが、大した違いはありません。

本当にテンプレートを安全に編集させようと思ったら、HTML テンプレート中には一切のロジックを記述できないようにすべきです。 つまり、Amrita2 や Wicket のように ロジックをメインプログラム中に記述するか、または CGIKit や Tapestry や Kwartz のようにロジックを別ファイルに分離するようにしなければいけません。

HTML テンプレート中にロジックが一切記述できないのであれば、デザイナーが何をしようと HTML テンプレートは安全です。 もっというと、デザイナーでない任意の人にも編集させることすらできるようになります。 たとえばブログサービスを展開していたとして、ユーザにブログの CSS だけでなく、HTML テンプレートの編集すら許可できるようになります (危険な Java スクリプトなどは、HTML テンプレートがアップロードされるときに削除すればよいでしょう)。

繰り返しますが、HTML テンプレートにロジックを記述できるのであれば、どのテンプレートシステムでも危険です。 安全に HTML テンプレートを編集させたいのであれば、プレゼンテーションロジックを HTML テンプレートから分離できるものを選びましょう。

プレゼンテーションロジックの担当者

プレゼンテーションロジックは、プレゼンテーション層に属します。 またプレゼンテーション層は「どう表示するか?」を表すため、デザイナーが担当するべきです。 このことから、三段論法でいうと、プレゼンテーションロジックはデザイナーの担当であるといえます。

たとえば、奇数行と偶数行で背景色を変えたテーブルがあるとします。 この「奇数行と偶数行で背景色を変える」というのはまさに表示のためのロジックであり、これをたとえば「金額がプラスかマイナスかで背景色を変える」に変更するとしたら、それを決定するのはプログラマーではなくデザイナーのはずです。 その意味では、「プレゼンテーションロジックはデザイナーの担当」というのは筋が通っています。

しかし実際には、プレゼンテーションロジックはプログラマーが担当したほうがいいでしょう。 なぜならプレゼンテーションロジックは、ビジネス層 (メインプログラム) からプレゼンテーション層 (テンプレートシステム) にどのようなデータが渡されるのかを知っていないと記述できず、それを知っているのはプログラマーだからです。

たしかに、見た目を管理するのはデザイナーの仕事です。 しかし、システムが正しく動作することのほうが見た目よりも重要です。 システムが正しく動作するうえで、ビジネス層からプレゼンテーション層にどんなデータが渡されるかは大変重要な情報であり、これについて責任を持つのはデザイナーではなくプログラマーです。 そのため、表示のためとはいえプレゼンテーションロジックはプログラマーが担当すべきというのが、筆者の考えです。 デザイナーがプレゼンテーションロジックを変更したくなったら、その都度プログラマーに頼むのがいいでしょう。

HTML エスケープについて

HTML エスケープとは、「< > & "」を「&lt; &gt; &amp; &quot;」に変換することです (サニタイズと呼ばれることもあります)。 これは Web アプリケーションを作る上で、セキュリティ上非常に大切なことです。 そのため、多くのテンプレートシステムでは HTML エスケープをする機能をサポートしています。

ただし、デフォルトで HTML エスケープするかどうかについては議論が分かれています。 最近では安全性を重視して、デフォルトで HTML エスケープするようなテンプレートシステムが出てきてはいますが、そうすると HTML 以外の用途では使いづらくなります。

また HTML エスケープしたくないときにどうするかという問題もあります。 たとえば ERB では <%= %> がデフォルトで HTML エスケープされるように改造することも可能なようですが、その場合は HTML エスケープしたくないときの指定が面倒になります。

いちばん手軽で効果が高いのは、HTML エスケープする記法としない記法の両方をテンプレートシステムが提供することです。 たとえば、<%= %> は HTML エスケープあり、<%== %> は エスケープなしにしたとします。 そうすれば、通常は <%= %> を使うのでエスケープし忘れがなくなり安全性が高まります。 また <%== %> は <%= %> より長いため、 間違えて <%== %> のほうを使う可能性は低いです (エスケープありよりエスケープなしのほうが書くのが面倒くさいことがポイントです)。

ホワイトリスト方式について

ホワイトリスト方式とは、すべてのデータはデフォルトで HTML エスケープして表示するようにし、エスケープしたくないデータだけ個別に明示するという方式です。 こうすることで、エスケープし忘れを防ごうというのが狙いです。

たとえば、次のようなデータを表示するとします。

@context = {
  :title=>'Tom & Jerry は面白い',
  :date=>'2008-01-01',
  :body=>'<p>Tom &amp; Jerry の再放送みた。なつかしー!</p>'
}

ホワイトリスト方式では、まずこのデータをすべて HTML エスケープしたものを用意します。

@escaped_context = {
  :title=>'Tom &amp; Jerry は面白い',
  :date=>'2008-01-01',
  :body=>'&lt;p&gt;Tom &amp;amp; Jerry の再放送みた。なつかしー!&lt;/p&gt;'
}

そして、表示するときにはこの HTML エスケープしたデータ (@escaped_context) のほうを表示します。 ただし、テンプレート側で個別に明示すれば、エスケープしていないデータ (@context) のほうも表示できます。 こうすることで、エスケープし忘れを防ごうというわけです。

しかし筆者としては、以下のような理由からホワイトリスト方式には反対です。

  • エスケープし忘れを防止する方法として、不完全である。たとえば以下のような場合、ホワイトリスト方式では事前に HTML エスケープできない。
    • 渡されたデータではない、別のデータを表示する場合 (たとえばテンプレート中で User.find(123).name を表示したりとか)。
    • 渡されたデータから導出されるデータを表示する場合 (たとえば @user = User.find(123) が渡されたとき、@user.title を表示すると、これはエスケープされていない)。
  • テンプレート中で、HTML エスケープするデータとしないデータを区別して記述しなければならない。
    • これなら、埋め込み式がデフォルトで HTML エスケープするようなテンプレートシステムと違いはない。
  • パフォーマンスが悪い。
    • エスケープする必要のないデータまで無駄にエスケープしてしまう。

筆者としては、ホワイトリスト方式を使うことで、テンプレートを書く人が HTML エスケープする・しないを気にすることなく、かつ必要な HTML エスケープが漏れなく行なわれるのであれば、ホワイトリスト方式に賛成しますが、現状はそうなっていません。 また前のセクションで説明した、デフォルトで HTML エスケープする方法と比べて、特に利点があるとも思えません。

もし、ホワイトリスト方式のよりよい利点がありましたら、ぜひ筆者に教えてください。

「目印」として使える属性

テンプレートシステムは、メインプログラムからテンプレートを読み込み、必要な箇所を書き換えて出力する仕組みです。 そのため、書き換える箇所を表す「目印」をテンプレート中に記述する必要があります。

テンプレートの HTML デザインを崩したくないなら、そこに埋め込む「目印」も HTML の仕様に含まれるものを利用すべきです。 たとえばタグやコメントや処理命令 (「<?php ?>」など) を使うことが考えられますが、最近はタグの属性を使うタイプが増えてきました。

目印として属性を使う場合、どのような属性を使うといいでしょうか。 まず任意の箇所を目印として使うためには、どのタグにでも使える必要があります。 またできれば、属性値に任意の文字列が書けるものがいいでしょう。

どのタグでも使えるような属性としては、次のものがあります。

  • XHTML の名前空間を使った独自属性 (Zope PageTemplate や Kid がこの方法)。
  • HTML の共通属性 (id, class, style, title, lang, dir のこと)

XHTML の名前空間を使った独自属性は便利なのですが、HTML で使うことも考えると、おいそれと独自属性を使うわけにはいきません (テンプレートに対して HTML validator が使えなくなります)。 また共通属性のうち、テンプレートシステムでよく使われるのは id 属性や class 属性ですが、これらは属性値として使える文字が限られているため、たとえば「id="if(items.length == 0)"」のような記述をすると HTML validator でエラーになります。

筆者のお勧めは title 属性です*9。 title 属性だと、属性値として使える文字列に制限がありません。 また HTML テンプレートをブラウザで表示したときに、該当箇所にマウスカーソルを乗せれば属性値が表示されるというのも、テンプレートシステムに都合がいいです。

テンプレートシステムを自作しようとしている人がもしいるなら、title 属性を使うことを考えてみてください。

キャッシュ機能について

テンプレートシステムには、出力結果 (生成された Web ページ) をファイルやメモリにキャッシュする機能を持つものがあります。 しかし、これは設計上の誤りだと言えます。

キャッシュ機能を持つには、以下のような事項を考慮する必要があります。 - キャッシュの対象 (何をキャッシュするか) - キャッシュの有効期間 (いつまでキャッシュするか) - キャッシュの保存場所 (どこにキャッシュを置くか)

しかし、こういったことはプレゼンテーション層で考慮すべきことなのでしょうか。 本来、プレゼンテーション層は「データをどう表示するか」という役目だけを果たせばよく、他の責務を果たす必要はないはずです。

キャッシュ機能が必要であれば、それはメインプログラム (または、MVC でいうところのコントローラ) が持つべきものであり、プレゼンテーション層が持つべきものではありません。 メインプログラムがキャッシュをコントロールすれば、たとえばデータベースへのアクセスを減らすという効果が期待できますが、プレゼンテーション層がキャッシュをコントロールするならこのような効果を期待することはできません。 出力結果のキャッシュ機能を有したテンプレートシステムというのは、プレゼンテーション層としての役割を間違えていると言えます。

なお、出力結果のキャッシュと、テンプレートシステムが内部で使用する構文木などのキャッシュとは、きちんと区別してください。 たとえば Jakarta Velocity はパースしたテンプレートの構文木をキャッシュできますが、これは出力結果のキャッシュとはわけが違うことに注意してください。 構文木をキャッシュすることはテンプレートシステムの役割に入りますが、出力結果のキャッシュはテンプレートシステムがする必要はありません。

まとめ

本稿では、以下のような内容を説明しました。

  • ビジネス層とプレゼンテーション層
    • ビジネス層 … 何 (What) を表示するか? を司る。メインプログラムが担当。
    • プレゼンテーション層 … どう (How) 表示するか? を司る。テンプレートシステムが担当。
    • ビジネス層とプレゼンテーション層のそれぞれに「データ」と「ロジック」が存在する。
      • プレゼンテーション層にもロジックが必要
  • プレゼンテーションロジックをどこに記述するか
    • テンプレート中に記述 … HTML デザインが崩れるなど欠点が多い
    • メインプログラム中に記述 … ビジネス層とプレゼンテーション層の分離ができない
    • 独立した別ファイルに記述 … いちばん理想的
  • テンプレートシステムの分類
    • プレゼンテーションロジックをどこに記述するかで 3 種類 (テンプレート中、メインプログラム中、別ファイル)
    • 対象とする形式で 2 種類 (テキスト汎用型、HTML 専用型)
  • デザイナーとプログラマーが協業するために
    • プレゼンテーションロジックを HTML テンプレートから分離することこそが大事
    • デザイナーに対するリスペクトを持とう
  • その他
    • テンプレート独自言語は、速度面からも学習コスト面からもよくない
    • HTML テンプレートからロジックを分離できると、安全にテンプレートを編集できる
    • プレゼンテーションロジックはプログラマーが担当すべき
    • 埋め込み式は HTML エスケープするのとしないのを両方用意すべき
    • ホワイトリスト方式に利点はない
    • 「目印」として属性を使うなら title 属性がお勧め
    • テンプレートシステムがキャッシュ機能を持つべきではない

次回からは、実際のテンプレートエンジンを紹介していきます。

テンプレートシステム入門 連載一覧


*1 厳密にいうと、出力形式は HTML に限りませんが、本稿は Web アプリケーションを対象としているため、主に HTML を出力することを前提とします。

*2 これはテンプレートシステムから見た分け方というだけであり、他の分け方 (MVC など) を否定するものではありません。

*3 厳密にいうと、テンプレートの HTML デザインが崩れるかどうかは単に記法の問題であり、プレゼンテーションロジックをテンプレート中に埋め込むかどうかは関係ありません。ただし、埋め込むタイプでデザインが崩れないような記法を採用しているのは少数派です。

*4 Wicket は、実際にはフレームワークであり、テンプレートエンジンだけを取り出して使うことはできません。

*5 ColdFusion や JSP のようにカスタムタグを使うものも考えられますが、それよりは独自属性を使う方がセンスがいいでしょう。

*6 CGIKit と Tapestry は、実際には Web アプリケーションフレームワークであり、テンプレートエンジンとして使うことはできません。

*7 デザイナーとプログラマーとが同じリポジトリを共有できる幸せな環境なら、この問題はあまり重要ではありません。デザイナーが Subversion や Git を使いこなせればの話ですが。

*8 ただし、「複数の言語で使えるテンプレートエンジンを作りたい」のような、特別な事情がある場合は別です。

*9 本音をいえば、特定の用途を定めないような共通属性が HTML の仕様にあると、テンプレートシステムにとっては大変都合がいいのですが。