2013年2月27日水曜日

Ruby 2.0 の refinement で DCI?

ruby 2.0 では refinement という機能が追加されています。これってDCIの実装に使えるのかも?というのが今回のテーマです。仕様が難しいので、実際動かしてみないと判断はできませんが。

注意:refinement は2013/02/27 時点でexperimentalですので、この記事が三日後ゴミになっている可能性はありますので注意してくださいね。

refinementとは?

まず簡単に refinement について説明します。refinement とは、モジュールの適用範囲を特定のファイル内のみに制限するための仕組みです。おっともうこの時点でなんか使えそうな気がしてきました。

http://jp.rubyist.net/magazine/?0041-200Special-refinement

からのサンプルコードを拝借すると、

# rationalize.rb
module Rationalize
  refine Fixnum do
    def /(other)
      quo(other)
    end
  end
end

という感じで定義します。通常の module と違うのは refine というブロックの存在です。ここで指定されたクラスにそのブロック内のメソッドをまとめて追加するぞ、という意味になります。もちろん複数のクラスを指定して、一気に refine することができます。

で、これをどう使うかというと、

using Rationalize
p 1 / 2 #=> (1/2)

使う側はこのような感じ。ファイルでこのように using で refinement を指定すると、そのファイルの中でのみ、refine 対象のクラスにメソッドが追加される、という動きになります。

DCI的に解釈

これをDCIに使えるのか?DCIではコンテキストを定め、そのコンテキスト以下においてはモデルに対して自動でroleが付与され、そのコンテキスト下でのみ実装の追加や差し替えが行われます。そしてそのコンテキストを抜け出たらそのroleは自動的に消滅します。

すると、そのコンテキスト下で各modelに付与すべきroleを refinement として定義しておき、そのコンテキストを表すファイルの先頭で using refinement する。すると refinement が role として各modelに付与される。その refinement はそのファイル内でのみ有効なので、コンテキストを抜けると自動的に role が消えます。

仕組みとしては良さそうです。また、特定コンテキスト下の role をひとまとめに書いておいて、それを一括で漏れ無くガツンと追加できる、というのもワタシ好みな感じです。今までのナイーブな rails での問題として、特定のコンテキスト下でのみ必要なメソッドが各モデルに分散してよくわからなくなる、という問題がありました。それがコンテキストという単位で一箇所にまとめることができるのは良いと思います(もちろん、モデルに置くべきか、コンテキスト単位でまとめるべきか、というのはケースバイケースだと思いますが)。また、このコンテキストで、いったいどのmoduleがextendされているのか?というのも一目瞭然なのが良いです。最もビジネスロジックレイヤーに近い、サービスなどの実装ではこの refinement が活躍しそうな予感で一杯です。

以上

2013年2月17日日曜日

考えるという事は問いを立てるという事

「何がわからないか」がわからないをそのままにしておかない技術モドキ

これと似た内容(劣化版)を昔社内の勉強会で発表したことがあるので、自分の言葉で書いてみたいと思った。

その時の発表のタイトルは「考えるとは何か」というものだった。考えるとはどういう行為なのか?入力は何で出力は何か?何ができたら「考える」という行為が達成できたことになるのだろうか?そういう疑問に答えを出したくて「考えた」結果の発表だった。

自分が過去そうだったのだが、数学のような抽象的思考(図形的思考)が得意な人の中には、なまじっか複雑なことを頭の中で図形的に処理して答えを出せるがために、考えるという行為が体系化されていない人がいる。そういう人は問題を解く際の戦略があまり洗練されておらず、難しい問題を解くときに頭の中にとりあえずその問題だけを突っ込んで、後はうーんと色々ランダムに考えてみて、時間をかけていつか答えが出るのを待つ。動物が餌を探す時に闇雲に歩きまわるような、そういう解き方をしてしまう人がいる。ちょっと極端な書き方をしたが自分が実際そうだったし、同じような人を何人か知っている。こういう、戦略のない、図形的思考にばかり頼る考え方のことを自戒を込めて「動物的思考」と呼ぶ。こういう人にはまず「言語的思考」を覚えてもらわないといけない。

動物的思考をする人は、色々な面で欠点がある。物事を図形的な思考にのみ頼ってなんとなーく処理しているので、細かいところで色々抜け落ちが出てくる。忘れっぽい。ノートも取らない。スケジュール管理もできない。机の上が散らかっている。おっと俺の悪口はそれまでだ!最大の特徴は、日常的に物事を言葉で表現する習慣がないので、とっさの質問に答えられなかったり、うまく言葉が出て来ないことがとても多い。難しい質問をすると黙り込むし、いきなり映画の感想とか聞かれても「よかったです」みたいな小学生のような答えしかでてこなくて恥ずかしい思いをする。夜になってあーあの時こう言っておけば良かったななんて後の祭りがチャメシゴト。このブログが要所要所で具体性に書けるのもそういうことだ。

私はこの自分自信の弱点を30年近い人生を経てやっと意識することができた。そしてここから脱却するために、自分の思考をもっと言葉をよく使うように変えていこうと考えた。そのための最初のステップが考えるとは何か?という問に答えを出すことだった。

では考えるとは何か?

私の定義では、考えるとは
  1. 問を曖昧さのない言葉で表現する。
  2. その問に答えを出す
の2ステップからなる。こちらのブログとほぼ同じ事を言っていると思う。そして、重要なのは1の方であり、動物的思考をする人がほとんどしていないのも1だ。動物的思考をする人はとても大雑把な問題をそのままの規模でいきなり解こうとしてしまう。するとその問題の規模が手に負えない場合、なかなか前に進まなくなる。こういう人が最初にやらなければいけないことは、問題の曖昧さをなくすことと、問題を処理可能な小さな問題になるまで分割していくことで問を洗練させるということだ。

問を洗練させるためには、まず問いがないといけない。自分が今いったいどんな問題を解こうとしているのか、自分のやりたいこと、ゴールは一体何なのか、頭の中にぼんやりとある問いを紙に書き出す。大雑把で良い。次に適当に書いた問いには5W1Hが抜けていること多いのでそこを明確にする。さらに、使われている一つ一つの単語の意味を全て拾い上げて明確に定義する。「かっこいい写真を撮りたい」というゴールだったとすると、それがいったい誰にとっての「かっこいい」なのか。かっこいいとは一体どういうことなのか、あるいはどういう物ではないのかを明らかにするということだ。そしてその実現のために必要なこと、障害となっていることは何か?ということを箇条書きにしていく。こういう整理を繰り返すと大きな問題がだんだんと小さな問題に分割されていく。当初は途方もなく大きく、どこから手をつけて良いかわからなかった問題が、ずっと規模が小さく、ゴールも明確で、なんとか手をつけれそうな問題になっていたりする。問題の最も難しい部分がどこなのかが明確になっていたり、一つ一つの小さな問題のできるできないが判断可能になっていたりする。これが問いを洗練させることの効果だ。

重要なことは、この問いを洗練させるという行為は、ある程度習熟すればかなり機械的な処理として実行できるということだ。もともと解こうとしていた途方もない問題に比べると極めて難易度の低い手続きのはずだ。簡単な整理をするだけで問題がずっと簡単になるのだから、やらない手はない。

このように、考えるという行為をはじめるときに紙の上で問題を整理していく作業をするようになってから、自分自信では物事を考えることの質も速度も向上したと感じている。こういう風に物事を整理して考える方法をもっと若い時に身に着けていれば人生変わっただろうとも思わなくはない。なので全国の動物系男子、動物系女子にはぜひとも自分の頭の中を紙にダンプして整理するという方法を身に着けてもらいたいなぁと思ってネットの片隅にこんな記事を書いた。

さらに補足として言っておきたいことが少しある。この「考える」の定義に従うならば、「一緒に考える」というのは考えている問いを誤解ない形で共有するということである、と再定義できる。また、森先生のブログにあった「考えたんですがまとまらなくて」という場合には、どこまで問いをブレイクダウンしたか、分割した中で一番むずかしい問いはどこか、その途中結果を共有するということになるだろう。「思い悩む」という状態は問いを明確にせずに「考え」ようとしてしまった結果ランダムな思考の迷路に入り込んでいる場合がある。他にもこの定義にしたがって捉え直すことのできるものがあるかもしれない。こういうことができるので言葉を自分なりに再定義するのは中々楽しい。

最後に、動物的思考をする人たちの名誉のために補足しておくと、この種の人は、その人がこれまで培ってきた多数のパターン認識という強い武器を持っている(というか、パターン認識能力に優れているから抽象的思考に強いのだと思う)。だから、パターン認識にすっぽりハマる分野では人並み以上の性能を出すことができているはずだ。ただ、それ以外の分野では抜けていることが多いという話。

2013年2月4日月曜日

view table を使って 1+N 問題回避

今回はtipsの紹介的な話。

検索結果で表示する内容が簡単な association だけでは取ってこれないような場合の解決方法について考える。

例えば、ユーザの検索結果にユーザの投稿数を表示する、という場合を考える。rails にはcounter cache columnという仕組みがあるためこの例に限っては別の解決法があるが、今回はそれを使わないことにする。

通常これを実装するためのコードは
@user = User.all

- @users.each do |u|
  %tr
     = u.posts.count

のようになるだろう。ここで明らかな問題は 1-N 問題が発生すること。posts.count を通るたびに

select count(*) from posts where user_id = 1

のような sql が実行される。u.posts.any?{|p| p.comments.present? } みたいなのだとさらに増える。

一つの解決法は当然ながら includes を使うことだ。
@user = User.includes( :posts => :comments )
としておけば 1-N 問題が解決される。しかしこれは数を数えるという目的のために comment オブジェクトを大量に作るという点で良くない。rails では残念なことに ActiveRecord の生成は大きなパフォーマンスの低下を招く。

別の方法としては、別途 sql で count した結果を inject してやるというものがある。
def inject_count_comments( posts )
  sql = "select post_id, count(*) from comments where post_id in (?) "
  restult = Comment.connection.execute [sql, posts.map(&:id)]
  posts_by_id = posts.index_by(&:id)
  result.each do |row|
     post = posts_by_id[""]
  end
end

これを毎回書くのは面倒なので、与えられた sql を実行して、model にセットするという汎用のメソッドは作ればすっきりする。model にセットするときの属性名は sql のカラム名をそのまま取るようにすればよい。

呼び出し側としては
posts = Post.some_condition.all
Post.inject_count_commitments( posts)
という二段構成になる点が残念。

さらに別な解決方法として、mysql の view を作るという方法もある。やや大げさすぎる感じもするので、これに値するほど複雑な sql のみ使っている。

最初の例の場合でいうと、以下のような view を作る。

create view posts_counts as
   select user_id, count(*) as count from posts group by user_id;

そして、この view を user の子モデルとして定義する。

class PostsCount < ActiveRecord::Base
end

class User < ActiveRecord::Base
  has_one :posts_count
end

@users = User.includes(:posts_count).all

ただこのやりかたは、Post がコメントをいくつ持っているか?という、明らかに Post の属性の一つであるものが独立したモデルとなってしまっているのがあまりよろしくなく、多様すると app/models や mysql の show tables が混雑してくるので限られた場合のみ使うほうが良いとは思う。コード上はすっきりするが。