早期リターンのすゝめ

沖縄チームのキリです。突然ですがあなたはやたらネストの深いif文に悩まされたことはありませんか?

コーディングにおいて読みやすさは正義です。とにかく動けばいいからとif文の乱雑したコードをデプロイせざるを得ないこともあるかもしれません。ですが機能の追加や改修となればコードを書いている時間よりもコードを読んでる時間が多くを占めます。『AかつBまたはAかつCならばDとEを満たす場合、あるいはFかGのときにHとIをともに満たす場合はこの処理』のようなわけのわからない分岐に脳のリソースを割かれるのは無駄以外の何物でもありません。

こんなコードになっていませんか?

以下のコードは冗長です。ネストが不必要に深くなり可読性が低いです。

function process()
{
    $ok = true;
    if ($flagA === true) {
        // 処理1
    } else {
        $ok = false;
    }

    if ($ok) {
        if ($flagB === true) {
            // 処理2
        } else {
            $ok = false;
        }
    }

    if ($ok) {
        // 処理3
        return true;
    } else {
        return false;
    }
}

早期リターンを使う

よく見てみると$okがfalseのときは特に処理をせず最終的にfalseを返すようになっています。つまり$ok = falseを代入する時点でreturn falseすることと同義です。

function process()
{
    if ($flagA !== true) {
        return false;
    }

    // 処理1

    if ($flagB !== true) {
        return false;
    }

    // 処理2

    // 処理3

    return true;
}

$okを使わず早期リターンするようになったことでネストが減り読みやすくなりました。

カスタムエラーを使う

処理が複雑な場合はカスタムエラーを使うのも良い方法です。パターンごとにカスタムエラーを作成しエラーの種類によって処理を変えるなど柔軟な対応も可能です。早期リターンと比べると考えることが多く少しむずかしいかもしれませんが例外処理はしっかり扱えるようになっておくと役に立つことが多いはずです。

class NotFlaggedException extends RuntimeException
{
}

function process()
{
    try {
        if ($flagA !== true) {
            throw new NotFlaggedException('A is not flagged.');
        }

        // 処理1

        if ($flagB !== true) {
            throw new NotFlaggedException('B is not flagged.');
        }

        // 処理2

        // 処理3

        return true;
    } catch (NotFlaggedException $e) {
        $_SESSION['error'] = $e->getMessage();
        return $e;
    }
}

ちなみにPHPには組み込みの例外がいくつかありますが、実行時に発生する例外を表すRuntimeExceptionとロジックの誤りを表すLogicExceptionの2種類を抑えておき、それぞれを拡張して使うとコードの意図が伝わりやすくて良いと思います。RuntimeExceptionはネットワークやメモリのような環境を起因としたものや、外部入力によるものなどプログラムのコードが原因ではない例外です。LogicExceptionはウェブサービスの開発ではあまり使うことはないかもしれませんが、コードの間違いが原因となる例外で、例えば自作のライブラリの使い方が間違っている場合にスローさせます。

この例ではNotFlaggedExceptionというカスタムエラーを作りましたが、どのエラー名とカスタムエラーを投げるかでコメントを書くまでもなく『このような動作は想定外です』という情報をコードに持たすことができます。

カスタムエラーはあくまでも例外処理であって正常な処理で使用されるべきものではありません。ここで失敗したら以降の処理はリソースの無駄なのでさっさと処理を中断してエラー画面を表示しましょう、というような扱いです。キャッチすることはできますが必ずキャッチして処理を書くものではありません。その意味ではフラグが立っていないという状態は正常系なので上記のコードは例としては不適切です(この処理を実行している時点でこのフラグが立っていないはずがないというシステムなら例外になり得ます)。カスタムエラーは便利ですが、本来例外を投げるべきではない処理でスローしてしまったりキャッチ頼りの分岐処理になってしまうと逆に複雑に入り組んだスパゲッティコードになってしまうので、用法用量を守り適切な取り扱いを心がけましょう。