Hachirog

作業メモ

JavaScriptの書式と整形についてあれこれ

JavaScriptの書式はどうすればいいのか?

しばらくJavaScriptの案件から遠ざかっており、すっかり世の中の動向から取り残されてしまった、これはまずいと巷のJavaScript情報を収集していました。

すると、なにやら書式(インデントや単語間のスペースなど記述スタイル)も昔と雰囲気が変わっているぞ、と気になって動向を調べることに。 しかし、調査してみたものの結局のところどうすればいいんだ、よく分からんとさじを投げるに至りました。

書式のトレンドは?

いまやインデントは2スペースがメジャーな模様。昔の書籍は4スペースを推奨されていたような気がします。 最近の人気OSSや有名企業のGithubをのぞいてみると2スペースが多く見受けられました。 JavaScript MVCフレームワークのAngular、React、Vueも2スペース。NodeJSも2スペース。

きりがありませんが、最近は2スペース派が勢力を伸ばしているということが分かったところで JavaScript Standard Styleというスタイルガイドが話題になっているということも知りました。
行末にセミコロンをあえてつけないという、これまでの私の見聞とは真逆のルールで衝撃的でした。 しかし、これも世の流れと思い、取り入れてあれこれ書いてみたのですが、なるほど確かにコードがすっきりすると思うようになりました。 住めば都というものですね。また何よりもStandardという名前が強烈です。 本当にスタンダードになるかもしれないと期待感を抱きこのスタイルでしばらくやってみようと決めました。

構文チェックツール

構文のチェックツールを調べてみると、ESLintがメジャーであると知りました。 書籍 JavaScript: The Good Parts の中でJSLintを知りましたが、現在はESLintがデファクトスタンダードのようですね。

書式のチェックに加え、マジックナンバーの禁止やら複雑度の制限やらルールの豊富さに圧倒されます。 細かく設定するのは骨が折れそうだと思いつつも、構文チェックツールはESLintを使えば間違いなさそうだと思いました。

整形ツール(フォーマッタ)

ソースコードの整形はエディタの標準機能でも行える場合も多々ありますが、コマンドライン上でも整形をしたいところです。 (往々にしてチーム内のエディタの好みがバラバラだったりするので、整形がエディタ依存だと厄介)

その用途にあう整形の手段としては調べたところ次の3つが有力そうでした。

ESLintの --fix オプションで整形

ESLintはルールに違反した箇所の検出だけでなく、--fix オプションで自動修正する機能もある模様。
構文チェックのルールがそのままフォーマッタのルールともなるので無駄がない。

しかしながら、この機能を利用するのは若干不安に感じました。
どのルールがどう修正されるのか(あるいは修正されないのか)、めっぽうたくさんのルールがあるので把握しきるのが大変そうです。

例えば、インデント程度なら修正してくれて全く問題ないのですが、マジックナンバーを禁止するルールで --fix オプションはどういった挙動になるのか。
たぶん何も起きないんでしょうが、そういったことをいちいち確認していくのが辛いのではないかなと思いました。

というわけで、ESLintはあくまで構文チェックだけの用途にとどめることにしました。 --fix オプションは今後もう少し調べてみたいと思います。

JS Beautifyで整形

整形に限定したツールが欲しい、という需要にマッチするのがJS Beautifyでした。 ダウンロード数も多く、アップデートが続いているようなので安心感があります。何より以前、私も使っていました。 JSの整形に加えてHTMLやCSSに対応しているのもありがたいです。 VS CodeにもJS Beautifyに対応したプラグインがあったので、利用させていただくことに。

ところが、しばらく便利に使っているとちょっとした不都合に遭遇しました。 Reactに挑戦してみようとチュートリアルを試していたときです。 JSXの整形がうまくいきません。

// チュートリアルのサンプルコードが
class Square extends React.Component {
    render() {
      return (
        <button className="square" onClick={() => alert('click')}>
          {this.props.value}
        </button>
      );
    }
  }

// 整形すると崩れる
class Square extends React.Component {
  render() {
    return ( <
      button className = "square"
      onClick = {
        () => alert('click')
      } > {
        this.props.value
      } <
      /button>
    );
  }
}

調べてみるとオプションの指定でXML部分の整形を適用外にすることができるようです。 確かにこれで難をしのげるのですが、XML部分のスタイルを整えようと思ったら自分で編集する必要があります。

// e4xオプションを有効にするとXML部分を崩すこともしなければ揃えることもしない
  render() {
    return (
            <div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
    </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
               </div>
    <div className="board-row">
       {this.renderSquare(6)}
      {this.renderSquare(7)}
                {this.renderSquare(8)}
     </div>
      </div>
    );
  }

Prettierで整形

Reactを使っている人はどうやって整形しているんだろう、と思って調べているとPrettier という整形ツールを知りました。 JavaScriptはもちろん、JSX、TypeScript、JSONCSS、SCSS・・・と幅広く対応しており今後もサポート対象を広げていく模様。

// さきほどのガタガタなXML部分もPrettierなら整えてくれる
render() {
  return (
    <div>
      <div className="board-row">
        {this.renderSquare(0)}
        {this.renderSquare(1)}
        {this.renderSquare(2)}
      </div>
      <div className="board-row">
        {this.renderSquare(3)}
        {this.renderSquare(4)}
        {this.renderSquare(5)}
      </div>
      <div className="board-row">
        {this.renderSquare(6)}
        {this.renderSquare(7)}
        {this.renderSquare(8)}
      </div>
    </div>
  );
}

素晴らしいですね。

実際に私がJSXをガリガリ書いていくか、そもそもReactを利用するかどうかは分かりませんが、 Prettierを使っておけばどう転んでも問題なさそうです。

これで決まり

ようやく着地点が見えました。

  • 書式: JavaScript Standard Style
  • 構文チェック: ESLint
  • 整形: Prettier

具体的には以下のようにしました。

  • JavaScript Standard Styleの書式にあわせてPrettierの設定ファイルを作成
  • ESLintのルールはJavaScript Standard Style(eslint-config-standard)
    • ESLintは構文チェックだけで --fix オプションは使わない
    • その他、有用そうな構文チェックルールは適宜追加していく方針にする
  • エディタはVS Codeを使用

これで安心してコーディングができます。

と思ったら、まだ終わっていなかった

ようやく納得のいく構成になったところで心機一転JavaScriptを書き進めていました。

しかし、しばらくしてまたもや不都合が生じました。なにやら関数宣言のところでESLintがエラーを吐いていました。

f:id:hachir0:20180224020809p:plain

JavaScript Standard Styleのfunctionの後にスペースが必要というルールの違反でした。

// functionキーワードの後ろにスペースが必要
const hello1 = function () {
  console.log('hello')
}

// 関数名の後ろにもスペースが必要
function hello2 () {
  console.log('hello3')
}

// オブジェクトやクラス内での関数宣言(ES6)でも同様
const obj  = {
  hello3 () {
    console.log('hello')
  }
}

一番上のケースについては アロー関数 で宣言するようにすれば、functionキーワードが登場しないのでほとんど問題がないかもしれません。 (実際、私は極力functionキーワードを使わずにアロー関数にする方針でいたのでルール違反に該当していませんでした。) しかし、三番目のオブジェクトやクラス内での関数宣言の記法は一般的に用いられていくと思います。

そして厄介なのが、Prettierではこのスペースに対処できるオプションが設けられていない点でした(2018年2月時点)。 現在、議論中という状態です。

関数宣言のスペースに関する議論

Prettierではこの議論が次のissueから始まり、

github.com

その後、2つのissueに別れ継続しています

github.com

github.com

そもそもJavaScript Standard Styleではどういった論拠でスペースありというルールにしているのか。 JavaScript Standard Style以前にはあまり見かけたことがなく、スペースなしが一般に普及しているように感じられます。

Githubリポジトリにも異口同音の質問が投稿されていました。

スペースありとする理由を読み解くと以下のようです。

  1. JavaScript Standard Style はそう決めているから
    • どちらが優れているかという議論は不毛
      • 両者の言い分それぞれに正しさがある
    • 決定することが大事
      • JavaScript Standard Styleではスペースありというルールにしただけのこと
  2. 関数宣言と関数呼び出しが見分けられる
    • スペースがあれば関数宣言
    • スペースがなければ関数呼び出し
    • 単純なルールで区別できるのでgrep検索でも見分けやすい

確かに関数宣言と関数呼び出しを見分けられるという理由はもっとものように感じます。

// 関数宣言
function hello () {
  console.log('hello');
}

const obj = {
  // 関数宣言
  hello () {
    console.log('hello');
  }
}

// 関数呼び出し
hello();

また別の観点として、functionキーワードの後のスペースは他のキーワードと同様にして一貫すべきという意見もあります。

//  ifキーワードの後にスペース
if ( count > 10 ) {
   console.log(count)
}

// forキーワードの後もスペース 
for (let i = 0; i < 10; i++) {
   console.log(i)
}

//  "function" もキーワードなのに後にスペースがないのは不揃い
const hello = function() {
  console.log('hello')
}

そういった観点も踏まえてPrettierのissue #1139は、

  • 関数名の後のスペース(#3845)
  • functionキーワード後のスペース(#3847)

に分けて議論すべきということで、二つのissueに別れました。

よく分からなくなった・・・

一連の議論を読んでみて、functionキーワード後のスペースについては確かに他のキーワードと同様に指針を揃えるべきではと思うようになりました。 ただ、関数名の後のスペースについて、Prettierのissue #1139で次のような指摘があります。

https://github.com/prettier/prettier/issues/1139#issuecomment-361718553

f:id:hachir0:20180225103324p:plain

JavaScript自体にはない機能ではあるものの、Genericsのことを考えると何がよいのか分からなくなってきました。 いずれはTypeScriptにも挑戦してみたいと思っていた私にとっては気になる点です。

// 関数名の後にスペース
function identity <T>(value: T): T {
  return value;
}

// 関数名、Genericsの後にスペース
function identity<T> (value: T): T {
  return value;
}

どちらのパターンにしても私には奇妙に見えてしまうのですが、Javaではこのようにスペースを入れる書き方をした事がないからかもしれません。 TypeScriptの公式ドキュメントでも関数名、Generics、括弧の間にスペースがありません。 MicrosoftC#のドキュメントを見てみると同様のようです。 今更ですが、そもそもその他のプログラミング言語を見回しても関数名の後のスペースはマイナーなようです。

エディタ標準のJavaScript整形機能ではどうなっているのでしょうか。

VS Codeではスペースが除去されることを確認できました。

Atomでは・・・

というところで気力が尽きました。 ここまで来て、なんだかJavaScript Standard Styleにこだわらなくてもいいような気がしてきました・・・。

結局どうする?

JavaScript Standard Styleを採用したい場合は、 eslint-config-standardを導入してESlintの--fixオプションで整形するのが良さそうです。

Prettierを使用しつつJavaScript Standard Styleを実現するnpmパッケージやエディタのプラグインもあるようです(内部でPrettierとESlintを併用している模様)。

私はひとまずJavaScript Standard Styleへの熱がクールダウンしてしまったので、いったんはPrettierをデフォルト設定で利用するでよいかなと思い始めました。 ESLIntもPrettierのルールにそったもの(eslint-config-prettier)を利用し、その後、良さそうなルールを個別に追加していこうと思います。

JavaScriptの書式は今後どうなるのか。動向を見守りたいと思います。

Androidのアプリ内課金を試してはまる

近況

職場が変わり、Javaの開発から離れ、最近はもはやWebサイトの開発の業務さえ なくなってしまいました。さみしい。

そんな中Androidのネイティブアプリの開発案件が。 Javaの開発経験者だからということで振られたものの、アプリ開発の実業務は今回が初になります。とりあえずやってみたのですが課金アイテムの購入を試そうとして嵌ってしまいました。

ドキュメントをちゃんと読めば問題なかった、という話なんですが つまづいたポイントを書き残したいと思います。

課金アイテムの購入はテストが可能なのか

アプリ内課金を実装して試している開発段階で、実際に請求が発生したらどうしようと思ったのですが そこはちゃんと課金をテストできる仕組みがあるみたいですね。

http://developer.android.com/google/play/billing/billing_testing.html

テストのやり方には2つの方法があるようで

  • 開発中のアプリをalpha/beta版に登録する
  • Static Responsesを使う

前者の方法だとアプリ登録の手間があるので、手軽ではありません。

Static Responsesを利用する方法ではGoogle Play側で用意されている テスト用のプロダクトIDをリクエストします。まず課金の実装を試すなら こちらがおすすめとドキュメントに書いてありました。

アプリ内課金を試す

実装方法の説明はこちら

http://developer.android.com/google/play/billing/billing_integrate.html

ドキュメントに沿って開発していたんですが、所々つまづいてしまい・・・

AIDLの追加

IInAppBillingService.aidlを追加せよとあります。 AIDLファイルというものは今回お初にお目にかかったのですが、 調べてみるとプロセス間通信を実現するためのものらしいですね。

そしてプロジェクトへの追加の仕方が独特

  1. SDK ManagerでGoogle Play Billing Libraryをダウンロード
  2. IInAppBillingService.aidlファイルをコピー
  3. プロジェクト内にパッケージ(com.android.vending.billing)をつくる
  4. コピーしたファイルをペースト
  5. ビルドする(IInAppBillingService.javaができる)

依存関係を追記すればOK、というわけにはいきませんでした。

実装する。そして嵌る

ドキュメントに実装例が記載されています。 そちらにしたがって実装、エミュレータで実行してみたものの NullPointerExceptionが発生してしまい、うまくいきません。

IInAppBillingService mService;

ServiceConnection mServiceConn = new ServiceConnection() {
   @Override
   public void onServiceDisconnected(ComponentName name) {
       mService = null;
   }

   @Override
   public void onServiceConnected(ComponentName name,
      IBinder service) {
       mService = IInAppBillingService.Stub.asInterface(service);
   }
};

どうもonServiceConnectedが呼び出されず、mServiceがnullのまま。 StackOVerflowにも同じ悩みを抱えている方がいるようですが、 回答の内容を試してみたものの結局解決にいたりませんでした。

http://stackoverflow.com/questions/10635512/android-in-app-billing-null-pointer-exception

半日くらい右往左往していたところ、公式ドキュメントをよく読むとこんな記述が!

To test In-app Billing in an application you must install the application on an Android-powered device.

アプリ内課金は実機でテストしないとダメでした・・・。 エミュレータではテストできないんですね。

ドキュメントをよく読んでなかった私が悪いのですが、 「アプリ内課金はエミュレータでテストできません」とか エラーメッセージが出てくれたら嬉しかった・・・。

  • USBケーブル経由で実機にアプリを入れる
  • 実機には開発者アカウントまたはテストアカウントでログインする
  • 実機のGoogle Play Serviceのバージョンを5.0.12以上にする

これで、サンプルアプリがNullPointerExceptionで落ちずに めでたくアイテム購入のダイアログが出てきました。

Unexpected response code 500 が出たので待つ

ひとまず購入処理をひと通り試すことができました。 安心したところでリファクタリングし、そして再度購入処理を試しました。 しかし失敗。

コンソールには

BasicNetwork.performRequest:Unexpected response code 500

と出ています。 500というとInternal Server Errorですね。 Google Play側のサーバが不調なのか、ということで待機。 結局回復せず、続きは翌日に持ち越しました。 そして日をまたいでからまた試してみるも失敗。

ここまでサーバの調子が悪いのはおかしい、ということで調べてみるとこんな記述が。

http://developer.android.com/google/play/billing/api.html#consume

Once an in-app product is purchased, it is considered to be "owned". In-app products in the "owned" state cannot be purchased from Google Play.

一度購入したアイテムは消費処理をしないと、再度購入することができないようです。 そこでconsumePurchaseメソッドを実行してから、 アイテムを再度購入する、という処理にしてみると無事成功!

 まとめ

  • Androidの課金テストには実機が必要。エミュレータで出来ない。
  • 一度買ったアイテムは消費しないと再購入できない。

以上、私がAndroidのアプリ内課金を試す際につまづいたポイントでした。

Reveling Prototype Patternについて

前回はReveling Module Patternについて書きました。JavaScriptでもprivateとpublicの領域を明快に分けられるという利点がありましたが、複数のインスタンスを作る際に、privateな関数もpublicな関数ももろもろが複製されるので、あまり効率が良くなさそうというのが懸念でした。

そこで、その点を補ったのがReveling Prototype Pattern(リビーリングプロトタイプパターン)ということで、紹介記事を読んで試してみました。

Revealing Prototype Pattern - Techniques, Strategies and Patterns for Structuring JavaScript Code - Dan Wahlin

Revealing Prototype Patternによるオブジェクトの作り方

//最初にコンストラクタを定義
var MessageCard = function(decorationChar) {
  this.decoration = decorationChar;
  this.history = [];
}

//プロトタイプに機能を追加
MessageCard.prototype = function() {
  function create(to, message){
    var head = createHead(to);
    var body = createBody(message, this);
    var contents = head + body;
    this.history.push(contents);
    return contents;
  }

  function createHead(to){
    return 'Dear ' + to + '\n';
  }

  function createBody(message, thisObj){
    var line = createLine(message.length + 4, thisObj.decoration) + '\n';
    var body = line;
    body += thisObj.decoration + ' ' + message + ' ' + thisObj.decoration + '\n';
    body += line;
    return body;
  }

  function createLine(length, char){
    var line = '';
    for(var i = 0; i < length; i++){
      line += char;
    }
    return line;
  }

  function getHistory(index){
    return this.history[index];
  }

  return {
    create: create,
    getHistory: getHistory
  };

}();

var messageCard = new MessageCard('+');

console.log(messageCard.create('tanaka', 'hello'));
/*
Dear tanaka
+++++++++
+ hello +
+++++++++
*/

console.log(messageCard.getHistory(0));
/*
Dear tanaka
+++++++++
+ hello +
+++++++++
*/

最初にコンストラクタを用意して、そのプロトタイプを拡張するみたいですね。 少々厄介なところがthisの参照を引き回す箇所がある点です。

  function create(to, message){
    var head = createHead(to);
    var body = createBody(message, this); //thisを引き回す
    var contents = head + body;
    this.history.push(contents);
    return contents;
  }

  function createBody(message, thisObj){ //オブジェクトの参照を引数から受け取る
    var line = createLine(message.length + 4, thisObj.decoration) + '\n';
    var body = line;
    body += thisObj.decoration + ' ' + message + ' ' + thisObj.decoration + '\n';
    body += line;
    return body;
  }

これを次のように書くとうまくいきません。createメソッドからcreateBodyメソッドを呼んだときの this.decorationはundefinedになります。JavaScriptのthisの難しいところですね。

  function create(to, message){
    var head = createHead(to);
    var body = createBody(message);
    var contents = head + body;
    this.history.push(contents);
    return contents;
  }

  function createBody(message){
    //ここでのthisはこのオブジェクトを参照していない
    var line = createLine(message.length + 4, this.decoration) + '\n';
    var body = line;
    body += this.decoration + ' ' + message + ' ' + this.decoration + '\n';
    body += line;
    return body;
  }

まとめ

Revealing Prototype Patternはプロトタイプを使って実行上の効率が良くなるものの、thisの参照に気を払わなければならない箇所があり、気をつけないと実装ミスにつながりそうだなと感じました。(JavaScriptに慣れていればそんなことはないのかもしれませんね・・・。)

一方、Revealing Module Patternの方が直観に反せずコーディングできそうです。なので、オブジェクトを山のように作る事がなければRevealing Module Patternでもいいのではないでしょうか。例えばTodoアプリを作るとして、1画面に1000個のTodoを表示させることはないと思います。複数ユーザの処理をさばくサーバーサイドはともかく、クライアントサイドに関してはRevealing Prototype Patternにしようが、Revealing Module Patternにしようが、さほどパフォーマンス的に気になるレベルではないと思いますし、好みの方を選択すればいいのかなと思いました。

Revealing Module Patternで盲点だったこと

これまで業務ではJavaScriptを場当たり的にコーディングしてきたので、今年は基本的なところから見直そうと思っています。JavaScriptの実装でいつもに気になっていたのがオブジェクトの生成で、どういった書き方が定石なのか、本で調べてもネットで調べてもいろいろ意見があって悩ましいところでした。

その中で、良さそうと思ったのが『JavaScriptデザインパターン』の中でも紹介されている「リビーリングモジュールパターン(Revealing Module Pattern)」です。

JavaScriptデザインパターン

JavaScriptデザインパターン

Revealing Module Patternによるオブジェクトの作り方

JavaScriptデザインパターン』にならったRevealing Module Pattern

var messageCard = function(){

  var decoration = '+';
  var history = [];

  function create(to, message){
    var head = createHead(to);
    var body = createBody(message);
    var contents = head + body;
    history.push(contents);
    return contents;
  }

  function createHead(to){
    return 'Dear ' + to + '\n';
  }

  function createBody(message){
    var line = createLine(message.length + 4, decoration) + '\n';
    var body = line;
    body += decoration + ' ' + message + ' ' + decoration + '\n';
    body += line;
    return body;
  }

  function createLine(length, char){
    var line = '';
    for(var i = 0; i < length; i++){
      line += char;
    }
    return line;
  }

  function getHistory(index){
    return history[index];
  }

  return {
    create: create,
    getHistory: getHistory
  };

}();


console.log(messageCard.create('tanaka', 'hello!'));
/*
Dear tanaka
++++++++++
+ hello! +
++++++++++
*/

console.log(messageCard.getHistory(0));
/*
Dear tanaka
++++++++++
+ hello! +
++++++++++
*/

いくつかfunctionが宣言されていますが、最終的に一番下でreturnしているオブジェクトが外部に公開する部分となり、それ以外は内部的に扱われるprivateな部分となります。JavaScriptにはpublicやprivateなどの修飾子はありませんが、この手法をとれば実現ができます。

JavaScriptでprivateを実現するのは不自然という意見もありますが、内部的な参照のみを意図しているものを不用意に外部にさらすのはカプセル化の観点からどうなのと思う所があります。なので明瞭にpublicとprivateを分けられるRevealing Module Patternは素晴らしい実装パターンだと思いました、が・・・

Revealing Module Patternで複数のオブジェクトをつくる

しかし、上記の実装では複数のオブジェクトをつくることができません。messageCardが即時関数の返却値となっているためです。シングルトンなオブジェクトの生成方法としては全く問題ないのですが、messageCardを何個も用意したいケースではどうしたらいいのかなと思っていました。

調べたところ、その点に言及した記事を見つけました。 http://weblogs.asp.net/dwahlin/archive/2011/09/05/creating-multiple-javascript-objects-when-using-the-revealing-module-pattern.aspx

複数オブジェクト生成に対応したRevealing Module Pattern

var MessageCard = function(){

//省略

}; //即時関数として実行しない!

//異なるインスタンスのMessageCardが生成できる
var messageCard1 = MessageCard();
var messageCard2 = MessageCard();

単純に即時関数にしなければいい訳ですね!『JavaScriptデザインパターン』の内容を鵜呑みにしていて、これは盲点でした。

複数のオブジェクト生成時の問題点

ただし、これはこれで問題がないわけではありません。 messageCard1とmessageCard2は同じ構造を持つオブジェクトですが、MessageCard関数が呼び出される度にまるまる新規に生成されます。つまり内部で宣言している関数も複製されることになり、メモリの使い方としてはあまり嬉しくありません。 この点は上記の紹介記事にも触れており、筆者はその解決策としてRevealing Prototype Pattern を紹介しています。次回はこれを調べてみたいと思います。

Thymeleafのテンプレート部品化(2)

前回の続きです。

Thymeleafでページの断片を取り込むにはth:include、th:replaceを利用しますが、そのまま生のテンプレートをブラウザで確認してもページ断片は反映されません。 しかし、テンプレートを直接ブラウザ確認する際でもJavaScriptを利用してページ断片の取り込みをサポートしてくれるThymolというライブラリが紹介されていたので試してみました。

続きを読む

Thymeleafのテンプレート部品化

前回に引き続きThymeleaf関連のエントリです。今回はテンプレートの共通部分を部品化して使い回す方法について書きました。

ページ断片を取り込む

Webページの共通部分(ヘッダーやフッターなど)を表示させたいとき、JSPではincludeやc:importの機能を利用すれば、作成したページ断片を取り込むことができます。

<%@ include file="header.jspf" %>

一方、Thymeleafではth:includeやth:replaceを利用すれば同様のことが可能です。

続きを読む

Thymeleafを業務で使いたい

Advent Calendarに参加してみたくてブログを始めてみました!よろしくお願いします!
こちらはJava EE Advent Calendar 2013 - Adventar 20日目の記事です。
前日の記事はAOE Takashiさんの「 Java EE Managed Beanについて (Java EE Advent Calendar2013 19日目) - AOEの日記 」でした。
本日はJava EEと併用するテンプレートとして、しばしば話題にあがるThymeleafについて思うところを書きました。

続きを読む