コントローラを単純化するために例外を使う

このエントリーをはてなブックマークに追加
はてなブックマーク - コントローラを単純化するために例外を使う
Share on Facebook

2009年6月12日の記事でだいぶ古いのですが、CakePHPのコードを良くするTIPSがあったので訳してみました。

Simplifying Controller logic with Exceptions | Mark Story

要約すると、「モデル内で例外を吐くようにすると、エラーコードが読みやすくなり、コントローラのテストもしやすくなるよ」ということです。


増大するコードと格闘する日々・・・どうにかクリエイティブな方法で解決したい。そんな悩みを、モデルのメソッドから例外を投げる方法で解決しました。別にビックリするようなものではないです。ただfalseを返すより、ちょっと便利な点がいくつかあります。

第一に、if-elseを減らすことができます。第二に、エラーが起こる部分のソースにエラーメッセージを書くことができるので、何度も使うメソッドならエラーメッセージを重複させずに書くことができます。

例えば以下のメソッドは、リモートアドレスからリソースをダウンロードし、 ローカルファイルシステムとデータベースに記録するものです。(訳注: ダウンロードの時点で失敗すると例外を、保存に成功するとtrueを、保存に失敗するとfalseを返します。)

public function downloadResource($url, $userId, $type = 'image') {
    if (!isset($this->_fetchMimes[$type])) {
        throw new OutOfBoundsException(__('Invalid media type', true));
    }
    $this->_loadSocket($url);
    $resource = $this->Socket->get($url);
    if (!isset($this->Socket->response['header']['Content-Type'])) {
        throw new OutOfBoundsException(__('Submitted url has no Mime-Type', true));
    }
    $allowedContentTypes = $this->_fetchMimes[$type];
    if (!in_array($this->Socket->response['header']['Content-Type'], $allowedContentTypes)) {
        throw new OutOfBoundsException(__('Submitted url has an invalid Mime-Type', true));
    }
    $newFile = array(
        'File' => array(
            'file' => $this->_saveFetchedFile($resource, $url, $this->Socket->response['header']['Content-Type']),
            'user_id' => $userId,
            'title' => $url,
        )
    );
    $this->create($newMedia);
    if ($this->save()) {
        return true;
    }
    return false;
}

見てわかる通り、起きてはならないことが起きた時には OutOfBoundsExceptions を投げています。最近のバージョンのPHPに含まれているSPLライブラリ (訳注: 標準PHPライブラリ) には、便利なクラスが多数用意されています。もちろん自分で例外を作ることもできますが、たいていは組み込みの例外を使うだけで十分でしょう。

これを使えば、コントローラのメソッドをかなりスッキリさせることができます。何重ものifでチェックする必要が無くなります。また、シンプルでエラーの見通しも良いコードを書くことができます。

try {
    $this->File->downloadResource($this->data['File']['url'], $this->Auth->user('id'), 'image');
    //do some additional file handling and data processing.
 
    $this->Session->setFlash(__('File uploaded successfully', true));
} catch(OutOfBoundsException $e) {
    $this->Session->setFlash($e->getMessage());
}
$this->redirect(array('action' => 'index'));

このメソッドの例外発生をテストするのは簡単です。assertFalse を使うのではなく、pass()とfail()を使えばいいのです。

// (訳注) 不正なURLを送った時に例外が発生することをテストする
try {
    $this->File->downloadResource('http:/bogus.com/', 1, 'image');  // 間違ったURL
    $this->fail('No exception thrown with bogus arguments');
} catch (Exception $e) {
    $this->pass('Exception thrown');
}

例外を使うと便利になることは多いです。だからと言って、falseを返すタイプのメソッドすべてを例外を返すようにする必要はないでしょう。たとえばヘルパから例外を返すようにすると、要望を満たしてくれるよりも苦痛を感じることのほうが多くなるでしょう。

どんなツールもそうですが、正しく使えばメンテナンス性の良いコードを生み出すことができるのです。

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>