【MVC】
Controllerは「解釈」「分離」「指示」。ファットコントローラー/痩せすぎコントローラーの問題点と改善

記事イメージ

M-V-C(Model-Controller-View)パターンは、Webアプリケーションフレームワークにおいて関心の分離を組み込む代表的手法です。

しかし、M/V/Cそれぞれが為すべきことに解釈が色々あり混同されがちであり、「ファットコントローラー」(Controlletrに全てをつっこんだもの)といった揶揄が生まれる所以でもあります。

他方、アンチパターンであるファットコントローラーを意識しすぎるあまりControllerにほとんど処理を書かない「痩せすぎコントローラー」(本記事の造語です)になりがちでもあります。

このような背景を踏まえ、改めてMVCにおけるControllerには何を書くべきか?を考えてみます。

Controller(UIからの受け口)は、「解釈」「分離」「指示」を行う。

この記事が考える「Controllerは何をするべきか?」のまとめは以下の通りです。

Controllerがするべきこと
  • View(UI)からの情報を解釈してフレームワークからデータを「分離」する。
  • モデル層やインフラ層に処理を「指示」する。
  • View(UI)にモデル層やインフラ層の処理結果を受け表示する内容を「指示」する。

具体的には次のセクションから順に述べていきますが、端的にまとめると「解釈」「分離」「指示」をするべき、となります。

ちなみに本記事の議論はWeb系フレームワークに限らず、デスクトップにおけるコードビハインドなどUIのすぐ裏にいるコード全般に共通するものと言えます。

Controller層がするべきこと概観図
Controller層がするべきこと概観図

ファットコントローラーの何が問題か?→主に後の変更難易度上がること

ファットコントローラー(直訳:太った操縦端)はMVCパターンにおける代表的なアンチパターンとして有名です。

コントローラーはUIに対して最前線にいる(情報を受け取り返す部分)ため、何も考えず全ての処理をコントローラーに詰め込むことができます。これがファットコントローラーが生まれる背景です。


//ファットコントローラーの例。
//Controllerでデータの取り出し、計算、DB操作など全てをこなす。
void IActionResult Controller(FormItem item) {
    //Viewの情報を取り出す
    var email = item["email"];
    var body = item["body"];
    ・
    ・いろいろ計算
    ・
    
    //データベースに情報を挿入
    database.Insert(email, body);
    ・
    ・
    ・
    //UIに表示を返す
    return View();
}

上記のようなファットコントローラーでもソフトウェアとしての動作はしますが、何が問題でしょうか?

多くのプログラムは、ビジネスを処理するツールであり収益源であるため、法令、トレンド、技術環境等に合わせて常に変化・成長することが求められます。

こういったソフトウェア自身の変化・成長の要請に対してファットコントローラーは脆弱です。処理が全て同じところに書かれているので、後から見た時に何が書かれているのか理解に時間がかかります。また一部の変更を行うと後続処理に影響が出る可能性があり改修や不具合対応に余計な労力(コスト)が消費されます。

Controller
void IActionResult Controller(FormItem item) {
    //処理A

    //処理B ←処理Bを変えた時、処理Cに影響が出るかもしれない(ローカル変数などが変わるので)

    //処理C
}

尚、ここまでの話は「関心の分離」についての関連記事にほぼ同様の議論を深掘りして論じています。併せてご参考ください。

【社内PR】チーム・ウォーク

痩せすぎコントローラーも良くない。モデル層がフレームワークと密結合してしまう。

「ファットコントローラーがよくないから、他の処理を呼び出すだけにしよう」という中級者が陥りがちな「痩せすぎController」にも問題があると考えます。

例えば以下のコードのようにControllerが即時に他のクラスやメソッドを呼んでいます。

ファットコントローラーになるまいという硬い意志は感じますが、Controllerのやるべきことを放棄しており存在意義がほとんどありません。

Controller
void IActionResult Controller(FormItem item) {
    return Model.Run(item); //痩せすぎコントローラーは何もしない
}

痩せすぎコントローラーは、コントローラーの仕事を放棄している

多くのMVCフレームワークでは、UIとコントローラーのバインド処理をフレームワークが行っています。上記コードでも見られる通り、そのバインド変数としてフレームワーク特有の形式で受け取っていますが、それを直接モデルに流してしまっているので、フレームワークによるUIバインド変数とモデル層が密結合している状態(モデルはフレームワークがない環境では実行できない)となっています。

これは【関連記事】「疎結合化」の実践手法と失敗事例でも述べた通り、フレームワークと密結合しているモジュールは「個別にテストできない」「フレームワークが存在しない環境に転用できない」といった問題が発生し、上述の関心の分離と同じくソフトウェアの変更容易性を損なう結果となります。

「解釈」「分離」「指示」を行うコントローラーの構築実践

ここまで述べた通り、コントローラーは太りすぎず痩せすぎず、きちんと処理をこなす一つのレイヤであるべきと言えます。

具体的に何をするべきか、どのように記述するべきか。順に検討しながら実際に簡易的なコントローラーを構築していきます。

コントローラーが行う、UIからの情報の「解釈」と「分離」

MVCアーキテクチャを採用するフレームワークにおいて、UIから送られてくる情報はそのフレームワークがバインドしてくれたユーザーの入力結果です。

コントローラーはまずは、このバインド結果を解釈してフレームワークから分離する必要があります。(なぜ分離するのか?それは上述の通りモデル層とフレームワークの密結合を防ぐためです。)

Controller
void IActionResult Controller(FormItem item) {
    //①フレームワークがバインドしたUIの入力情報を「解釈」
    //②文字列などの普遍的な型にセット(フレームワークからの「分離」)
    string email = item["email"];
    string body = item["body"];
    DateTime orderDate = DateTime.Parse(item["order_date"]);
}

コントローラーが行う、モデル層への「指示」

フレームワークから分離された情報に基づいて、モデル層に処理を指示します。

Controller
void IActionResult Controller(FormItem item) {
    //①フレームワークがバインドしたUIの入力情報を「解釈」
    //②文字列などの普遍的な型にセット(フレームワークからの「分離」)
    string email = item["email"];
    string body = item["body"];
    DateTime orderDate = DateTime.Parse(item["order_date"]);

    //③モデル層へ「指示」(ビジネスロジックに値を渡して実行)
    var businessLogicResult = Model.BusinessLogic(email, body, orderDate);
}

痩せすぎコントローラーとの大きな違いは、①~②(解釈・分離)の処理を挟んでいる点です。

このワンクッションにより、モデル層がフレームワークのバインド変数(上記ではitem変数)との関係を断つことに成功しています。その結果、モデル層(ビジネスロジック)は個別にテスト可能であり、このフレームワークが存在しない環境でも実行可能となる恩恵をもたらします。

上述の通り、時代の流れとともにフレームワークが変更されたり、もしかしたらWebアプリやMVCアーキテクチャ自体が時代遅れになる時がくるかもしれません。そういった時にモデル層を手を加えず外部に持ち出せたり個別に変更できるという性質は、ビジネスの観点で極めて重要なのです。

コントローラーが行う、ビュー層への「指示」

ここまでで、UIから受け取った情報を元に処理を実行しました。最後に、その結果に基づいてビュー層に結果を指示しましょう。

Controller
void IActionResult Controller(FormItem item) {
    //①フレームワークがバインドしたUIの入力情報を「解釈」
    //②文字列などの普遍的な型にセット(フレームワークからの「分離」)
    string email = item["email"];
    string body = item["body"];
    DateTime orderDate = DateTime.Parse(item["order_date"]);

    //③モデル層へ「指示」(ビジネスロジックに値を渡して実行)
    var businessLogicResult = Model.BusinessLogic(email, body, orderDate);
    
    //④処理結果によりどのビューを描写するか「指示」
    if (businessLogicResult) {
        return ViewA();
    } else {
        return ViewB();
    }
}

ここまででControllerの処理が一通り構築できました。

見ての通り、大量の行があるファットコントローラーでもなく、処理がほとんどない痩せすぎコントローラーでもない、適切な処理を行うコントローラーができあがっています。

コードの長さが適切であることも重要ですが、コントローラーが存在意義を適切に発揮し「解釈」「分離」「指示」の役割を果たしていることもポイントといえるでしょう。

記事筆者へのお問い合わせ、仕事のご依頼

当社では、IT活用をはじめ、業務効率化やM&A、管理会計など幅広い分野でコンサルティング事業・IT開発事業を行っております。

この記事をご覧になり、もし相談してみたい点などがあれば、ぜひ問い合わせフォームまでご連絡ください。

皆様のご投稿をお待ちしております。

記事筆者へ問い合わせする

※ご相談は無料でお受けいたします。

この記事へのコメント

ニックネーム(任意)

返信通知先Emailアドレス(任意)

本文


* 感想やご意見等、お気軽にコメントください。但し、公序良俗に反するコメントはお控えください。
* 管理者が承認したコメントはこの箇所に公開させていただく可能性がございます。
* 返信通知先Emailアドレスは、筆者のみに通知され、公開されることはありません。返信通知先Emailを入力された場合は、コメントへの返信をこちらに掲載した際に通知させていただきます。その他の目的には使用いたしません。
* スパム対策のため、コメントは日本語のみ受け付けております。

堺財経電算合同会社 小規模IT構築サービス