jQuery で Ajax リクエストを行う場合, パラメータはハッシュ (JavaScript の Object 型) で指定して実行します.

この例だとまだシンプルに見えますが, success コールバックなどが肥大化してくると, 見通しが悪くなってきます.

そこで以下のようなコードを用意します.
jQuery.ajax() のラッパーとなる FluentAjax オブジェクトと, それを呼び出すヘルパーとして fajax() 関数を定義しました.

メソッドの定義にはメタプログラミングを使用しており, 実質上のプログラムは約 30 行程度です.

これを利用すると, 同じ Ajax リクエストを以下のように行うことができるようになります.

メソッドチェインでリクエストオプションを組み立てていき, 最後に execute() メソッドでリクエストを実行しています.
いわゆる Fluent Interface (流れるようなインターフェイス) になっていると思います.

メソッドチェインを使った方が可読性が高い… かどうかは好みの問題かもしれませんが, ひとつのサンプルとして.
これをもう少し抽象化すれば, 1 つのハッシュを引数にした複雑な関数は, 何でも Fluent Interface にできそうですね.

See also

, , ,

思いつきをメモ.

良い点

  • コンストラクタが隠蔽される
    JavaScript では, オブジェクトのコンストラクタは関数として定義されます.
    なので, new Constructor と呼び出すべきなのに, Constructor() というように呼び出されることもあり得ます.
  • インスタンスメソッドも隠蔽される
    Dog.create() でオブジェクトが生成されるまでは, greet() メソッドは見えません.
    オブジェクトを通してしか, インスタンスメソッドにアクセスできないということです.

悪い点

  • create() の定義が面倒
    create() に渡された引数を, init() に渡さないといけないので, 定義がやや煩雑です.
    Function.apply() が使えれば, arguments を渡すだけで良さそうですが, コンストラクタを Function.apply() で呼び出すことってできるんでしょうか?
    解決策としては, 引数を全てハッシュ変数 (Object) にする, というのが考えられます.
  • これで本当に幸になれるのか微妙
    JavaScript では private や protected といった修飾子で, メソッドを隠蔽することはできません.
    普通に定義する create メソと, コンストラクタもメソッドも明け透けな状態にあります.
    しかし, そこは開発者の良心に委ねることでも十分解決できるのではないでしょうか.
    「アンダースコアから始まるメソッド, プロパティはプライベート扱い」という紳士協定を共有すればいいのではないでしょうか.
    Python は, そういった文化の中で利用されている言語のひとつでしょう.

Ruby 以外のプログラムを書いていると, 「RSpec で spec が書けない」というだけの理由で, 億劫になってしまうことがあります.
RSpec で JavaScript の spec が書ければ… そう思って, やってみました.

まずは実際のコードを説明なしに紹介し, 環境構築手順や, 解説については後述します.

テスト対象 (System Under Test)

今回はこの JavaScript のコードを対象に, spec を書いてみます.

とりあえずは「RSpec で JavaScript の spec が書けるのか」という検証が目的なので, シンプルなもので十分でしょう.
t-wada さんのRSpec の入門とその一歩先へをそのまま JavaScript にしてみたようなものです.

detect メソッドに渡す文字列の中に, コンストラクタに渡された単語が含まれるか, を検証するだけの簡単なオブジェクトです.

$ js
Rhino 1.7 release 2 2010 09 15
js> load("message_filter.js")
js> var filter = new MessageFilter("foo");
js> filter.detect("foo bar baz");
true
js> filter.detect("Hello, World!");
false

spec

上記のコードをテストするための spec です.

MessageFilter というクラスは Ruby 上で定義されているわけではないので, ここでは Symbol 型を利用しています.

これを実行すると, 以下のようになります.

$ rspec -fs message_filter_spec.rb 

MessageFilter with argument "foo"
  should detect from "foo bar baz"
  should detect from "Hello World!"

Finished in 0.40217 seconds
2 examples, 0 failures

一応の動作は確認できましたね.

環境の構築

今回は以下のような環境を使用しています.
OS は Ubuntu 10.10 です.

$ ruby -v
ruby 1.8.7 (2011-02-18 patchlevel 334) [i686-linux]
$ rspec -v
2.5.1
$ gem list # 一部省略

*** LOCAL GEMS ***

harmony (0.5.6)
johnson (2.0.0.pre3)
rspec (2.5.0)
rspec-core (2.5.1)
stackdeck (0.2.0)

普段は Ruby 1.9.2 を使用しているのですが, 今回使用する harmony という gem の依存ライブラリである johnson が, 1.9.x ではインストールできないようなので, 1.8.7 を使用しています.
RVM を利用して複数の Ruby 環境を切り替えられる環境であれば, さほど問題は無いでしょう.

以下のようにインストールします. (Ruby, RVM のインストールは省略)

$ gem install stackdeck
$ gem install johnson -v "2.0.0.pre3"
$ gem install harmony
$ gem install rspec

harmony のインストール時には, 依存ライブラリである stackdeck と johnson も一緒にインストールされるべきですが, 現在は問題があってうまくできないようです.
とりあえず手動でインストールしておけば問題ありません.

どういう仕組みで動いているのか

Johnson が Ruby 上での JavaScript (SpiderMonkey) の実行環境を提供し, Harmony はそれをラップした DSL, とのことです.

Harmony::Page オブジェクトが .js ファイルを読み込んだコンテキストを保持し. x メソッド (execute_js メソッドのエイリアス) でプログラムを実行することができます.
x メソッドで実行した JavaScript プログラム中, 最後に評価された値が返り値として Ruby に渡されます.
(ただし, オブジェクトは返らないらしい)

問題点

  • 可読性が低い
    JavaScript のコードを文字列リテラルで書かざるを得ないこともあり, とても読みやすいとは言えないでしょう.
    RSpec はメタプログラミングにより “Test as Documentation” (ドキュメントとしてのテスト) を簡単に書かせてくれるはずですが. ここではその力を発揮できていません.
  • Object の検証ができない
    前述のとおり, Harmony は JavaScript の Object 型の値を Ruby に渡してくれません.
    JavaScript における Object は, ハッシュ変数として使われることもあり, これを検証できないのは致命的でしょう.
    とはいえ, オブジェクトがメソッドを持っていた場合, それを Ruby でどう表現するか, という問題もあり, 解決は難しいでしょう.

上記の理由から, 実際に運用・保守していくのは難しいでしょう.

最近は HTML5 によるスマートフォンアプリ作りに挑戦しようとしているのですが, TDD/BDD のフレームワークに何を使うべきか, で迷っています.
今のところは次作の簡単なフレームワークで検証していますが, さすがにずっと運用するわけにはいかないので, デファクトスタンダードなものが知りたいところです.
いいのを知っている方は是非 @yuya_takeyama まで教えてください.

JavaScript におけるテスト技法については, 2011/03/08 (Tue) に Test.js というイベントでおもしろい話が聞けそうです.
是非参加したいところですが, ATND への参加登録が遅くなったので絶望的な状態です…
この記事を LT する発表者としての参加, なんてのはダメですよねぇ…
Ust で我慢します.