はむはむエンジニアぶろぐ

このブログのコンセプトは"ハッキングの為なら愛する家族を傷つけることをいとわない" 自分にとってエンジニアリングは "手段ではなく生きる目的" である

ファットコントローラはFacadeパターンで撲滅しよう

f:id:secret_hamuhamu:20150816222337j:plain
MVCのコントローラのレガシーコードによくあるのが、ファットコントローラです。
最初は、単純なロジックだけだったのでコントローラに書かれていたロジックが月日を追うごとに肥大化して誰も読めなくなったというのはあるあるです。
ファットコントローラのビジネスロジックのテストコードを書くのはViewが絡む為、難しいです。
なので、ファットコントローラ = テストがないと言ってもいいでしょう。

そんな、ファットコントローラさんはGoFのデザインパターンの一つFacadeで撲滅できます。

ファットコントローラ

今回、撲滅したいファットコントローラ。

要件

  • ユーザが食べ物と購入数を指定し支払額を送信すると、購入額の計算をし商品の支払いを行う
  • クーポンの発行も行う
  • 何かしらエラーが発生したらログを残す
<?php
class TopController
{
    public function buy()
    {
        $userId   = $_GET['user_id'];
        $food     = $_GET['food'];
        $num      = $_GET['num'];
        $money    = $_GET['money'];  // 購入者支払額

        try {
            $calcer = new Calcer();                 // 購入金額計算クラス
            $payment = $calcer->calc($food, $num);  // 購入金額

            $resistor = new Resistor();
            $resistor->pay($payment, $money);       // 商品の支払いを行う

            $coupon = new Coupon();
            $coupon->publish($userId);              // クーポンの発行を行う
        } catch (Exception $e) {
            $logger = new Logger();
            $logger->write($e->getMessage());      // なんかエラーが起こったらログを書く
            // 500エラーページを返す
            $this->redirect('500.php');
        }
    }

この程度なら、ファットコントローラと呼べないかも。
ファットコントローラは、コントローラ内部でif文の嵐です。
ですが、こいつはファットコントローラになり得る予兆があります。

Facadeパターンとは?

GoFのデザインパターンの一つ。
Facadeパターンは、何も難しいことはないです。
ただ単純に先ほどのTopcontrollerのbuyアクションでやっていたことをやるだけです。

  • Calcerのcalcメソッドを呼ぶ
  • Resistorのpayメソッドを呼ぶ
  • Couponクラスのpublishメソッドを呼ぶ

Facadeパターンは、この呼び出し手順を守るだけです。

  • 何かしらエラーが起こったらログに書く

ログを書くのは、手順とは異なりますがコントローラでごちゃごちゃしたことやりたくない場合は有効です。
今回ログの書き出しは、全部まとめてますが例外の種類に応じてログのフォーマットを変えたい場合などコントローラでやると大変です。

先ほどのTopControllerのbuyメソッドの中身をFacadeパターンに置き換える。

クラス図

Facadeパターン適用前と適用後のクラス図

適用前

f:id:secret_hamuhamu:20150816222427p:plain

適用後

f:id:secret_hamuhamu:20150816222516p:plain

Facadeの実装

BuyFacadeクラス

<?php
class BuyFacade
{
    public function execute($userId, $food, $num, $money)
    {
        try {
            $calcer = new Calcer();                 // 購入金額計算クラス
            $payment = $calcer->calc($food, $num);  // 購入金額

            $resistor = new Resistor();
            $resistor->pay($payment, $money);       // 商品の支払いを行う

            $coupon = new Coupon();
            $coupon->publish($userId);              // クーポンの発行を行う
        } catch (Exception $e) {
            $logger = new Logger();
            $logger->write($e->getMessage());      // なんかエラーが起こったらログを書く
            throw new Exception('ほげほげ');
        }
    }
}

Facadeパターン適用後のTopcontroller

<?php

class TopController
{
    public function buy()
    {
        $userId   = $_GET['user_id'];
        $food     = $_GET['food'];
        $num      = $_GET['num'];
        $money    = $_GET['money'];  // 購入者支払額

        $facade = new BuyFacade();
        try {
          $facade->execute($userId, $food, $num, $money);
        } catch (Exception $e) {
            // 500エラーページを返す
            $this->redirect('500.php');
        }
    }

buyメソッドがスッキリした。
Controllerというのは、 ビジネスロジック つまりModelでやるお仕事をやるべきではない。

Controllerは、ユーザからリクエストパラメタを受け取ってそれをModelに受け渡す。
Modelの処理結果をViewに渡したり、処理に失敗した際はエラーページに遷移させる。

Controller中に if文 が出現していたら絶対悪とはいえないけど、きな臭い。

まとめ

Facadeパターンで、ファットコントローラがスリムになったよ。
ただControllerからModelにビジネスロジックを移す事に成功はしたが、テストがない。
テスト無きコードはレガシーであるし品質も担保できない。

Facadeパターンは、クラスの依存をFacadeが引き受けて閉じ込めたものである。
依存が生じたクラスをテストするには工夫が必要で DI というテクニックが要る。

DIに関しては、後日まとめようと思います。

おすすめの本

オブジェクト指向における再利用のためのデザインパターン

オブジェクト指向における再利用のためのデザインパターン

  • 作者: エリックガンマ,ラルフジョンソン,リチャードヘルム,ジョンブリシディース,Erich Gamma,Ralph Johnson,Richard Helm,John Vlissides,本位田真一,吉田和樹
  • 出版社/メーカー: ソフトバンククリエイティブ
  • 発売日: 1999/10
  • メディア: 単行本
  • 購入: 21人 クリック: 711回
  • この商品を含むブログ (210件) を見る

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

  • 作者: Eric Freeman,Elisabeth Freeman,Kathy Sierra,Bert Bates,佐藤直生,木下哲也,有限会社福龍興業
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2005/12/02
  • メディア: 大型本
  • 購入: 14人 クリック: 362回
  • この商品を含むブログ (97件) を見る

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)

実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる (Object Oriented SELECTION)