読者です 読者をやめる 読者になる 読者になる

はかますたいる!きょろの技的雑記

井上恭輔(@kyoro353)の私的かつ技的な日記です。米国サンフランシスコで暮らすエンジニアです。

俺が勝手に考える正しいMVCの実装。モデルはデータAPI!

最近、一緒にコードを書く人(特にRailsから始めた学生さん)に、
MVC(Model - View - Controller)において、「model = DB」だと考えている人が多いなぁと感じたので、このあたりに関する自分の考えをまとめて書いておきます。
あくまで俺の考えなので、違ってたらごめんね。
MVCをちゃんと理解している人には当たり前すぎる話かもなのでスルーでよろしく!


初学者はViewをモリモリ生やす


これはプログラミングを始めた人なら誰でも経験ありますよね。
むしろ、MVCとか始める前の、誰でも経験あるであろう

<?php
      print '<a href="${hoge}">link</a>';

なんてのは完全にViewだけで実装されたプログラムですね。

最近のMVCのテンプレートはとても高機能です。
変数の宣言も、条件処理も、ループも、プログラム言語としてひと通りの「逐次、反復、制御」ができちゃいます。
だからプログラムのロジックをそこに生やしてしまいたくなる気持ちはわかりますが、やはりMVCという考え方において、その実装は正しくありません。
Viewにロジックが入り込んでしまうと、Viewの生産性とポータビリティ、保守性を下げてしまうからです。
自分が本格的にMVCを触れた時に使ったテンプレートは、PerlのHTML::Templateでした。
とても高速な代わりに、まともなロジックなんて一切記述できない堅物で、最初はとっても使いにくかったのですが、
気づけば半強制的にViewとControllerの切り分けを意識させられていました。
既に時代遅れ感はあり、いろいろ問題もありますが、私としては好きなモジュールです。


MVCが理解できた!と思った頃に陥るController厨


Viewにロジックを書くことをやめ、処理と表示を切り分けて考えれるようになった頃に、多くのひとはController厨になり、Controllerに処理をモリモリ生やし始めます。
というか、Controller厨で止っている人は意外と多いんじゃないでしょうか。
さくっと書いて捨てるコードなら、コントローラだけで十分な場合も多いです。
また、Railsに影響を受けた多くのフレームワークがそうですが、一見すると「モデル=データベース」と扱っているように見えてしまいますので尚更です。
railsでモデルって勝手に作られるけど、自分でコードを足したことないなぁ」
って人もいるんじゃないかな。
しかし、コントローラにすべてのロジックを埋めこんでおくと、これもはやり保守性が下がります。
たとえば、モデルから呼び出したデータをキャッシュしたり、MySQL以外にKVSに格納されているデータと連携させたり、TwitterAPIにアクセスしたりするような処理を想像してください。
ある日、TwitterAPIスキーマが一部変更になりました。
それに応じてKVSへのデータの保持方法が変わったり、DBのカラムが追加しなければいけなくなりました。
このとき、すべての処理をコントローラに書いていると、モデルの書き直しに加え、コントローラの大幅な書き直しが発生します。
しかも、コントローラには入力値のヴァリデーションから表示用データの加工まで様々なロジックが入っています。
処理が長いこと、変更箇所が多いことは、それだけでバグを埋め込む確率が上がります。
保守性も下がります。


正しいMVCではModelが太る


コントローラ厨時代を乗り越え、「こりゃダメだ」と感じ始めると、エンジニアは表示、処理に加えてデータという概念を持ち始め、モデルを太らせ始めます。
様々な考えがあると思いますが、私はモデルが太っているコードのほうが健全だと思います。
もし上記のケースであれば、モデルにメソッドを生やして、モデル内部でTwitterAPIやKVSの処理を隠蔽して実装していれば、ビューや、コントローラ側の実装に一切手を加えること無く、プログラムを改修することができます。
MVCにおけるModelとは、広い意味でのデータ全般であり、DBだけを差すものではありません。
TwitterAPIや外部ストレージ、Memcached、KVS、こういった生データやデータストア系は
すべてModelで取り扱い、その処理を隠蔽すべきだと私は思います。


ModelはMVCにおけるデータAPI


ModelはMVCにおいて、データAPI的に設計すべきです。
それは、たとえばモデルへのアクセスがメソッド呼び出しじゃなく、RPCやWebAPIになったとしても困らないくらい綺麗なモデルのインタフェースを設計した方がいいという意味でもあります。

たとえば、Railsのモデルをコントローラでnewしたり、createすることって、ほとんどありません。
その理由は、フロントエンドのデータをそのまま無加工で流し込んで終わり、ってシーンがほとんどないからです。
ユーザ登録を行うことを考えてみましょう。ユーザ登録時にパスワードをSHA1などでハッシュ化して保存したいと考えた時、
コントローラ側で

User.create(:name => params[:name] , :password => Digest::SHA1.hexdigest(SALT + params[:password]) )

とするのは良くない実装です。
SALTや、ハッシュ化のロジックは、コントローラではなくモデル依存の都合のものなので、
クラスメソッドとしてモデル内に隠蔽すべきです。

#Controller
User.register(:name => params[:name] , :password => params[:password] )

#Model
def self.register(args)
  user = User.new
  user.name = args[:name]
  user.password = Digest::SHA1.hexdigest(SALT + params[:password])
  user.save
end

こうしておけば、SALTの変更や認証ロジックにTwitetr連携や他の操作を追加しようと思った時に、モデルの変更だけで改修が可能になります。
コントローラ側からすれば、モデルの内部実装を考えなくてすむ綺麗な呼び出し(インタフェース)なので、使いやすいですね。


オブジェクト思考は共同開発におけるプライベートスペース


ぶっちゃけ、複数人で共同開発しているときには、他人のコード1行1行について
趣味や価値観を押し付けあうのは、お互いにとって精神衛生良くないことですし、指摘する方も指摘される方も、なんだかギスギスしてしまいます。
それに、書き方なんて人それぞれの好みがありますし、友達と一緒に作る趣味プログラムに、厳格で厳しいコーディング規約を策定したりするほうが辛いし切ないですよね。

なので、インタフェースを綺麗に設計し、内部の実装にはある程度寛容に不干渉、というのが、みんなで楽しく開発するために良いスタイルじゃないかなと考えています。
そのぶん、インタフェースの設計に関してはどこまでもこだわって意見をぶつけ合って良いと思います。

人間関係もそうですが、共同開発にも「プライベートスペース」って大切ですよね。
それでは、みんなで楽しい共同開発を!