なぜコードは無駄だらけになるのか? - 問題の本質を見抜く重要性

はじめに

「とりあえず動くけど、読みにくくて無駄な処理が多い…」そんなコードに心当たりはありませんか?バグ修正や機能追加を繰り返すうちに、コードが複雑化し、気づけば誰も触りたくない「ぐちゃぐちゃコード」になっていることがあります。

このページでは、なぜそのようなコードが生まれてしまうのか、そしてどうすれば改善できるのかを、具体的なジャンプ処理の例を通して解説します。重要なのは、「問題の本質」を理解することです。

実例:ぐちゃぐちゃなジャンプ処理

以下は、あるゲームのキャラクタージャンプ処理の(よくない)例です。一見動いているように見えますが、多くの問題を含んでいます。

void update() {
    // ジャンプキーが押された時の処理
    if (jumpKeyPressed) {
        // まだジャンプしていない場合
        if (!isJumping) {
            // 地面にいる場合のみジャンプ開始(空中ジャンプ防止)
            if (velocityY == 0) {
                isJumping = true;
                velocityY = -10; // 上向きの初速度
            }
        } else {
            // すでにジャンプ中の場合(キーを離したらジャンプ中断の意図?)
            if (isJumping) { // この if(isJumping) は冗長
                if (velocityY < 0 && jumpKeyPressed == false) { // この条件、ここにあるべきか?
                    isJumping = false;
                }
            }
        }
    }

    // ジャンプ中にキーが離されたらジャンプ終了(上のelse節と重複?)
    if (isJumping && velocityY < 0 && jumpKeyPressed == false) {
        isJumping = false;
    }

    // ジャンプ中なら物理演算を適用
    if (isJumping) {
        positionY += velocityY; // 位置を更新
        velocityY += 1; // 重力(下向きの加速度)
    }
}

このコードには、条件の重複冗長なチェック意図が不明確な分岐が含まれています。なぜこのようなコードになってしまったのでしょうか?

なぜこうなったのか? - 開発者の心理的経緯

このようなコードは、多くの場合、場当たり的な修正の積み重ねによって生まれます。

状態開発者の内心結果として追加されたコード(イメージ)
最初の実装「キーが押されたらジャンプさせればいいや」if (jumpKeyPressed) { velocityY = -10; }
不具合①発覚「あれ?空中で何回もジャンプできちゃうぞ…」ジャンプ中か判定するフラグ isJumping を導入し、if (!isJumping) を追加。
不具合②発覚「地面にいるときだけジャンプさせないと!」速度が0(地面にいる)かチェックする if (velocityY == 0) を追加。
不具合③発覚「ジャンプ中にキーを離したら、上昇を止めたいな…」else 節や、別の場所に if (jumpKeyPressed == false) の条件を追加。
不具合④発覚「なんか条件が被ってる?でも直すと怖いし…」さらに条件分岐を追加したり、既存の条件を複雑化。
現在「とりあえず動いてるみたいだから、これでいいか…」意図不明で複雑怪奇なコードの完成。

このプロセスで問題なのは、発生した「現象」に対して「対処」するコードを都度追加しているだけで、「ジャンプ」という機能全体の「仕組み」や「状態」を整理・再設計していないことです。

本質的な問題:「ジャンプ」という処理の「状態」を理解していなかった

このコードの根本的な問題は、「ジャンプ処理とは何か」「どのような状態があり、どう遷移するのか」という本質を捉えずに、目先のバグだけを潰そうとした点にあります。

結果として、条件が複雑に絡み合い、重複や矛盾が生まれ、コードの可読性とメンテナンス性が著しく低下してしまったのです。

改善例:本質を踏まえたコード

ジャンプ処理の本質(状態と遷移)を捉え、関心事を分離することで、コードは以下のように改善できます。

// --- 状態判定のためのヘルパー関数 ---

bool canStartJump() {
    // ジャンプを開始できる条件:キーが押され、ジャンプ中でなく、地面にいる(速度0)
    return jumpKeyPressed && !isJumping && velocityY == 0;
}

bool shouldEndJumpEarly() {
    // ジャンプを早期終了する条件:ジャンプ上昇中(速度が負)にキーが離された
    return isJumping && velocityY < 0 && !jumpKeyPressed;
}

// --- 状態遷移と処理を実行する関数 ---

void startJump() {
    // ジャンプ開始処理
    isJumping = true;
    velocityY = -10; // 上向きの初速度を与える
    // (効果音再生などもここで行う)
}

void endJumpEarly() {
    // ジャンプ早期終了処理(上昇を止める)
    if (velocityY < 0) { // 上昇中なら速度をリセット(例)
        velocityY = 0;
    }
    // isJumping = false; はここではしない(物理演算は継続させるため)
    // あるいは上昇力を弱めるなどの調整
}

void applyJumpPhysics() {
    // ジャンプ中の物理演算(重力など)
    positionY += velocityY;
    velocityY += 1; // 重力加速度

    // 地面に到達したら着地処理
    if (positionY >= groundLevel && velocityY > 0) { // 地面より下に行かず、下降中なら
        positionY = groundLevel;
        velocityY = 0;
        isJumping = false; // ジャンプ状態終了
        // (着地エフェクトなどもここで行う)
    }
}

// --- メインの更新処理 ---

void update() {
    // 1. ジャンプを開始できるかチェックし、可能なら開始する
    if (canStartJump()) {
        startJump();
    }

    // 2. ジャンプを早期終了すべきかチェックし、該当すれば終了処理を行う
    if (shouldEndJumpEarly()) {
        endJumpEarly();
    }

    // 3. ジャンプ中(または落下中)であれば、物理演算を適用する
    if (isJumping) { // isJumping は「空中状態」を示すフラグとして使う
        applyJumpPhysics();
    }
}

※上記コードは一例であり、実際のゲームループや物理エンジンの設計によって詳細は異なります。(例:isJumping の代わりに isGrounded フラグを使うなど)

改善のポイント解説

改善後のコードは、元のコードのどこがどのように良くなったのでしょうか?

これらの改善はすべて、「ジャンプ処理の本質(状態、遷移、条件、アクション)」を理解し、それをコード構造に反映させた結果です。

まとめ:本質を理解しないと「無駄」が生まれる理由

なぜ、問題の本質を理解せずにコードを書くと、無駄だらけになってしまうのでしょうか?

理解不足な点コードに起こる問題結果
状態の設計をしていないどの変数が何を示し、どう変化するのか曖昧になる場当たり的なフラグや条件が乱立し、矛盾や重複が発生する
「なぜ」その処理が必要かを考えない目の前の現象(バグ)を抑えるためだけの対症療法的なコードになる根本原因が解決されず、別の箇所で新たなバグを生む。コードが複雑化する。
処理の目的や条件を言語化できない関数名や変数名が不適切になったり、処理の塊を適切に分割できない再利用できず、似たようなコードがコピペされる。修正時にどこを直せばいいか分からない。
全体の構造を意識しない部分的な修正が全体の整合性を破壊する可能性があることに気づかない変更に弱く、修正コストが非常に高い「負債コード」が積み上がる。

バグ修正や仕様追加に追われると、つい目の前の現象に対処するコードを書きがちです。しかし、一度立ち止まって「この機能の本質は何か?」「どのような状態があり、どう遷移すべきか?」を考えることが、長期的に見て読みやすく、メンテナンス性の高いコードを書くための鍵となります。

無駄のない、意図が明確なコードを目指しましょう。