1. なぜシンプルなロジックが重要なのか

同じ機能を実現するコードでも、複雑に書くことも、シンプルに書くこともできます。良いコードとは「動くこと」に加えて、他の人(未来の自分も含む)が読んですぐ理解できることが求められます。

複雑なロジックは次のような問題を引き起こします。

  • バグが混入しやすい(分岐が多いほどテストすべきパターンが増える)
  • レビューに時間がかかる
  • 修正時に影響範囲を把握しづらい

これは「KISS原則(Keep It Simple, Stupid)」と呼ばれる考え方で、「賢く見える一行」より「誰でも理解できる数行」の方が価値が高いとされています。

以下、PHPでのビフォーアフター例を3つ紹介します。


2. 例1:深いネストを「早期リターン(ガード節)」で解消する

Before(ネストが深く、読みにくい)


// 値引き閾値
define ('DISCOUNT_THRESHOLD',1000);

// 値引き率
define ('DISCOUNT_RATE1',0.9);      // 閾値以上の価格用
define ('DISCOUNT_RATE2',0.8);      // 閾値未満の価格用

//-----------------------------------------------------------------------
// 値引き価格の取得
// $user        : ユーザー情報
// $price       : 購入価格
// 返り値      : 値引き後の価格
//-----------------------------------------------------------------------
function getDiscountPrice($user, $price) {
    if ($user !== null) {
        // ユーザ情報がある場合
        if ($user->isActive()) {
            // ユーザがアクティブな場合
            if ($user->hasCoupon()) {
                // クーポンを持っている場合
                if ($price >= DISCOUNT_THRESHOLD) {
                    // 購入価格が閾値以上の場合
                    return($price * DISCOUNT_RATE2);
                } else {
                    // 購入価格が閾値未満の場合
                    return($price * DISCOUNT_RATE1);
                }
            } else {
                // クーポンがない場合
                return($price);
            }
        } else {
            // ユーザがアクティブでない場合
            return($price);
        }
    } else {
        // ユーザ情報がない場合
        return($price);
    }
}

問題点ifが4段ネストしており、「本来やりたいこと(クーポン所持者への割引計算)」がどこにあるのか読み取りにくい。分岐の組み合わせが多く、テストケースも追いにくい。

After(ガード節で早期リターンし、本質のロジックだけ残す)

// 値引き閾値
define ('DISCOUNT_THRESHOLD',1000);

// 値引き率
define ('DISCOUNT_RATE1',0.9);      // 閾値以上の価格用
define ('DISCOUNT_RATE2',0.8);      // 閾値未満の価格用

//-----------------------------------------------------------------------
// 値引き価格の取得
// $user        : ユーザー情報
// $price       : 購入価格
// 返り値      : 値引き後の価格
//-----------------------------------------------------------------------
function getDiscountPrice($user, $price) {
    if ($user === null) {
        // ユーザ情報がない場合
        return($price);
    }

    if (!$user->isActive()) {
        // アクティブではない場合
        return($price);
    }

    if ($user->hasCoupon()) {
        // クーポンがない場合
        return($price);
    }

    if ($price >= DISCOUNT_THRESHOLD) {
        // 購入価格が閾値以上の場合
            return($price * DISCOUNT_RATE2);
        } else {
            // 購入価格が閾値未満の場合
            return($price * DISCOUNT_RATE1);
        }
    }
}

改善点:「割引の対象外になる条件」を先に弾いてしまうことで、残ったコードは「割引対象者の金額計算」だけになり、本来のロジックが一目で分かる。


3. 例2:複雑な条件式は分割する

Before(条件式がそのまま埋め込まれていて意味が読み取りにくい)


//-----------------------------------------------------------------------
// 公開できるか?
// $article     : 記事情報
// 返り値      : true できる false できない
//-----------------------------------------------------------------------
function canPublishArticle($article) {
    if ($article->status === 'draft'
        && $article->author->isVerified()
        && count($article->body) > 0
        && $article->scheduledAt <= new DateTime()
        // ドラフト状態かつ、確認済みかつ、文章があるかつ、公開予定日時を超えている
    ) {
        return true;
    }
    return false;
}

問題点ifの条件が4つも連結されており、「結局どういう状態なら公開できるのか」を読み解くのに時間がかかる。同じ条件が他の場所にコピペされると、修正漏れも起きやすい。

After(条件に意味のある名前を付けて分解する)

//-----------------------------------------------------------------------
// 公開できるか?
// $article     : 記事情報
// 返り値      : true できる false できない
//-----------------------------------------------------------------------
function canPublishArticle($article) {
    if ($article->status === 'publish') {
        // 公開の場合
        return (false);
    }

    if (!$article->author->isVerified()) {
        // 未確認の場合
        return (false);
    }

    if (count($article->body) == 0) {
        // 文章がない場合
        return (false);
    }

    if ($article->scheduledAt > new DateTime()) {
        // 公開予定日時を超えていない場合
        return (false);
    }
    return(true);
}

改善点:行数は増えるが条件分を分割することで見やすくなる。今後条件が増えたときも改修しやすい。


4. 例3:似たコードの重複を「共通関数」にまとめる(DRY原則)

Before(似た処理がコピペで繰り返されている)

//-----------------------------------------------------------------------
// ユーザーフォーム内容の検証
// $data        : フォームデータ
// 返り値      : なし
//-----------------------------------------------------------------------
function validateUserForm($data) {
    if (empty($data['name'])) {
        // 名前がない場合
        throw new Exception('名前は必須です');
    }
    if (mb_strlen($data['name']) > 50) {
        // 名前が50文字より多い場合
        throw new Exception('名前は50文字以内で入力してください');
    }

    if (empty($data['email'])) {
        // メールアドレスがない場合
        throw new Exception('メールアドレスは必須です');
    }
    if (mb_strlen($data['email']) > 500) {
        // メールアドレスが500文字より多い場合
        throw new Exception('メールアドレスは50文字以内で入力してください');
    }

    if (empty($data['address'])) {
        // 住所がない場合
        throw new Exception('住所は必須です');
    }
    if (mb_strlen($data['address']) > 100) {
        // 住所が100文字より多い場合
        throw new Exception('住所は50文字以内で入力してください');
    }
}

問題点:「必須チェック」と「文字数チェック」という同じロジックが3項目分コピペされている。文字数の上限を変更したくなった場合、3箇所すべてを修正する必要があり、修正漏れの温床になる。

After(共通のバリデーション関数にまとめる)


//-----------------------------------------------------------------------
// フォーム項目の検証
// $label       : ラベル
// $value       : 項目値
// $maxLength   : 最大文字数
// 返り値      : なし
//-----------------------------------------------------------------------
function validateRequiredField($label,$value,$maxLength)
{
    if (empty($value)) {
        // 項目値がない場合
        throw new Exception("{$label}は必須です");
    }
    if (mb_strlen($value) > $maxLength) {
        // 最大文字数を超えている場合
        throw new Exception("{$label}は{$maxLength}文字以内で入力してください");
    }
}

//-----------------------------------------------------------------------
// ユーザーフォーム内容の検証
// $data        : フォームデータ
// 返り値      : なし
//-----------------------------------------------------------------------
function validateUserForm($data)
{
    validateRequiredField('名前', $data['name'],50);
    validateRequiredField('メールアドレス', $data['email'],500);
    validateRequiredField('住所', $data['address'],100);
}

改善点:ロジックを1箇所に集約したことで、文字数上限の変更やエラーメッセージの修正が1箇所で完結する。項目を追加したい場合も1行足すだけで済む。


5. まとめ:シンプルなロジックにするための3つの視点

視点 手法 効果
ネストを浅くする ガード節・早期リターン 本質的なロジックが目立つようになる
条件式を単純にする 条件分の分解 「何を判定しているか」が一目で分かる
重複をなくす 共通関数への抽出(DRY) 修正箇所が1つになり、修正漏れを防げる

ポイント:シンプルにする、とは「行数を減らす」ことではなく、「読んだ人が意図をすぐに理解できる形にする」ことです。時にはコードが数行増えても、分割・命名によって読みやすくなるなら、それは良いリファクタリングです。