2012年12月3日月曜日

Arel scope の OR

http://dodemoyoiblog.blogspot.jp/2012/09/scope.html
上の記事でも書いたが、Arel の scope は便利だ。これは何度言っても言い足りない。

細かいことは元記事を読んでもらうとして、
scope にビジネスロジック上の重要概念を表現しておいて、クエリでは scope の組み合わせを用いることで、複雑なクエリでも極めて簡潔に表現することができる。

scope にビジネスロジックの概念を表現させて組み合わせることによって得られるメリットは、(1)個々の概念に明確に名前が付けられるため開発者間でのコミュニケーションがスムーズになる。DDD のユニバーサルランゲージと役割が近い。(2)ビジネスロジックという比較的変化しにくくアプリ全体で利用されるものをパックしているので再利用性が高い。(3)クエリ側の修正も scope 単位での追加削除によってなされることになるので、sql や AREL の where/join などを直書きするよりも間違いが起きにくい。where や join の直書きでは、複数の join, where などの組み合わせでひとつの役割を果たすことが多いがそれは字面を読んでもわからない。(4)ビジネスロジックそのものに修正が入った場合も、そのビジネスロジックがひとつの scope の中に閉じ込められて表現されていることが多いため、変更がスムーズ。

などだ。

そしてさらに嬉しいのが scope の merge だ。これは複数の scope をくっつけることができる。例えば


User.valid.merge( User.has_email )

のように書くと

User.valid.has_email

と同じ意味になる。これだとあまり旨みがわからないかもしれないが、merge の効果が高まるのは両者のクラスが異なるときだ。

User.valid.join(:emails).merge( Email.email_contains( str ) )
Admin.valid.join(:emails).merge( Email.email_contains( str ) )

このように、Email の scope を別のモデルのクエリに使いまわすことができる。
上の例のような単に like で検索するみたいな scope だと正直あまり意味はないが、複雑なビジネスロジックが scope で表現されているときこれは非常にありがたい。


あたりが参考になる。

しかし、残念なことがひとつある。merge は and が表現できるが、or が表現できない。ドキュメントには記載されているらしいが、使えない。

例えば

User.valid.join(:email, :tels).merge( Email.email_contains(email).or( Tel.tel_contains( tel )) )

みたいなことがしたい。だがまだ未実装である。

そこで、限定的ではあるが、以下のような書き方をすることで scope を再利用しつつ OR が使える。

User.valid.join(:email, :tels).merge( 
  "(#{Email.email_contains(email).where_values.join(' AND ')}) OR (#{Tel.tel_contains( tel ).where_values.join(' AND ')})"
)

え、きたならしい?そこは適当にメソッドを作って、例えば以下のコードで上と同じ動きをするようにすればよい。
User.valid.join(:email, :tels).merge( 
  are_or( 
    Email.email_contains(email),
    Tel.tel_contains( tel )
   )
 )

大事なことは概念が scope としてパックされてアプリ全体で使いまわされるということだ。

以上。





2012年11月24日土曜日

入力フォームの設定化

現在開発しているプロジェクトでは、入力フォームの設定を外部化している。

例えばhamlで入力フォームを書くと大体以下のようになるはずだ。
  = f.text_field :name, :class => "Name"

  = f.text_field :price, :class => "Price"
  = f.date_select :created_on

こう書く代わりに、hamlには
  - input :name
  - input :price
  - input :created_on
とだけ記述するようにしておき、

別ファイルのCSVに
name,text
price,money
created_on,calendar
のように書く。この組み合わせによって入力フォームが生成される。
実際には CSV には 他にもタグの属性を追記できたりするが、そういう細かい話はおいておく。

ここで大事なことは haml はあくまでも要素のレイアウト(配置)を定義するファイルであり、個々のファイルがどのような見た目、動きになるか、というのはCSVで管理する、ということだ。また、アプリケーション共通で担保すべき動きは helper に記述され、CSVで記述されるのはそのフォーム固有の設定のみとなる。

Struts の時代に戻ったのか、と思ってしまうかもしれない。このやり方が理想的なやり方だと主張するつもりは無いが、ある程度の旨みは感じている。もともと以下のようなことを考えてこの仕組みに決めた。大体目的は達成できている。
  • haml は出来る限り構造を捉えやすい状態に保ちたい。入力フォームの詳細が入ってくるのは好ましくない。erb ではどうでもいい html タグの海の中に入力フォームタグが点在している状況で、タグの親子関係がどうなっているのか調べるのが意外と大変だった。(特に一画面では収まらないような div など)
  • text_field などのhtmlタグにほぼ対応した"物理"タグ生成メソッドはできる限り使わずに、もっと高機能な"論理"タグを出力するhelperメソッドを定義して使うようにしたい。例えば日付の入力フォームはアプリ全体を通して同じ見た目、動きをするのが自然である。また、datepicker などの機能も何も考えずに設定されている状態にしたい。したがって text_field を直接呼び出すのではなく calendar というメソッドを作ってこれのみ使うようにする。
  • フォームの動きを修正するという作業と、フォームのレイアウトを修正するという作業が同時に発生することはない。したがって両者は分離されてそれぞれコンパクトに表現されている方が好ましい。
  • 上記のような高機能な論理タグを定義することで、設定ファイルも簡素化できる。物理タグを使っていたのでは設定ファイルも煩雑になって旨みが薄れる。
  • フォームの動きに関する情報が設定ファイルにコンパクトにまとまっているため、フォームの動きを修正するという作業が高速に行える。過去erbに物理タグを直接書いていた時代は膨大なタグの海から修正箇所をエディタの検索で探しだすという作業に神経をすり減らしていた。構造を捉えることの難しいファイルの修正は絶えずウォーリーを探し続けるようなもので、ただただ疲れる。
  • レイアウトに関する情報は haml にまとまっているので、配置の変更などもスムーズに行える。<tr> から </tr> まで範囲指定でカットして、目的の tr の挿入位置をエディタの検索で探しだして。。。という事がなくなる。html を中途半端にコピーしてしまって崩れるということも起こりづらい。
  • 各パーツの標準的な動きが論理タグとして helper にまとまっているため、カレンダーの動きが使いづらい、という場合に一箇所を修正すれば全体の動きを変えることができる。ユーザにとってフォームの動きが場所によって違うというのは結構なストレスになるのでこの点はとても重要だった。
以上。

2012年10月1日月曜日

model の decorator の話

最近の sapporo.rb などでは ActiveDecorator など、model の Decorator の話で少し盛り上がっているようだ。view に関わるコードをどう整理するか?という話について、共通見解が定まってきたということだろう。

rails の MVC に沿ってコードを書くと、view 回りがだんだんごちゃっとしてくる。scaffold のように単純に model の property を表示するような view なら良いが、STI のタイプに応じて表示内容を変えるとか、新規登録の場合と更新の場合で表示を変える、ユーザの権限に応じてどこまで情報を見せるか制御するなど、model のデータやセッションのデータに基づく条件分岐と html の描画が絡み合うような場合にこれをどこに書くのか?というのが問題になる。

伝統的な rails のレイヤーわけだと候補としては view, model, helper のいずれかになるだろう。この中のどれが適切か?

view にロジックを書くな、ということは昔から言われている。view のようなUIに最も近いところにモデルのロジックが入り込んでしまうと仕様変更に弱くなる。またデザイナーと分業しているときにメンテがしづらくなる。だから view にはできるだけコードは書かない。

では model に書くのか?これもあまりうまくない。まずmodelには html を描画するための十分な能力が無い。helper を include してしまうとよくわからないメソッドが大量に流入してくる。また、view に関する雑多なメソッドが model に入り込むとすぐに fat な model が出来上がる。あるプロパティでも画面によって微妙に表示の仕方が異なる、ということはよく起きるが(たとえばメールと画面では微妙に違う)、そういう些細な違いのメソッドが沢山modelに生えるというのは好ましくない。

では helper に書くのか?helper に書くのは良いアイデアのように見える。view に直接ロジックを書かないようにするための仕組みがhelperだ。ここしかない。実際小規模のアプリでは十分うまく働く。しかし、規模が大きくなるにつれて問題が出てくる。原因はhelperがフラットなためだ。このフラットな空間に様々な view 用の メソッドが生えてくると結局管理できなくなる。ある helper メソッドがどの view で使われているのか、いちいち grep するか?また helper のデフォルトの分け方が controller 単位なのも管理がしづらい原因でもある。view のメソッドは view 単位か model 単位で分けられるのが自然だと思う。

そこで出てきたのがこれらの Decorator だ。上であげた3つのレイヤーいずれとも異なる新しいレイヤーだ。Decorator は model をラップすることで表示専用のメソッドを追加することができる。ラップしているだけなので元のモデルの挙動は確保される。また、必要なときに必要なロジックが追加できる。ラッパーから helper へのアクセスを用意しておけばhtmlの描画もそれほど苦ではない。

この Decorator をどういう単位で作成するか、というところについては検討の余地がある。選択肢としては、

  • 一つの View につき一つの Decorator
  • 一つの model につき一つの Decorator
  • View x Model の組み合わせにつき一つの Decorator
最初のものはいわゆる Presenter パターン。ActiveDecoratorは二つ目のパターン。最後のものは Exhibit パターンという名前がある。ここの理解は少し怪しい。最後の一番細かい粒度で設定できるのが使い勝手としては一番良いと思う。view か model のどちらか選べと言われれば view にすると思う。ActiveDecorator のような、model 単位で Decorator 一つ、というのは結局 helper と大差なくなる。


2012年9月26日水曜日

haml のうまい使い方

1年ほど前から業務アプリの開発に haml を使っている。

世の haml の紹介では html って閉じタグが無いからすっきり書けるよ!とか.Hogeって書くだけで<div class="Hoge"> になるよ!簡単!みたいなレベルで紹介されているが、haml の真価はそんなところにはないと思っている。haml が本当にすごいと感じるのは、view を作るための簡易 DSL を定義できるところにあると考えている。理想的には、html タグを一切かかない view、ということになる。(もちろんそのような原理主義的なやり方はうまくいくはずがない)。

世の中で紹介されている、haml すげー!という解説はこんな感じ。

 %div.SearchForm
    名前
    %input{:type=>:text, :name => :name}
    生年月日
    %input{:type=>:text, :name => :birthday}

上げられる利点は

  • 閉じタグ書かなくていいよ
  • クラスとかIDが簡単に設定できるよ
という感じ。上に書いたようにこれらはさほど重要ではないというか、こういう書き方だとデザイナーが入りづらくなっただけで私が良いと思う haml のコードは

  • 見た目上我々が認知するブロック構造と haml の構造が良く対応している
  • 全てのタグ(haml行)は単なる checkbox や div よりもビジネスロジック上やUI上リッチな意味を持つ(論理タグと呼ぶ)
  • ビジネスロジックやUI上意味を持たない余計な html タグ(物理タグと呼ぶ)があまり現れない

適当にでっちあげた例だが、例えば以下のようなものだ。

- search_form do
  - left do
    - search_field :name
    - search_field :email
  - right do
    - search_field :birthday
    - search_field :user_type_id
  - buttons do
    - clear_button
    - csv_download_button
    - submit_button

この例では検索フォームを記述するときに、フォームの左側と右側にそれぞれ何を書くか。サブミットボタンの部分に何を書くか、という設定をする。それ以外の枠組みは search_form というメソッドが書いてしまう。

あるいは、タブ形式の入力画面は以下のようになる。

- tabs do
  - tab "基本情報" do
    - input :name
    - input :kana
    - input :email
  - tab "住所" do
    - input :zip_code
    - input :address

ラジオボタンの選択結果によって入力できる場所が切り替わる(入力できない場所が disable されるようなもの)を書くときは

- exclusive do
  - choice "メールで連絡を希望" do
    - input :email
  - choice "電話で連絡を希望" do
    - input :tel

のようにする。

このようにすることで記述量を劇的に減らすことができるし、一行当たりの意味の濃度が圧倒的に高いので理解もしやすい。UIの要素ごとにきれいにコードの共通化が行われているため、品質の一定化が容易だし、変更にも非常に強い。

ただ、ほかでよく指摘されているように、デザイナーさんとの協業は相性が悪い。業務アプリのような、ページのコンポーネントが限られていて、かつデザイン上後から手を加えるようなことが少ない場合に使いやすい。私はもっぱら業務アプリに使っている。twitter bootstrap と組み合わせると非常に効果的だ。

また同様に、この DSL が動きだすまでの最初の準備にはかなり手間がかかる。そのため短期のプロジェクトではあまりペイしない。ここも twitter bootstrap でうまく組み合わせることでプロジェクトをまたがった再利用ができるようになると思う。

個人的にはさらに cucumber の用語とすり合わせることでテストが簡単に書けるようになるかなぁと考えている。

model を物理レイヤーと論理レイヤーに分割する。

エンタープライズrailsという書籍に、モデルをphysical レイヤーと logical レイヤーに分けろ、というアドバイスがあった。この分け方が絶対かといわれるとよくわからないが、私も基本的にはこの考え方には賛成だ。

モデルのレイヤーを分けて考えるということはrailsのような密結合のフレームワークにおいて長期にわたってメンテナンスするコードを書くのであれば必須の事項になると思う。

どのようなレイヤーが必要になるかはアプリの複雑度によると思うが、最低限必須なレイヤーが上で書かれているphysical(DB層) とlogical(ビジネスロジック層) の二つのレイヤーだ。モデルのメソッドを physical と logical に分離し、controller から呼び出すのは基本的に logical 層のメソッドに限定する(厳密に守る必要はないと思うが)こととする。

例えばブログの記事を投稿する、というビジネスロジックがあったとする。内部的にはこれは Post というモデルのインスタンスを save するということになる。しかしこの save という単語はビジネスロジックの単語ではない。ビジネスロジックでは post だ。ビジネスロジック上の「投稿」という処理を表すのが post であり、そのDBレイヤーでの実現方法としてsaveがある。

def post
  save
end

大事なことは、全ての post は save であるかもしれないが、全ての save は post ではないということだ。つまりこれらは異なる概念であり、混同すべきではない。controller から save を直接呼ぶというのはこれを混同していることになる。

以前 before_save の弊害について書いたことがあるが、その問題の具体例がこれにあたる.。たとえば、記事の投稿をするときに記事データからタグを除去するなど正規化の処理をかけたいという要件があったとしよう。これを before_save で実現するというのは post と save を混同しているということに他ならない。全ての save が post 処理ではないのに、post 時のフックがすべての save 時に発動してしまう。このような予期しないフックが見えないところで動いていると予想しづらいバグを生む。バグが起きたのでフックをはずそうということになったはいいが、一度発動した状態で運用が続いていたものに対して、そのフックを本当に外しても大丈夫か、というのを考えるのは骨が折れる。

また別の面でも問題がある。一般的に、仕様変更はビジネスロジックの概念の単位で行われることが多い。「post時の処理にxxxを追加」という要求は出てきても、「save時の処理にxxxを追加」という要求はあまり出てこない。ビジネス上の要件はビジネス上の言葉で表現されるからだ。だからcontroller と model のインターフェース部分にビジネスロジックのレイヤーを設けることには変更に強くなるという点で非常に意味がある。




2012年9月23日日曜日

to_sql のうまい使い方(2)

前回の記事で、通常の ActiveRecord のクエリではパフォーマンスがよくないという場合に、うまくARELのよさを生かしつつ find_by_sql で高速なクエリを実行するための方法を紹介しました。
to_sql と find_by_sql でクエリを高速化する方法としてもうひとつ適用例があります。

本題に入る前に ActiveRecord が持っているあるパフォーマンス上の問題を紹介します。もしかすると最近の rails では改善されているのかもしれませんが、確認せずに書いてしまいます。その問題とは、joins を使ったテーブルに対しては eager loading が行われない、というものです。たとえば

users = User.joins(:emails).where("emails.address like '%@gmail.com'").includes(:emails)

のようにして、gmail のアカウントを持つユーザをとってきたとします。このクエリでは includes によって emails を eager loading するよう指示していることに注意してください。

さて、先頭のユーザが softbank のアドレスも持っているかどうかを調べたいとしましょう。

users.first.emails.any?{|e| e.address =~ /@softbank.ne.jp\Z/}

この時、users.first.emails にアクセスしたときにどうなるでしょうか?includes(:emails) を指定しているのだから eager loading された結果の emails  がすでに存在するはずで、ここではクエリは発生しないはずです。ですが残念ながらそうはなりません。ここで select * from emails where user_id = xxx というクエリが発生してしまいます。いわゆる1+n問題が発生してしまうのです。しかもこれを回避するうまい方法というのもありません。preload_association がまだ生きていたころに association を強制注射してみましたが、eager loading のクエリは実行されるものの、モデルはセットされずに結局 lazy load が発生しました。

この問題に対してはいつも以下のように対処しています。

まず、前回の記事で説明したような方法を使って、select + to_sql でモデルの id のみを取得します。

ids = User.connection.select_rows( User.joins(:emails).where(...).select("users.id").to_sql )

このように、connection.select_rows を使うと id の配列が得られます。(正確には配列の配列だが)。前回は find_by_sql を使いましたが、モデルが不要な場合、select_rows などの下位APIを使って配列など生データのみ取得したほうがはるかに高速です。

そしてその後idだけをつかって

User.where(:id => ids).includes(:emails)

とするときれいなモデルが得られます。

前の記事で、ビジネスロジック上意味のある絞り込み条件(activatedなど)を定義することで再利用性が高まるということを書きましたが、これはここでも適用されます。そのような scope は最初の id を引っ張るところにうまく使い回しできて、実際に必要なデータ(カラムや関連)を取ってくるところは
二つ目のSQLで調整する、という役割分担になります。


2012年9月21日金曜日

resque と delayed_job

resque と delayed_job の違いについての質問に対する回答がなかなかよかった。

http://qa.atmarkit.co.jp/q/2406

個人的には、resque って長いトランザクションの中からキューを突っ込んだ場合、トランザクションが完了するまえに resque のワーカーが動き出してしまい、そのタイミングではまだコミットされてないからトランザクションの中で加えられた変更が見えてなくておかしな挙動になる、という問題が結構深刻で使いづらいんだけど、何か対策あるのかな?

delayed_job はキューが mysql なのでその心配はない。ということで delayed_job を使っている。


しっかし↑このサイト stack overflow のパクリ丸出しだなー。


2012年9月19日水曜日

DCI (data context interaction)


DCI アーキテクチャという設計についての考え方がある。数年前から scala 界隈で盛り上がっていた記憶があるが、最近は ruby/rails 界隈でも盛り上がっている模様。

先日の札幌 ruby 会議で角谷氏が発表を行っている。
rubykaigi  http://sapporo.rubykaigi.org/2012/ja/schedule/details/79.html
スライド    http://kakutani.com/20120916.html#p01

そこからたどって以下のような資料があるのも発見した。
objects on rails (書籍の無料公開)
http://objectsonrails.com/

Clean Ruby
http://clean-ruby.com/
書籍サイト。ベータ版書籍が購入可能。$42なので電子書籍にしてはかなり高い。

DCIの講演 togetter
http://togetter.com/li/3335
色々disられていて面白い


私の意見も混ぜてざっと紹介してみます。

まず大元の問題意識としては、rails のアーキテクチャ(MVC)のレイヤ分割だけでうまくいくのはとても小規模なアプリのみということ。rails のレールに沿って素直にコードを書いていくといつのまにか super fat model. super fat helper が出来上がる。一つのクラスに大量のメソッドがある状態。これはC言語のように function が flat になっている状態と差がなくなるということだから非常に良くない。

従って、コードを責務などによって適切にレイヤー分割、クラス分割しないといけない。そしてそのレイヤー分割の判断根拠なるのはまずは「ビジネスロジック」や「ユーザエクスペリエンス」になる。
これらの「ドメイン」の境界に沿うようにプログラムの境界が設定されるのが自然なプログラム。
ビジネスロジックを理解せずにクラスやレイヤーの分割をするというのは大局では良い結果にならない。

   そういう意味で、例えばデザインパターンのような技術者だけの道具が設計の初期に出てくるのはおかしい。技術者はえてしてそういう技術話が好きなものだが、ラムダとかデザインパターンとかmix-inとか、ビジネスロジックの出てこない道具は「syntax上」の道具でしかなく、これはつまり局所最適化にしかなりえない。それを全体レベルの最適化に使ってもうまくいくはずがない。こういう道具は局所戦でしか使ってはいけない。

 まずはビジネスロジックやユーザエクスペリエンスに基づいて大枠の設計がなされ、局所的な最適化に各種ツールが活用される。その優先度が逆転してはいけない。(xxパターンに合致させるためにビジネスロジック上歪なクラス構成になってしまうなど。)ビジネスロジックに沿った境界の中を局所的なツールを使ってさらに分割するということは許されるが、プログラマの玩具によってビジネスロジックの境界が捻じ曲げられることがあってはいけない。

そしてビジネスロジックやユーザエクスぺリエンスに基づいた設計を行うために何を学ぶべきか?
というのがこのDCIや以前紹介した(かな?)DDD(Domein Driven Design)だ、と考えている。

大事なことはコードが「適切に」分割されることであって、分割されたコードをいかにruby 的に楽にmix-inするか?みたいな技術話(こういうのが局所最適化)は、コードを気持ち良く書くという短期的な幸せにとって大事ではあるが、長期的な幸せ(メンテナンス)にとっては影響度が相対的に低い。

色々大口たたいたけど自分も勉強中。。。

(追記)
そもそものDCIについて何もかいてなかったな。。。まぁ最近は紹介がたくさん転がっているのでいまさらですが、簡単に説明してみます。

DCI は上に書いた super fat model を解決するための方法、として紹介されていることが多いと思います。rails で thin controller を突き詰めると model がfatになる。大きなアプリになればなるほどビジネスロジック上の中核のモデルに大量のメソッドが生えてしまう。一番変更が激しいモデルが一番fatになっていく。そういう問題が起きます。

モデルが fat になると何がまずいか?コードの構造が読み取りづらくなり、他のメンバーが初めてコードを読んだ時に辛い思いをする。似たようなメソッドがたくさんできる。似たようなメソッドがたくさんできた時に、最初に作ったメソッドには active のようなシンプルで一般性の高い名前をつけてしまって困る。似たようなメソッドがたくさんあってよく分からなくなって、同じ名前のメソッドが定義されたり、同じ意味のメソッドが定義されたりする。などなど。

で、DCIではこれをどう解決しようとするのか。基本的な方針としては、モデルが使われるコンテキストごとに必要なメソッドを分類してモジュールに切り出して、必要なときに必要なモジュールを include しよう、ということになります。例えば入力ページを書くときだけに必要なメソッド、というのはよくあります。保存処理をするときだけ必要なメソッドなどもよくありますね。そういうものを models/user/input_role.rb や models/user/save_role.rb などのように別ファイルのモジュールに切り出して必要なときに include しましょう。とういことです。

えー、いちいち include するの面倒だよ、という人のために、DCI ではコンテキストという「場」を設けて、コンテキスト内に model が入ってきた時にそのコンテキスト用の role が自動で include されるような仕組みを作ろう、と言っています。ここについては上で少し局所最適化だ、とか吠えたように、私はそれほど重要ではないと思います。逆にこの仕組みがDCIだ、仕組みをどうやって作ろうか、みたいなところに話のフォーカスが写ってしまうとDCIの本質を見失った議論になると思います。

さて、ではmodelとroleを分けるとして、何を role に追い出すべきなのか?何が model に残っても良い、modelにとって本質的なものなのか?本来はここが議論されるべきポイントのはずです。この方針が決まる前に module が自動で include される仕組みが作られるというのは順序が逆だと思います。まぁ、ここはこれからじっくり時間をかけて合意が取られていくので。今の時点で明らかなものとしては

role に追い出すべきもの

  • 特定の場面で一回のみ使うようなメソッド。例えば退会処理を実行するメソッドなど。
  • view でのpresentationに関わるメソッド。
  • 一時的に association の動作を変えたい場合などに上書きする
model に残すべきもの
  • よく使うビジネスロジックを表現した scope。例えば、現在有効な会員データはregistered_on is not null and left_on is null だ、など。
  • model の状態を判定するようなメソッド。上のscopeをメソッドで表現したようなもの。active? とか left? など。
  • association
など。validation をどちらに置くべきかが議論の余地があると思う。save するときには必ず validation が走って正しいデータが保たれなければいけない、という場合は model に置くのが良いと思うし、業務アプリなどで、どうしても外部からの取り込みデータなどvalidationに通らないようなデータを格納しておかないといけない場合、常に validation が走ると batch 処理などがうまく走らなくなる。save :validate => false を使えば良いのだが、そういう validation を必要としない save がたくさんある場合は難しい。


最初の方で吠えていた局所最適化とかの話は↓でも似たようなことを書いてます。

コードの重複を排除するのもほどほどに!



2012年9月16日日曜日

ActiveRecord の changed? のバグ


ActiveRecord にはちょっとひどいバグ?がある。

ActiveRecord は save するときに実際に変更のあったカラムのみ
sql の update 文に書き出すようになっている。blob を持つようなモデルやカラム数がやたらと多いモデルの場合には sql が短くて済むので効果がある。

例えば

 u = User.find(1)
 u.name = "山田"
  u.name_changed? #=> true
 u.save
とすると、高速化のために name というカラムだけを更新するような sql が出力される。


同様に、
 u = User.find(1)
 u.save
としても何もカラムが変更されていないため、この場合そもそも sql が発行されない


これは良い仕組みだが、カラムが変更されたかどうかの判定に問題がある。
具体的には、代入が起き、かつ異なる値がセットされたときに変更があったとみなされるため、
gsub! を使った場合変更されたとみなされない。
 u = User.find(1)
 u.name.gsub!("山", "川")
 u.name_changed? #=> false
 u.save #=> なにもおきない

通常フォームからわたってくるデータを突っ込んで保存、という流れを想定しているので
あまり問題にはならないのだと思うが、バッチなどでこのようなコードを書いてしまう可能性はある。


2012年9月15日土曜日

コードの重複を排除するのもほどほどに!

コードの重複を排除する、ということは良いコードを書くための指針として昔から言われている。全く同じ構造のコードを1000行にわたって繰り返すことがばかげていることは誰にだってわかる。ruby はそのようなコードの重複を排除することに優れた言語だと思う。メタプログラミングを工夫することでかなり重複を排除することができる。

例えば

def opened?
  self.state == "opened"
end

def closed?
  self.state == "closed"
end

def waiting?
  self.state == "waiting"
end

のようなコードもメタプログラミングを使えば

%w(opened closed waiting).each do |name|
  define_method "#{name}?" do
    self.state.to_s == name
  end
end

のように書くことができる。

これはプログラマとしてはなかなか楽しい作業となる。コードの重複した部分を見つけて、うまいことメタプログラミングを使うことで、ちょっとしたパズル感覚で、しかもコードがぐっと短くなる。とても楽しい。

だから僕らはついついやりすぎてしまう。これが問題だ。コードの重複の排除というのは局所最適化でしかない。局所最適化を繰り返すことで全体最適化が達成されるというのは世界のほぼ全ての場所で成立しない。局所最適化のやりすぎは全体最適化を損なうというのは一般的な法則と言ってよい。

短いコードは良いコードだ。だがそれをどこまでも追及するとわけのわからんコードができる。短いコードが本当にいいコードなら、zip 圧縮してそれを読めばよい。

コードの重複を排除する、というルールをやりすぎると逆に読みづらいコードが出来上がる。重複排除がコードの品質の悪化につながるのはどういう場合か?重複の排除というのは通常同じ構造の部分をメソッドなりブロックなどで切り出すことで達成される。その切り出したコードがビジネスロジック上、あるいはアルゴリズム上などで明確で簡潔な名前をつけることができない場合、コードの品質は下がる。

機械的に重複部分だけをぴったり切り出した場合、そのようなことがよくおきる。そのようなメソッドにはよくわからないメソッド名がつけられてしまい、後からメソッドを読んだ時にそのメソッドが担っている役割が読み取りづらく、結果記憶もしづらい。メソッド名を読んでも何をしてるものなのかわからないから、結局コードの中身を一行一行読む、ということがおき、しかも繰り返す。無理やりわかりやすいメソッド名を付けても、それが内容とかい離しているようなら誤解を招くだけでより悪い。

だから簡潔なビジネスロジック上意味のある単位でコードを切り出すというのはとても重要なルールとなる。たとえそういうい切り出しによってすべての重複が排除できなかったとしても、それは気にしなくてよい。メソッドにはわかりやすい名前をつけよう、とよく言われているが、それは順序が逆なのだと思う。わかりやすい名前をつけることができるメソッドの切り出し方にしよう、というのが本来あるべき姿のはずだ。

このおかしさをわかってもらうためにしつこく説明を繰り返すが、コードから機械的に重複部分を切り出して、そこにドメインの単語をつける、というのはあってはならない。前者はシンタックスで後者はセマンティクスだ。表現すべき意味がまずあってそれをコードで表現しているはずなのに、そのコードを機械的に意味を考えずに切り貼りして、そのコードの断片がドメイン上偶然意味を持つか?という話。

具体例が出せるともっと伝わるんだろうな。もしコード書いてて具体例に出会ったらまた追記します。

2012年9月13日木曜日

before_save はやっぱり使いづらい

before_save は filter としての役割と、hook としての役割の両方が重なっている。それが便利でもあるが、バグを生む原因にもなる。

before_save はそのブロックが false を返すと save の実行自体をやめてしまうというフィルターとしての役割がある。そうとは知らずにsaveの前に実行される hook としてのみ before_save を扱ってしまうと、うっかり before_save が false を返してしまったときに保存されないという痛い目を見る。

まぁそれがメインのモデルならまだ気づきやすいのだが、子供や孫モデルだったりすると発見が遅れてしまう。

2012年9月11日火曜日

scope

AR の scope はとても良い。AREL によって表現力が高まってからは本当に使い勝手が良い。

scope は何が良いかというとビジネスロジック上重要な概念に名前を付けてパッケージ化できるところがよい。複雑な sql を書いた場合、join, where、group by, order by などに散らばった複数の sql の断片がセットになっていることが多い。

例えば以下の例を見る。


select u.id
  from users u
         left outer join user_activation ua
            on ua.user_id = u.id
         left outer join user_emils ue
            on ue.user_id = u.id
 where ua.activated_on is not null
    and ue.emails = ""
 order by u.id

これは、activateされたユーザの中から email をまだ登録していないものを抽出する、という意味になる。activate された、という条件が user_activation のjoin と 最初の where 句に散らばっている。email についても同様だ。

scope を使うと sql では文法の制限上バラバラに配置されていたものが意味のまとまりによって再配置することができる。

scope :activated, joins(:user_activations).where("ua.activated_on is not null")
scope :has_not_email, joins(:user_emails).where(:user_emails => { :email => "" })

クエリは以下のようにかける。

userss = User.activated.has_no_email.order(:id).select(:id)

非常に読みやすくなる。実際の開発ではこれよりもはるかに複雑な sql を書いているが、scope のおかげでかなり理解しやすい。sql ゴリゴリ書いての開発なんてぞっとする。

なお、order(:id) や select(:id) は scope に含めることはあまりしないし、別途 scope を定義することもあまりしない。

scope :order_by_id, order(:id)

のような、名前がそれほど意味を圧縮できない場合にはうまみがないし、そのような scope が乱立するとコードがごちゃごちゃしてくる。このあたりは単純に、同じものがくりかして出てくるかどうかで scope にするかどうか、適当にまとめたらよいと思う。

before_saveの弊害

before_save, after_save は中々便利な機能で、このような何かをフックに勝手に処理が走ってくれる、という仕組みは小規模なアプリでは大変楽にプログラムが書ける。save すれば必ず処理を走らせてくれるのだからうっかり忘れることが無い。

しかし、ある程度大きなプログラムになってくるとこれが厄介者となってくる。save したときに必ずhookを走らせるというのは、どんな時もhookが走るのだから整合性を保つうえでは楽な仕組みではあるが、例えばパフォーマンスに目を向けたときには厄介なものとなる。バッチで10万件のデータの save を行う、みたいな場合にヘビーなhookが自動で走ってしまうと問題が起きてしまう。そもそもその save は例えば特定のフラグを立てるなど、簡単な更新処理のはずなのに、 validation に引っかかってしまって保存できずにバッチ処理の処理もれが発生する、などが起きるとまずい。

validateion だけなら save :validate => false したり、update_attribute を使えば回避できるが、validate => false は association を伝搬しないし、update_attribute は一つのカラムしか更新できない(おまけにもうobsolete)。他にも before_save をスキップしたいときというのはいくらでもある。本来は入力画面から保存したときだけは知ればよいはずのhook を簡単で確実、という理由で before_save にしたばっかりに、どこで save しても hook が走るようになってしまい困る、ということが何度か起きていた。アプリの規模が大きくなるとhookを1.動かしたい場所、2.動かしても構わない場所、3.動かしては困る場所のうち3がどうしても増えてくる。そうなると結局細かくhookを実行するかどうかの制御をしないといけなくなる。before_save はそれが実行されることが若干わかりづらいために、特に複数人開発などではだんだんと混乱してくる。

そのために before_save はあまり使わないようにしている。代わりに、先のポストに書いた、機能ごとのモジュール内でhookを定義するようにしている。こうするとたとえば入力画面で保存したときは必ずhookが走ることが保証される。モデルそのものに hook を定義することはしない。



model の module 化

巨大な rails アプリを作っていると、すべての業務ロジックをモデルに書くというrails標準の考え方はすぐに破たんする。最もコアのモデルだと数千行を簡単に超えてしまうためである。長すぎるメソッドが良くないのと同様に、長すぎるクラスもよいものではない。

そこで module やラッパーを使って、特定の場面でのみ必要な機能を付加する、ということを考える。通常の rails の考え方では、モデルにはそのモデルに関係したビジネスロジックを書く、ということになっているが、そのモデルのビジネスロジックのうち、特定の場面でのみ必要なロジックを「場面xモデル」の単位でコードを切り出してmodule化しておき、必要なときにそれを include するということをしている。

例えば入力画面でのみ必要なメソッド、というのはよくあるはず。確認画面に表示するためのメソッドだったり、入力フォームを構築するために必要なちょっとしたメソッドなど。このような、本来のモデルのビジネスロジックとは無関係な、特定の機能のためのロジックを models/user/input.rb などに追いやって、実行時に都度includeすることでコードをすっきりさせる。

このような考えを進めたものに DCI というものがある。モデルにすべてのロジックをおくことはせずにモデルxビジネスロジックごとにモジュールを用意しておいて、ビジネスロジックが実行されるときにモジュールが自動的に読み込まれるようにする、という仕組みだ。最近 rails 界隈でもはやっている。

切り出すモジュールとしてよくあるのは

  • 表示用のメソッド。これがたぶん一番多い。たとえば住所を表示する際に郵便番号から番地までひとつの文字列として出したい場合や、それぞれ別々に改行して出したい場合、市区町村以下しか必要ない場合、などいろいろある。そういう細かい違いだけのメソッドが大量に混ざるとコードがどんどんごちゃごちゃするし、どこで何が使われているのか把握できなくなる。exhibit オブジェクトとか presenter とか言う。
  • validation など編集時だけに必要なメソッド。
  • 各種ビジネスロジックごとのメソッド。バッチファイルでのみ必要なメソッドなどを突っ込む
モジュールを include するのをどうするかという問題がある。モデル単体であれば簡単だが関連上のモデル全部に対して extend していくのはなかなか面倒だ。

自分は traverse というオブジェクトツリーをたどるライブラリを自作しており、これを使っている。

user.acts_as! :list_presenter, [ :emails, :tels, :user_groups => :representative ]

などとして一覧表示用のメソッドを追加する。最後の引数は Arel の includes などに渡す hashと同じ形式のものを渡す。そうすると Arel と同様にアソシエーションを解釈して関連にも module を extend していってくれる。





ghostscript の dithering がひどい件

gs で pdf => 白黒tiff に変換すると、文字部分は問題ないが画像部分に dithering が発生してかなりひどいクオリティとなる。細かい文書だと読むのは不可能になってしまう。

gs -dBATCH  -sDEVICE=tiffg3  -dNOPAUSE  -sOutputFile=output.tiff -r200 -f input.pdf

これを回避するために gs のオプションを設定して何とかしようと試みたが、どれもあまりうまくいかない。というかどうやっても無理だった。なのでもうあきらめて imagemagick を使うことで回避する。ただ、imagemagick は imagemagick で PDF のフォントの扱いに難があって文字が異様に小さくなったりしてこれも調整が面倒になる。

ということで、以下のような2段階の方法を取ることにする。まず gs で PDF => カラー画像(jpg, png など)に変換し、その後 convert を使って tiff に変換する。


gs -dBATCH  -sDEVICE=jpeg  -dNOPAUSE  -sOutputFile=output$04d.jpg -r200 -f input.pdf
convert -page A4 -density 200 output*.jpg -compress Group4 -colorspace GRAY -colors 2 -normalize output.tiff


こうすることでまともな tiff が得られる。ただしこれも少し難点がある。JPG にしたときに若干モアレが発生してしまい、それが最終的な白黒画像に若干のノイズドットとして残ってしまう。

中間のフォーマットを png にすればこれは解決するのだが、png はtiffと同様にそのフォーマットに pixel 数だけでなく resolution(dpi) を持っており、同じpixel数でも dpi が異なれば印刷したときなど違う用紙サイズとなる。これは tiff にしたときにも引き継がれてしまい、最終的なoutputが通常の1/3サイズの紙になってしまう。これはまだ解決できていない。jpgには resolution という概念はなく pixel のみなのでこのような問題は起きない。

2012年9月10日月曜日

ruby の Iconv の文字変換エラー対策

Iconv は変換不可能な文字があった場合にIllegalSequenceエラーを返してくれる。勝手に文字を無視されて知らない間に変なデータが登録されてしまうよりかは良いが、無視をしたい場合はある。たとえばユーザのメアド一覧をCSV出力したい、というときに名前に中国の漢字が含まれているためにCSVが出力できない、というのは好ましくない。

このような目的のためのオプションが Iconv には用意されている。

 str = Iconv.conv("CP932//IGNORE", "UTF-8", str)

とすれば、変換不可能な文字は単に無視される。

使いどころを間違うと問題になりうるオプションではある。

boolean 引数

boolean をそのまま引数に取るようなことはあまりしないようにしている。過去 ActiveRecord の save メソッドは validation をするかどうかを boolaen の引数で切り替えていた。

  save(true) #=> validation あり
  save(false) #=> validation 無し

save くらい良く使うメソッドならまだ良いが、そうではないメソッドにおいて、このような引数設計をしてしまうと、あとで読むとその boolean が何を意味するかが読み取りづらい。

rails3 からは上記メソッドは

 save :validate => false

のような書き方になっており、その boolean で何を切り替えているのかが呼び出し元を見てわかるようになっている。自分でメソッドを作る場合もこれに倣うようにしている。

2012年9月9日日曜日

メソッド名の動詞

先のポストに続いての話で、私は複雑なモデルのprivateメソッドを作るにあたって、メソッド名には動詞を必ず記載することにしている。java はおそらくそういう慣習があり rubyはそうではないが、複雑なモデルにおいては動詞を省略することはしない(じゃないと後で混乱を招きやすい)。 

そしてその動詞の使い分けにもそれなりに気を使う。どこかからデータを取ってきて返す、という場合 何も考えないと get_emails としてしまいがちだが、これだとその裏でどのような処理が走るのか見えない。get というとすでに取得済みの変数への参照が帰るだけ、というイメージがある。これが実は複雑な計算をはらんだ結果かえって来るようだと知らずに多用してしまうとパフォーマンス上の問題を容易に引き起こす。裏でどのような処理が行われるのか、ある程度見えるようなメソッド名をつけるようにしている。

 具体的には以下のような動詞を使い分けする。


  • get (すでにメモリ中に存在する変数への参照を返す)
  • read (ファイルから読む)
  • load (DBから取得する)
  • fetch (外部サーバにとりにいく)
  • calculate (deterministic な方法で計算する)
  • detect (複数の候補の中から計算して選択する)
  • build (新しく作る)
  • create(新しく作って保存する)
  • search (条件を使って多数の候補から絞り込む)
これは private メソッドを作るときの指針であって、public なメソッドの場合の判断基準はほかにもある。public なメソッドは誤解の生じない限り簡潔にしたほうがよいし、そのモデルが使われるコンテキストから動詞は省略できることが多い。使う側がloadかgetか知っているというのも仮定として成り立たないことも多い。load か get かで使う側が迷うのもおかしな話。private なメソッドは似たようなメソッドが作られることもあり、多少冗長でも誤解のないようにしたい。

メソッド名をどこまで短くするか

ruby を書いていると簡潔なメソッド名や変数名を使うようになる。たとえば load_emails_by_user_id と書くべきところを単に emails とするなど。簡潔なメソッド名を使うのは読みやすくよいことのように思えるが、そうではないこともある。

短いメソッド名を使うということはどういうことかというと、本来書くべきことのうち、コンテキストから明らかな情報をそぎ落とすということになる。この判断を失敗することが多いのが問題なのだ。具体的には2種類の失敗がある。

メソッド名に書くべき単語のうち、「1. コンテキストから明らか」「2.コンテキストからはまったく推測できない」「3.そのどちらでもない」の3つにわけたとしよう。このとき、1 をメソッド名に記載しないのはよい判断といえる。だが実際には 2 のみを書く、という判断をしてしまいがちである。この間違いは非常に多い。上記の例でいうと、emails というのは 2 にあたる。ここだけに注目して emails というメソッド名にしてしまうのはまずい。by_user_id というのはこのメソッドが user クラスの中にあるとすると 1 に相当すると判断してよい。load は微妙なところなので 3 になるとしよう。たとえばビジネスロジック上ファイルからemailsを読む(read)場合がある、としよう。この場合 load_emails とするのが無難となる。もちろん、email をとってくるときは load 以外ありえない、と判断できるなら emails で誤解は生まれない。

もうひとつの失敗は、コンテキストというのは変化するということだ。上記の例で、load 以外ありえないと思っていたのが開発を進めていくうちに read する場合もありえると判明することがある。本当に省略してしまってよいのかどうか(それで将来誤解を生む可能性がないのかどうか)、一寸考えるに越したことはない。



引数の並び順

メソッドの引数の並び順には少しだけ気を払うようにしている。この並び順が与える影響はおそらくそれほど大きなものではないし、何が正解かはかなり微妙な問題だから、あまり深く考えるわけではないが、少なくとも適当な並びにはしていない。

どのような順番にしているかというと、そのメソッドが呼ばれるであろうコンテキストにおいて、最も変化しにくいもの、もっとも基本的なデータを左に持ってくることようにしている。あるいは、引数の中に親子関係があるのなら、親を左に持ってくる。感覚的な言い方になるが、メソッドの動作のための「足場」となるようなデータを左に持ってきて、細かい動作を切り替えるオプションに相当するような引数は右に持ってくる。





2012年9月5日水曜日

find_by_sql のうまい使い方(1)

rails3からARELが導入され、ActiveRecordのクエリインターフェースはとても洗練されたものになりました。
scopeを組み合わせることで複雑なクエリをとてもシンプルかつ自然な形で表現できるようになっています。実際のところ、ARELの表現力はよく現れるSQLのほとんどは表現可能だと思います。

たとえば

 User.activated.without_email.includes(:emails, :tels).order("name").limit(10)

のようにしてメールアドレスを登録していないユーザを名前順に10件取り出す、ということが実現できます。ARELがすごいのはこのようなシンプルな例ではなく、もっと多数のテーブルが絡んだ複雑なクエリであっても、scope を適切に定義することですっきりとしたクエリが記述できることにあります。

しかしそんなARにも弱点はあります。パフォーマンスの問題です。ARはとにかく遅い。結果が1000行単位になると数秒待たされることもざらです。何に時間がかかるかというと

  • AR のオブジェクトの組み立て
  • オブジェクトツリーの構築
です。sql の実行がいかに早くても、オブジェクトの件数が多いとそれだけでもっさりしてしまいます。

性能がさほど問われないページではそれでもARELのメンテナンス性を重視して多少の速度には目を瞑るのですが、検索結果などの速度が必要なページでは、残念ながら生のARELは許容できません。

このようなパフォーマンス上の問題を改善するためにどういう方法がとれるかというと、基本的な方針としては find_by_sql に切り替えることになります。これはなぜかというと、

  • 不要なカラムを取得しないことでモデルオブジェクトの作成にかかるコストを減らす。
  • 細かいテーブル(ユーザに紐づくメアドや、市区町村名などの各種マスタ)をいちいち ActiveRecord のモデルとして作成せずに join してプロパティとしてアクセスできるようにする。たとえば user.email.address などのメソッドチェーンを発生させるのではなく、user.email_address などとしてワンステップでアクセスできるようにする。これによって余計なモデルの作成とオブジェクトツリーの組み立てが省略できる。
  • ActiveRecord の eager/lazy load がおきないようになる。ひとつのsqlですべてのデータを取得する。

ということを実現するためです。

このために従来は find_by_sql を使っていたのですが、これだとせっかく scope で定義したクエリの内容を重複して sql に書かないといけません。この sql の作成に scope を使いたい、と考えるのが人情です。

この目的のため?に、ActiveRecord は to_sql というメソッドを持っています。名前のとおり、scope などで定義された AREL をそれが表現する sql に変換するためのメソッドです。


sql = User.activated.select("users.id, users.name, emails.address, tels.tel").order(:name).limit(10).to_sql
User.find_by_sql(sql)

ポイントは

  • includes の代わりに joins を使う。
  • select で必要なカラムだけを取得する。
  • 結果として、association が発生しないように必要なカラムはすべて取得しておく。
ということになります。こうすることでパフォーマンスが必要な場合でも scope を使ってクエリを記述することができます。もともとの、sql を使わない版からの変更も比較的容易だと思います。


この方式がなぜうまくいくかを別の視点から説明しておきますと、
  • データの絞り込み条件として定義されたscopeは通常ビジネスロジック上の重要な概念を表しており(activated など)、ページごとに意味が変わることがなく、再利用しやすい。またscopeの形で字面上ひとつの単語(メソッド名)になっていても頻出の概念であるため理解が容易。
  • 一方でページごとにころころ変わるのはどのカラムを取得するか、だが、これは select でその都度切り替えることができる。逆にこれをscopeにしたところで適切な名前をつけることは難しく、結局中身を見ないとわからないということになりがちです。
ということで、変化しにくい部分をうまいことコードの下のほう(モデルの定義)に追いやって、変化しやすい部分をコードの上のほう(よりcontrollerに近いほう)にすくいあげていることになります。このようにコードがあるべき場所に配置されているためメンテナンスしやすいといえます。