Friday 14 December 2012

Ruby on Rails AntiPatterns (3) このシリーズはこれで終わりです

Saleh, T.とPytel, C.著の"Rails AntiPattern"から幾つか私が参考になった箇所を少しずつ書いていきます。

AntiPattern: 何故かデータベースに保存されていない…

これは最近暫く悩んだ問題です。MongoDBをMongoidを使っていたのですが、モデルのバリデーションのところで

validates_uniqueness_of :product_number, allow_nil: true

としていたのですが、モデルのインスタンスを新規保存・更新するとエラーも出ないのに、何故かDBのデータが更新されません???。暫く迷っているうちにふとindexの時に

index( {model_number: 1}, {unique: true} )

と書いた事に思い当たりました。DBがロールバックした事実をmongoidの方でユーザに報せる機構が内容です。ActiveRecordでもDBのレベルでの一意性のチェックなどはしないのが良いプラクティスだそうです。SQLデータベースでもこの辺りの実装は異なるらしいので互換性の実装が難しいのでしょうね。それとももっと他の所に問題があるのでしょうか。もっとDBの実装を理解しないと問題の本質が分からないです(残念ですが)。MongoDBでも例えばクラウドサービスのMongoHQなどでは若干仕様を変えている可能性はありますね…。

AntiPattern: アセットをアプリケーションサーバのファイルシステムに保存する

例えばユーザがアップロードした画像や音楽などのファイルをアプリケーションサーバのファイルシステムに保存すると、サーバ移行やデプロイの際に無駄に作業が増えてしまうことが多い様です。
クラウドのファイルストレージサービスが推奨されています。現時点で代表的なサービスはS3ですね。

AntiPattern: テストされていないrake tasks

rakeのタスクはプロジェクトルートでrake spec raketasksなどとしてテストを走らせる事ができないため、テストでカバーされない事が多い様です。

これはモデルにコードを記述して、

class Post < ActiveRecord::Base
  def send_summary_report_to_admin
    ### Here comes actual codes...
  end
end

rake taskには

namespace :send_summary_report_to_admin
  task search: :environment do
    Post.send_summary_report_to_admin
  end
end

と記述すると、他のモデルと同じようにrake spec:models でテストできます。

ちなみにAntiPatternではないですが、他の人が書いたライブラリを利用する際は下の事を参照にすると良いとの事です。
T: Test  テストでカバーされているか?
A: Activity ユーザに使われているか?
M: Maturity 実際にある程度の期間動いているか?
テストでカバーされていないライブラリにはテストを書くという貢献の仕方もOSSの世界なのであり得るそうですが、人が作ったコードにテストを書くという行為はちょっと勇気が要りますね。メンテナンスの責任も勿論大きくなりますし…。実際ある文献によるとプログラミングの50%はメンテナンスに割かれているそうです。

テストはできるだけ自分で書いた全ての行をカバーするべきである、それができない時は最低限ブロックのレベルでテストを書くべきだという基準を耳にします。責任のあるコードを書くのは大変ですね。例としてmongoidのコードを見てみると、実際のコードとテストコードの割合は約2:1。大体これぐらいが良いプラクティスの目安なのでしょうか…?

ちなみにちなみにネットワークのレスポンスはFakeWebというgemで、ファイル読み書きはFileUtils::NoWriteでモック・スタッブできるそうなので、ここにもテストを書かない口実は無さそうです…。

AntiPattern: レスポンスが200OKなのにbodyはエラーですと言っている…

これはHTTP APIを提供しない限り許されるのかもしれませんが、レスポンスコート200を返しながら.jsonのレスポンスは

{
  "error": {
    "message": できません
  }
}

などとなっているのはAPIのコンシューマとしては不便です。レスポンスコードはW3のサイトに記載されています。ざくっと見たらコードの数はそんなに多くないので、対応するのは難しくなさそうです。

HTTPステータスコードはコントローラで下のように返します。

respond_to |format|
  format.json {
    render json: @post.errors, status: :unprocessable_entity
  }
end

RailsでのHTTPレスポンスコードとシンボルのマッピングは下のサイトにあります。

http://www.codyfauser.com/2008/7/4/rails-http-status-code-to-symbol-mapping

AntiPattern: respond_to do |format| x 3 x methods.count

コントローラのメソッド毎にそれぞれhtmlとxmlとjsonでのレスポンスの挙動が書かれていています。Rails3で導入されたresponderを使えば例えばindexは

def index
  @posts = Post.all
  respond_to do |format|
    format.html
    format.xml { render xml: @posts}
  end
end



def index
  @posts = Post.all
  respond_with(@posts)
end

と書けるそうです。

何故scaffoldでもこちらの方法でコードを生成しないのかは少し不思議です。確かにこのメソッドを使うとコントローラのコードを見ただけではxmlでサービスを提供しているのか、jsonのレスポンスも生成されるのか分からないですね。これは必要に応じて使い分けでしょうか。ウェブアプリケーションの中で統一してしまって、全てのレスポンスはxmlとjsonで返すというような指針を決めてしまえばresponderを使った方が無駄が無いかもしれないですね。

No comments:

Post a Comment