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を使った方が無駄が無いかもしれないですね。

Thursday, 13 December 2012

iPad (3rd Generation) WiFi model needs a new owner

The iPad has been sold. Thank you for your interestes.

An iPad needs a new owner for 35,000 yen.

The iPad is 3rd Generation WiFi model (no mobile phone network), with Retina Display, 64 GB storage capacity, Apple Care+ covered until April 2014.

I have used the iPad always with Belkin's case, thus I see very little scratch.

You can find more information on Apple's website:

Preferably I would like to hand over the iPad in person, but I can also ship it to a remote location (within Japan).

Let me know if you have questions.

My contact details are

Taro Murao
phone: 090-1951-6892
email: taro.murao@gmail.com



Ruby on Rails AntiPatterns (2)

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

AntiPattern: RESTfulでないアプリケーション

状況が許す限りscaffoldの状態に近いルーティングに保つのが良いプラクティスの様です。

デフォルトのCRUDにコントローラのメソッドを追加する際にも例えばconfig/routes.rbの中で

resources :orders do
  collection do
    post :shipping
    post :billing
  end
end

とすると下のようなパスが作成されます。

/orders/new
/orders/shipping
/orders/billing
/orders/1

これを書き直して

resources :orders do
  members do
    post :shipping
    post :billing
  end
end

とすると下のようにもっとRESTfulな下のようなパスをつくってくれます。

/orders/new
/orders/1/shipping
/orders/1/billing

私も以前に作ったショッピングカートのルートをリファクタリングしないと駄目です…。

AntiPattern: 何故か500ページが表示される

ユーザがボタンをクリックして何故か知らないけど500ページが表示されたり、トップページにリダイレクトされてエラーメッセージも表示されないなどのサイトが多いらしいです(耳が痛い…)。
ユーザとアプリケーションの信頼を確保するために正確なエラー表示が必要です。サーバサイドのエラーは管理者に報告して、ユーザサイドのエラー(例えばメールアドレスが違うなど)はユーザにその旨を伝えると同時に、New Relicなどのログシステムを使って管理者に随時報告するのが筋な様です。
エラーをキャッチする際にもメソッドにエラーの際はnilを返すようにして do_this rescue nilとするのは無責任なので、メソッドの中で的確なエラーをキャッチして報告する仕組みにする必要があります。
例えばメールを送信する場合はまず

config.action_mailer.raise_delivery_errors = true

にします。

Net::HTTPでは下のようにエラーが下のように定義されている様なので、

SMTP_SERVER_ERRORS = [TimeoutError,
  IOError,
  Net::SMTPUnknownError,
  Net::SMTPServerBusy,
  Net::SMTPAuthenticationError]

SMTP_CLIENT_ERRORS = [Net::SMTPFatalError,
  Net::SMTPSyntaxError]

管理者のログシステムとしてHoptoadを使用した場合、コントローラでは

と書く事ができるらしいです。

なんでもかんでもbegin/rescueしたら良い訳でも無く、例えばメールアドレスの誤入力など例外ではなく、通常の行動として予想できる内容はActiveRecordのバリデーション機能などを使用してレスポンスを返すのが的確な挙動です。

AntiPattern: 長いレスポンス待ち

問題の一つは、外部サービスを使っている場合(例えば外部サーバのSMTPを使ってメールを送信する場合)サーバからのレスポンスが遅い場合があります。デフォルトではNet::HTTPのタイムアウトは30秒に設定されているそうですが、これを3秒に設定するという対処方法があります。
もう一つの問題としては、大きなプロセスの仕事があります。この場合、対処方法の一つとしてはプロセスをキューに押し込んでしまい、バックグラウンドで仕事をしてもらい順々にこなしてもらうというものです。ライブラリとしてはdelayed_jobというのとRescueというgemがあるそうです。delayed_jobはSQLを、RescueはRedisを使ってキューを管理するそうです。
ちなみにeventmachineなどconcurrencyライブラリもありますが、大きな仕事の場合は同時にさばく意味が少ないので、外部APIから情報を引っ張ってくるなど待ち時間が発生して処理に時間がかかる仕事と、プロセスの処理自体に計算の時間がかかるなどの仕事はそれぞれ異なるストラテジーでさばく必要がある様ですね。


Wednesday, 12 December 2012

Ruby on Rails AntiPatterns (1)

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

AntiPattern: Fat Models

"Fat Model, Skinny Controller"という言葉は聞きますが、too fat modelはそもそもコードが読めなくなるし、Railsのパターン以前にOOPやもっと足下の理念から外れてしまいます。

クラスを分ける、モジュールを使う、callbackを使うなどORM自体で搭載されている機能は使用するという解決策を。

AntiPattern: ながーいSQLチェーン

例えば人気のある記事トップ10を引っ張って来る時にコントローラの中で、

@posts = Post.where(status: "published", order: "number_visited DESC").limit(10)

とするよりも

モデルの中で

class Post

  scope :tops, where(status: "published", order: "number_visited DESC").limit(10)

end

とすれば

@posts = Post.tops

だけですみます。これはテストを書く際にも

before(:each)
  published_posts = create(:posts)
  Post.stub(:where){published_posts}
  tops_posts = published_posts.take(10)
  published_posts.stub(:limit).with(10) {top_posts}
end

などというスタッブ・モックチェーンを書かなくても済みます。テストを書く際にメソッドチェーンはしんどい…。"Don't ask codes but tell what to do."というような言葉もありましたが、やはりTDDのプラクティスをうまく実行すればしんどいコードを書かなくても済みそうですね。

AntiPattern: 入り組んだユーザ権限処理(authorization)

authorizationの実装を下の様にするケースが多いそうです。

こうやっていくとだんだんとスパゲッティーになっていくので、こういう書き方は一切辞めて下のように is_guest? is_manager?  などと役割役割の名前でauthorizationを実装します。また、役割もデータベースに保存しないでコンスタントとしてハードコードしてしまいます。

多くの場合、役割をデータベーすに保存するのはメリットが少ないそうです。

Monday, 10 December 2012

ActiveRecord #count, #length, #size

データベースのクエリを使ってできる事はRubyで書かないのが一つのパフォーマンスを向上するキーになるのは当然ですが、勿論それにはデータベースのクエリやORMのコマンドを知らないといけないですね。

limit(num)などは明らかにORMっぽいコマンドなので分かりやすいのですが、最近になってようやくORM使用の際の#count, #length, #sizeの相違についての記事を読みました。結論でActiveRecordでは#countはSQLでCOUNTを出します。#lengthはSQLでは処理されないので、一度配列としてデータを取ってきてRubyとして配列の長さを数えます。#sizeについては、既に配列としてデータを取ってきているオブジェクトに対しては配列の長さを返し、まだクエリのProcで残っているものに対してはCOUNTを発効してくれる便利な関数です。

こういう事を知らないでいつの間にか無駄なデータ転送を強いるコマンドを闇雲に走らせている可能性は冷や汗ですね…。

ちなみに私が最近メインで使っているmongoidでも#countはMongoDBのcountを発効してくれ様です。

Saturday, 8 December 2012

Ruby on Rails 人の失敗から学ぶ

私の経験としては失敗からは学ぶ事がとても多い様に感じます。自分で失敗すると印象が強く残るからでしょうか?でも人の失敗を観察する機会でも学びが多い様に感じます。これは自分の失敗と人の失敗を重ね合わせるというプロセスを勝手に頭の中でしているのでしょうか?

Saleh, T & Pytel, C著 "Rails AntiPattern"という本を読みました。興味深く、おもしろく、苦い内容でした。

デザインパターンの書籍は見る機会が多いのですが、AntiPatternというのは面白く感じました。成功例を並べられても正直面白くないですが、人の失敗を例に挙げて解説してくれるのはとても興味深く面白く感じました。

少しずつ学んだ事を記事に書いてはいきたいと思います。

Friday, 7 December 2012

Ruby Array#each_sliceメソッド

配列を4つごとに区切った後で更にその4つを.eachで繰り返したいということは多くあるのではないでしょうか?

RoRでの実装はeach_sliceを使ってこうしました(slimを使ったviewのテンプレートです)。

- @products.each_slice(4) do |products|
  ul
    - products.each do |product|
      li= product.name

これで

ul
  li
  li
  li
  li

ul
  li
  li
  li
  li

ul
  li
  li

と続くHTMLを作れます。