home
スポンサーリンク
2015-09-30

オブジェクト指向言語を使っても、オブジェクト指向設計にはならない! MVCモデルとオブジェクト指向

どうも。

偉大なる先人(ポンコツPHPプログラマ)が作った秘伝のタレ(仕様書のないポンコツPHPコード)と日々格闘している、チンポコエンジニアです。

最近、仕事が辛い(つらい)です。秘伝のタレが辛い(からい)です。もう、ウチの秘伝のタレは大分カビ(バグや脆弱性)が生えてきていて使い物にならなくなっているのに、未だなお、経営層は客に提供し続ける方針のようで、私は毎日憂鬱な気持ちでそのカビを除去し続けています。そして客は腹を下しています。

なぜ、秘伝のタレは秘伝のタレと蔑称で呼ばれるまでになってしまったのか(ホントに社内で呼ばれています)。なぜ、もう簡単に手がつけられないほどに複雑化してしまったのか。その原因を考察してみます。

前提

そのシステムの利用言語はPHPであり、Webサーバー(Apache HTTP Server)経由でユーザーに機能を提供する、いわゆるWebアプリケーションというやつです。

利用言語はPHPの5系を使っているため、当然オブジェクト指向が実装されています。また、PHPフレームワークに、CakePHPが使用されています。

MVCモデル設計

CakePHPはMVCモデルを採用したフレームワークです。MVCモデルが何かわからない人は、これとかこれを参照してみてください。

CakePHPを利用している以上、MVCモデルに則って設計されます。整理し、わかりやすくするために。でも、結局例のプロジェクトでは、整理できずに破綻しました。

一般にMVCは様々なフレームワークに利用されるほど、優れたアーキテクチャーですが、なぜ破綻してしまったのでしょうか。それは、一言でいうと、「オブジェクト指向設計ではなかったから」です。

オブジェクト指向設計

オブジェクト指向の概念は、ここここなどで詳しく解説しているため、割愛します。

MVCモデル設計とオブジェクト指向設計は、全く異なる概念です。

MVCモデルで正しく設計していれば、データはM(モデル)が入出力します。つまり、オブジェクト指向でいうところの「オブジェクト」に一番近い概念が「モデル」ということになります。

しかしながら、通常のMVCでは、「モデル」はカプセル化しなければならないといったルールはなく、単にデータベースとの接続インタフェースでしかありません。つまり、何も考えなければ「オブジェクト」ではなく単なる「データ」にしか過ぎないのです。

ここを、「オブジェクト」として外部から利用しやすい形に設計することが、MVCでは最も重要なことだと考えています。

言葉で説明してもわかりにくいので、例を挙げます。以下のコードは、例の秘伝のタレで、コントローラに記述されていました。

$this->loadModel('Category');
$items = $this->Category->find('list', array(
    'conditions'=>array(
        'category_id'=>array_keys($items2)
    ),
    'order'=>array(
        'sort'=>'asc',
        'id'=>'desc'
    )
));

$items2 とかいうクソ命名の変数があるのはこの際気にしないでください。

このfindメソッドはデータベースにSELECT文を送るメソッドですが、これをコントローラに直接記述している時点で論外です。オブジェクト指向どころか、MVCの設計上もよろしくないです。コントローラからSQL発行するバカがプログラマを名乗れるのかすら疑問です。

なので、まず、このコードをモデルに移動するとします。このときに、オブジェクト指向の考え方を使います。

まず1つのモデルクラスのファイルがどういう役割を持つのかを考え、きちんとオブジェクト指向設計します。ここで、例の秘伝のタレのように、以下のようなコードにしたら絶対にダメです。

class Category extends CategoryAppModel{
    public function getList($items2) {
        $this->loadModel('Category');
        $items = $this->Category->find('list', array(
            'conditions'=>array(
                'category_id'=>array_keys($items2)
            ),
            'order'=>array(
                'sort'=>'asc',
                'id'=>'desc'
            )
        ));
        return $items;
    }
    /*以下、延々と同様のパブリック関数が続く*/
}

わけわかんないクラスを継承しているのはこの際無視してください(設計以前の問題が多すぎてごめんなさい)。エラー処理などの関係ない処理は省略しています。

略しすぎてどういうことかわからないかもしれませんが、「モデルを単なる関数の集合として使うな! オブジェクトとして使え!」ということです。データを取得したのなら、きちんとプロパティに格納し、再利用可能なようにするべきであり、それらのプロパティにはきちんとゲッターとセッターを用意すべきということです。

上記のような関数の羅列にしてしまうと、同じSQLを1度のリクエストで何度も発行する可能性もありますし、オブジェクトのカプセル化が中途半端なので、結局モデルのソースを読まないと誰も利用できないモデルが出来上がります。それはオブジェクト指向の考え方に反する設計であり、結局書いた人しか自由に利用できないクラスが出来上がります。秘伝のタレです。

まあ、この例では数行なので読めばいいですが、規模が大きくなってくると、このような設計では追いつかなくなります。モデルからモデルを呼ぶようなこともあり、返り値に他のクラスのインスタンスを返すこともできるようになるので、きちんとオブジェクト化しておくべきです。

class Category extends CategoryAppModel{
    private $list = null;

    function __construct() {
        parent::__construct();
        $this->loadModel('Category');
    }

    public function getList($items2) {
        if(empty($this->list)){
            $this->list = $this->Category->find('list', array(
                'conditions'=>array(
                    'category_id'=>array_keys($items2)
                ),
                'order'=>array(
                    'sort'=>'asc',
                    'id'=>'desc'
                )
            ));
        }
        return $this->list;
    }
    /*以下、続く*/
}

あと、個人的にはSQLのカスタマイズ性と、開発者の学習コスト低減のため、findメソッドではなくqueryメソッドでSQLを直接書くべきだと思います。フレームワークを熟知していればいいですが、たまに変なクエリを投げることがあるので。開発チーム全員がその仕様を理解しているわけはないですし。

まとめ

さて、すごく中途半端な感じになってしまいましたが、言いたかったのは、「フレームワークがMVCモデルだからといって、使っている言語がオブジェクト指向言語だからといって、ちゃんとオブジェクト指向的な設計を意識しないと、規模が大きくなったときに破綻しちゃうよ!」ってことです。

まあ、うちの会社のソースコードは、見てのとおりそれ以前の他の問題が山積みなんですけどね。データベースも一切インデックス張ってないし、クエリも全てフレームワークに任せっきりで使わないカラムも呼び出してるし、1ページの呼び出しでなぜか同じSQL何度も発行してるし……。

みなさんは、あとあと保守する人のことも考えて、拡張しやすい設計をするようにしましょう……。とほほ……。

コメントを残す

スポンサーリンク
スポンサーリンク
ツイートする
シェアする
オススメする
ブックマークする
購読する