さようならはてなダイアリー、ありがとうはてなダイアリー

はてなブログに移行するかなーと思いつつ、色々考えてOctopress+GithubPagesでやることにしました


http://blog.koba04.com/ (これを機に独自ドメインに)


これからはこちらで書いていくのでRSSに登録してもらってる人は登録しなおしてもらえると嬉しいです


このブログを書いていることでたくさんのものを得ることが出来たのでとても感謝しています。
ありがとうはてなダイアリー

Backbone Eyeが面白い(かも)

https://addons.mozilla.org/ja/firefox/addon/Backbone-Eye/?src=cb-dl-recentlyadded

  • BackboneEyeというBackbone.jsで作られたアプリケーションの情報をfirebug上で色々見ることが出来るものがあります
  • 面白そうだったので久しぶりにFirefoxを立ちあげて試してみました

ViewやModelやCollectionの状態を確認することが出来る

こんな感じでModelやCollectionやViewの状態をみることが出来ます

  • View

  • Collection

View、Collection,Modelがどう連携しているのかをみることが出来る

  • こんな感じでfetchやrenderやイベントがどう連携しているのかをシーケンス図でみることが出来ます

ZombieViewを確認することが出来る

  • Zombiesというタブがあって、ZombieViewがあるとここに表示されるようです
  • が、試したみた限りちゃんと検出出来てないような気も...
  • ↓みたいにしてViewの切り替えをやってみるとApp._eventsの中にどんどんviewが古いViewが増えていくような実装で試してみたのですが、Zombiesの中には表示されない...
class View extends Marionette.ItemView
  onRender: -> App.on "xxx", @xxx, @
  xxx: -> console.log "xxx fire => #{@cid}"
    • ここは要調査かなと...

というわけで

  • 詳しくはこちらを!(iPhoneだとメニューが表示されっぱなしで見られなかったですが...)
  • 正直、実際の開発ではまだ使っていないので実際に使えるかどうかはわかりませんが、面白そうなので試してみようかなと思いました。

Backbone.jsのlistenToについて

  • Backbone.jsでviewの中でmodelのイベントを購読するときに「model.on("change", view.render, view)」のようなことをすると、modelからviewへの参照が残ってしまって、SinglePageApplicationやViewの切り替えを行ったりするアプリケーションではZombieViewが誕生してしまいます。
  • このような場合には0.9.9から追加されたlistenToメソッドを使って「view.listenTo(model, "change", view.render)」として「view.remove()」するといいのですが、listenToにするとどうなるのかということについてちゃんと確認してなかったので、DeveloperConsoleを使って確認してみました。

model.onの場合

viewとmodelのインスタンスを作った状態


model.onでイベントを購読した状態
  • model._eventsの中にviewがcontextとして保存されています
  • この場合、modelにしかイベントに関する情報は入ってないので、viewからは解除することができません
    • viewがmodelを持っている場合はthis.modelとして参照することは可能ですが...。


view.stopListening()した場合
  • viewからmodelへの参照は持っていないのでイベントはそのまま登録されたままです


model.off()した場合
  • model.off()することでイベントの購読を解除する必要があります


view.listenToの場合

view.listenToでイベントを購読した場合
  • _listenIdというものが購読対象(この例だとmodel)毎に発行され(この例だと"l70")て設定されています
  • さらにviewには_listeningToというオブジェクトが生成されて、その中に_listenId毎に購読対象(この場合はmodel)が入っています
  • イベントの購読自体は中でmodel.onが呼ばれているだけです


view.stopListening()した場合
  • viewから購読対象(model)を取得することが出来るので、stopListening()でイベントの購読を解除することが出来るようになっています


view.remove()でも解除できる
  • view.remove()中ではstopListening()が呼ばれているのでview.remove()すればイベントの購読を解除することが出来ます
    remove: function() {
      this.$el.remove();
      this.stopListening();
      return this;
    },


というわけでlistenToでイベントを購読することで、viewにlistenIdをキーにした購読対象(model)に対する参照が保持されるので、view.remove()の中でstopListening()が呼ばれて購読対象(model)からイベントの購読を解除することが出来るのでした

テストでstubを使いすぎる問題

特にJavaScriptのテスト書いたりしてると、局所的にテストするためにsinon.jsのstubを多用するのですが、そうするとstub対象の仕様が変わった時にもテストがfailしなくなるので使い過ぎには注意しないとなぁと自分のテストコード見てて思いました。


コードにするとこんなイメージ

# Viewの実装
TestView = Backbone.View.extend
  :
  testMethod: (args) ->
    results = @model.veryComplexMethod args.value
    @render result: results[0]

# テスト
describe "testMethod", ->
  view = null
  beforeEach ->
    view = new TestView model: new TestModel()
    sinon.stub(view.model, "veryComplexMethod").returns ["return value"]
    sinon.stub view, "render"
    view.testMethod value: "test input"
  afterEach ->
    view.model.veryComplexMethod.restore()
    view.render.restore()

  it "should call model.veryComplexMethod with args.value", ->
    expect(view.model.veryComplexMethod.calledOnce).to.be.ok()
    expect(view.model.veryComplexMethod.args[0][0]).to.be "test input"

  it "should render with model.veryComplexMethod return", ->
    expect(view.render.calledOnce).to.be.ok()
    expect(view.render.args[0][0]).to.be.eql result: "return value"
  • model.veryComplexMethodの処理がHTTPリクエスト投げていたり、動作させるための準備が面倒なのでとstubにした場合、model.veryComplexMethodが返り値を配列から文字列に仕様を変えた時にもこのテストは通り続けます。
    • このコード自体は意図したとおりに動いているのですが、出来ればテストがfailしてほしいところです。
  • 他にはFakeServerを使った場合なども実際のAPIの仕様が変わってもテストがfailしなくなりますが、それは単体テストの範囲でないなぁと思ったりしていて、どれをstubとするのかが考えだすと難しいなぁと思ったりしています。
  • なるべくstub対象が局所的になるようにとは思っていますが、つい簡単にテストを書くためにテスト対象のコード以外の部分をガツッとstubにしてしまうことがあるので注意しないとなと思っています...。

Marionette.js in Single Page Applicationという話をした

最近Marionette.jsを使っているのでその話を社内勉強会でやった資料です。


https://speakerdeck.com/koba04/marionette-dot-js-in-single-page-application


SinglePageAppをBackbone.jsでつくろうとするとどうしてもView周りで独自実装をせざるを得なくて、でもオレオレフレームワークは作りたくないなぁと思ってたところ、Marionette.jsがいい感じにやってくれたのでその辺りについて書いています。

蛇足(Marionette.jsとAngular.js)

Marionette.js
  • 前のプロジェクトではAngular.jsを使っていて今回Backbone系なMarionette.jsを使ってみての感想としては、Marionette.jsはView周りも含め構造化して書くことが出来てメモリ管理もある程度してくれるのでとても魅力的だと感じました。
  • あと、ソースが読みやすくて勉強になるのもいいです。
Angular.js
  • 一方Angular.jsを使うと学習コストはあるものの、簡単にコード量も少なく書くことが出来るメリットは大きいので個人的にはまずはAngular.jsで書けないかと考えています。
  • 1.2.0でさらに便利になって、日本語情報も増えてきたのでだいぶ開発しやすいのではないかと思います。

SinglePageAppとSEO

GalaxyS4のWebviewでのCanvasがおかしい件の解決方法


Galaxy S4のWebviewで、非同期処理の中でのCanvasの描画がバグってる


というエントリを書いて放置していたところいくつか解決方法を書いてくださった方がいて、それらを参考に再び戦ってみました。

その1. Create.JSの場合

// 擬似コード
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width+1, canvas.height+1);
img.onload = function() {
  ctx.drawImage(img, 0, 0, 100, 100);
};

その2. 要素を非表示にしてCanvasいじって要素を表示する

// 擬似コード
var $canvas = $("#canvas");
$canvas.hide();
var ctx = document.getElementById("canvas").getContext("2d");
img.onload = function() {
  ctx.drawImage(img, 0, 0, 100, 100);
  $canvas.show();
};
  • ちなみにhide()を非同期処理の中でやるとダメなようです...
// 擬似コード
// うまくいかない例
var $canvas = $("#canvas");
var ctx = document.getElementById("canvas").getContext("2d");
img.onload = function() {
  $canvas.hide();
  // ↓ちなみにしてもダメ
  // var ctx = document.getElementById("canvas").getContext("2d");
  ctx.drawImage(img, 0, 0, 100, 100);
  $canvas.show();
};

その3. アプリ側でハードウェアアクセラレーションをオフにする


とんちにしか思えないのですが、ブラウザに気持ちになって考えればわかるんですかね...。

Webアプリケーションのテストを書くときに考えていること

テストを書く目的

自分の書いたコードが意図した通りに動いてるか確認するために書くのですが、自分が楽をするためと他の人のために書いてます。

自分が楽するため
  • Webアプリの場合、実装した機能がちゃんと動作するかを確認するために何度もブラウザポチポチしてというのは時間がかかります。なのでその回数をなるべく減らすためにテストとして書けるところはなるべくテストで確認して、ブラウザポチポチする回数を必要最低限にしたいと思っています。
  • ブラウザポチポチするのも立派なテストだと思っています。再現性のない。
他の人のため
  • テストがないと他の人がその機能に関連する機能を変更しようとした時に変更の影響がないのか確認することが出来ず、その機能に対するテストを手動で行わせてしまうことになってしまいます。

テスト書く時間がない問題

  • テストの話をすると書く時間がないと言われたりしますが、既存の開発の流れにテスト書くことをプラスするのではなくブラウザポチポチをテストを書くことで自動化して置き換えることでむしろ開発の時間を短くすることが出来るんじゃないかという実感。

シンプルな設計

  • またテストを書きながら開発することで、実装もテストの書きやすいシンプルな形にする必要があるのでその点もメリットかなと思っています。

機能の実装とテストを書く順番

  • 実装したい機能についての設計をざっくりやります。
  • 必要そうなI/Fを空で実装します。
    • 他と連携させた時にこのI/Fで問題無さそうか確認します。
  • 機能に対するテストケースをざっくり書きます。
    • ここでは実際にテストを書かずにケースだけ書きます。
    • 出力結果がわかりやすいように、class > method みたいな感じで階層化して書いています。
describe "User", ->
  describe "greet", ->
    it "xxxxの場合はyyyyになる", ->
    it "zzzzの場合は例外を投げる", ->

  • テストと機能に対する実装を交互にやっていきます。
    • テストコードを先に書くか、機能の実装を先に書くかはその時次第でやってます...
    • 先に書くか後に書くかよりも機能の実装をテストで確認しながらやることが重要だと思っているので(TDDでできれば素晴らしいと思いますが...)
  • テストが通れば、ブラウザなどで実際に動作するか確認します。

テストする範囲を意識する

  • モックなどを有効に使って、テストを書く対象に対するテストだけを書いていくのが重要だと思っています。
  • そうしないとテストを書くのが難しくなったりテストが増えてくるので...。

サーバーサイドのテスト

  • 基本的には全てのpublicメソッドのテストを書くようにと考えています。モデルだけなくWebのHTTP Responseを返すところも。
  • HTTP Responseを返すところなどはPerlの場合はPlack::Testを使うと簡単に書くことが出来ますし、外部APIを叩いているところはMockを使ってテストを書いています。
    • 外部のAPIの仕様が変わってないかどうかについては別途それ用のテストがあるといいのかなぁとは思ってます。
  • サーバー側でテンプレートエンジンによってHTMLを返す場合は、stashなりrenderメソッドに渡される値までのテストでいいかなと思ってます。
  • モックについてはモック用のライブラリがあればそれを使って、なければTest::Mock::Objectを使ったり、局所的に強引にメソッド上書きしたりでテスト書いてます。
no warnings qw/redefine/;
local *Hoge::Foo::method = sub {
    ....
};

JavaScriptのテスト

  • jQueryでベタに書かれていると結構面倒ですが、テストを書こうという規模の場合、最近だとなんらかのフレームワークを使っていると思いますし、その場合はテストを書くのは難しくないかなと思います。
  • レンダリング結果についてもテストを書くことは出来ますが、最終的にブラウザで確認する必要があるところなどはテスト書かなくてもいいかなと思っています。
    • コストに見合わなかったり、デザイン変更の度にテストを修正するのは辛い場合があるので。
    • なのでなるべくDOMを触る部分は独立させて他の部分のテストを書きやすい構成にすることは大切だと思います。
  • JavaScriptのテストについてはここにあるような構成でやってます。
  • mochaだと非同期のテストも簡単に書けますし、sinon.jsを使うとspyやstubがものすごく簡単に出来ます。
  • JavaScriptのテストは難しいと言われてましたが、ライブラリが整っているので今だとむしろ書きやすいかなと思います。特にsinon.js。
  • JasmineやMochaでテストを書いてみて、BDDな感じでitに仕様を書いてexpectでその確認をする形がとても見やすくていいなと思ったので、他の言語で書くときもそんな感じで書こうと思いました。
  • Backbone(Marionette)でのテストのサンプル

テストとドキュメント

  • mochaとか使って、describeの説明を丁寧に書くとテスト結果がドキュメントみたいになってとてもいい感じになるし、テストケース==ドキュメントというアプローチは無駄がないしメンテしやすいしとてもいいなぁと思っています。
  • autodocみたいなアプローチも面白いですね


TDDについてはこちらのt_wadaさんの説明が素晴らし過ぎるのでオススメです!
https://codeiq.jp/magazine/2013/11/1475/


テストというとなんだか意識高い感じがしますが、自分が怠ける(楽する)ために書くことから始めてみるといいんじゃないかと思います。