Pythonの「if文のネスト」深くなっていませんか? 読みやすいコードの書き方を徹底解説

こんにちは。ゆうせいです。

プログラムを書き始めたばかりのころ、特にPythonのような言語で、条件分岐(if文)がどんどん深くなってしまった経験はありませんか?

「もしAだったら、その中でBだったら、さらにCだったら…」

こんな風に、if文の中にif文が重なっていく状態を「ネスト(入れ子)が深い」と呼びます。ついついやってしまいがちですが、この「ネストが深いコード」は、実はプロの世界ではあまり歓迎されません。

なぜダメなのでしょうか? そして、どうすればスッキリと読みやすいコードが書けるようになるのでしょうか?

今日は、新人エンジニアの皆さんが陥りがちな「深すぎるネスト」の問題点と、Pythonを使った具体的な改善テクニック(傾向と対策!)について、詳しくお話ししていきますね。

なぜ「ネストが深い」と良くないのか?

ネストが深いコードには、大きく分けて3つの問題点があります。

1. とにかく読みづらい!(可読性の低下)

一番の問題は、コードが非常に読みづらくなることです。

ネストが深くなると、インデント(字下げ)もどんどん深くなりますよね。コードを読む人は、「今、自分はどの条件分岐の中にいるんだっけ?」と、常にインデントの深さを目で追いながら、頭の中で条件を記憶し続けなければなりません。

例えるなら、ロシアの民芸品「マトリョーシカ」を想像してみてください。

  • ネストが浅いコード: 2〜3個のマトリョーシカを開ければ、中身(本当にやりたい処理)にたどり着く。
  • ネストが深いコード: 10個も11個もマトリョーシカを開け続けないと、中身にたどり着かない。

あるいは、分岐だらけのダンジョンです。どの道がどの部屋に繋がっているのか、地図なしではすぐに迷子になってしまいますよね。

2. 修正が怖い!(保守性の低下)

読みづらいコードは、当然ながら修正するのも大変です。

「ここの条件を少し変えたい」と思っただけなのに、ネストが深すぎると、その変更が他のどの部分に影響するのか把握するのが困難になります。

例えるなら、複雑に絡み合った毛糸玉です。一本の糸を引っ張ったつもりが、予期せぬ別の場所が固く結ばれてしまうかもしれません。

3. バグが潜みやすい!(品質の低下)

ネストが深くなると、条件の組み合わせが爆発的に増えていきます。すべてのパターンを正しく網羅できているか、人間の目で確認するのが難しくなり、ロジックの漏れや間違い(バグ)が潜みやすくなるのです。

「AだけどBではなくCの場合」は考慮したけれど、「AだけどBだけどCではない場合」を見落としていた…なんてことが起こりやすくなります。


なぜネストは深くなるのか? (傾向)

では、どうして私たちはネストの深いコードを書いてしまうのでしょうか。

多くの場合は、「仕様をそのまま素直にプログラムに置き換えようとする」ことが原因です。

「もし、ユーザーがログインしていたら(if user_is_login:)…」

「その中で、もし、ユーザーが管理者権限を持っていたら(if user.is_admin:)…」

「さらに、もし、リクエストがPOSTだったら(if request.method == 'POST':)…」

このように、条件を一つずつ追加していくと、自然とネストは深くなっていきます。また、後から仕様変更が加わったときに、「とりあえずこのif文の中に追加しておこう」と安易に付け足してしまうことも、ネストが深くなる大きな原因です。


ネストを浅くする具体的なテクニック (対策)

お待たせしました! ここからは、Pythonを使ってネストを浅くするための具体的なテクニックを2つ、紹介します。

対策1:早期リターン(ガード節)

これは最も強力で、すぐにでも実践してほしいテクニックです。

「早期リターン(Early Return)」や「ガード節(Guard Clauses)」と呼ばれます。

これは、「メインの処理を実行するための条件が整っていないなら、さっさと関数(処理)を終了させてしまおう!」という考え方です。

悪い例:ネストが深いコード

まずは、ネストが深い例を見てみましょう。あるユーザーが記事を編集できるかをチェックする関数です。

def can_edit_article(user, article):
    # ユーザーがログインしているか?
    if user.is_authenticated:
        # ユーザーが記事の作者か?
        if user.id == article.author_id:
            # 記事が公開前か?
            if not article.is_published:
                print("編集可能です")
                return True
            else:
                print("公開済みの記事は編集できません")
                return False
        else:
            print("他人の記事は編集できません")
            return False
    else:
        print("ログインしてください")
        return False


どうでしょう? if文が3段階もネストしていますね。インデントが深くて、本当にやりたい「編集可能です (return True)」という処理にたどり着くまでが長いです。

良い例:早期リターンを使ったコード

これを早期リターンで書き換えてみます。

def can_edit_article(user, article):
    # 【ガード1】ログインしていなければ即終了
    if not user.is_authenticated:
        print("ログインしてください")
        return False

    # 【ガード2】記事の作者でなければ即終了
    if user.id != article.author_id:
        print("他人の記事は編集できません")
        return False

    # 【ガード3】公開済みなら即終了
    if article.is_published:
        print("公開済みの記事は編集できません")
        return False

    # すべてのチェックを通過した!
    # これがメインの処理
    print("編集可能です")
    return True

いかがですか?

ネストが一切なくなり、上から下へまっすぐ読み下せるようになりました。

「ダメな条件(エラー条件)」を最初にはじいていくことで、関数の最後には「本当にやりたいメインの処理」だけが残ります。

この書き方なら、後から「ブロックされているユーザーは編集できない」という条件を追加するのも簡単ですよね。ガード節を一つ追加するだけです。

対策2:処理を関数に切り出す(関数化)

もう一つのテクニックは、if文の中の処理が長くなりそうな場合に有効です。

ネストが深くなる原因として、「if文の中で、さらに複雑な処理や条件分岐が始まってしまう」ことがあります。

そういう時は、その「複雑な処理」を別の関数として外に追い出してしまいましょう!

悪い例:if文の中でごちゃごちゃ処理

def process_data(data):
    if data['type'] == 'A':
        # タイプAの時の複雑な処理
        result = data['value'] * 1.1
        if result > 100:
            # さらに分岐...
            pass
        else:
            # さらに分岐...
            pass
        print(f"タイプAの処理結果: {result}")

    elif data['type'] == 'B':
        # タイプBの時の複雑な処理
        result = data['value'] / 2
        if result < 10:
            # さらに分岐...
            pass
        else:
            # さらに分岐...
            pass
        print(f"タイプBの処理結果: {result}")

    else:
        print("不明なタイプです")

if文のブロックが長くなると、全体の見通しが悪くなります。

良い例:処理を関数に切り出す

# タイプAの処理を関数化
def process_type_a(value):
    result = value * 1.1
    if result > 100:
        pass
    else:
        pass
    print(f"タイプAの処理結果: {result}")

# タイプBの処理を関数化
def process_type_b(value):
    result = value / 2
    if result < 10:
        pass
    else:
        pass
    print(f"タイプBの処理結果: {result}")


def process_data(data):
    if data['type'] == 'A':
        process_type_a(data['value'])

    elif data['type'] == 'B':
        process_type_b(data['value'])

    else:
        print("不明なタイプです")

このように、if文の中身を関数として外に出す(専門用語で「リファクタリングする」と言います)ことで、process_data 関数の役割が「データのタイプに応じて、適切な処理関数を呼び出すこと」だけになり、非常にスッキリしました。

それぞれの処理内容を修正したい時も、process_type_aprocess_type_b の関数だけを見れば良いので、保守性も格段に上がります。


今後の学習の指針

ネストを浅くする技術は、プログラミングの「読みやすさ」を追求する上で欠かせないスキルです。

今日紹介した「早期リターン」と「関数化」は、その第一歩です。

まずは、自分の書いたコードを見直して、「このネスト、浅くできないかな?」と考えてみてください。

  • 「エラー条件を先にはじき出せないか?」(早期リターン)
  • 「このif文の中の処理、長すぎないか?」(関数化)

この2つの視点を持つだけで、あなたのコードは劇的に読みやすくなるはずです。

さらにステップアップしたい場合は、「リーダブルコード(Readable Code)」という考え方について書かれた本を読んでみることを強くお勧めします。コードの「質」を高めるための、たくさんのヒントが得られますよ。

そして、最も効果的な学習方法は、他の人のコードを読み、自分のコードを他の人(先輩や同僚)に読んでもらうこと、すなわち「コードレビュー」です。

読みやすいコードを書く意識を持って、日々のコーディングに取り組んでみてくださいね!

セイ・コンサルティング・グループの新人エンジニア研修のメニューへのリンク

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。