深くネストした if 文は、コードの可読性を著しく損なう。早期リターン(early return)とガード節(guard clause)を使えば、ネストを減らしてフラットな構造を維持できる。この記事では、ネスト削減のテクニックと、その適用判断について解説する。
問題:深いネスト
典型的な例として、ユーザー認証とデータ処理を行う関数を見てみよう。
# 深いネスト(悪い例)
def process_order(user, order):
if user is not None:
if user.is_authenticated:
if user.has_permission("create_order"):
if order is not None:
if order.is_valid():
if order.items:
# ここでようやく本題の処理
total = sum(item.price for item in order.items)
return {"status": "success", "total": total}
else:
return {"status": "error", "message": "No items"}
else:
return {"status": "error", "message": "Invalid order"}
else:
return {"status": "error", "message": "Order is None"}
else:
return {"status": "error", "message": "Permission denied"}
else:
return {"status": "error", "message": "Not authenticated"}
else:
return {"status": "error", "message": "User is None"}
ネストが 6 段階にもなり、本題の処理がどこにあるか見つけにくい。条件の else 分岐がどの if に対応するかも追いづらい。
解決策:ガード節と早期リターン
条件を満たさない場合に早期リターンする「ガード節」を使うと、ネストを解消できる。
# ガード節を使った改善(良い例)
def process_order(user, order):
# ガード節:異常系を先に処理
if user is None:
return {"status": "error", "message": "User is None"}
if not user.is_authenticated:
return {"status": "error", "message": "Not authenticated"}
if not user.has_permission("create_order"):
return {"status": "error", "message": "Permission denied"}
if order is None:
return {"status": "error", "message": "Order is None"}
if not order.is_valid():
return {"status": "error", "message": "Invalid order"}
if not order.items:
return {"status": "error", "message": "No items"}
# ここから本題(正常系)
total = sum(item.price for item in order.items)
return {"status": "success", "total": total}
ネストが完全にフラットになり、各ガード節が何をチェックしているか一目で分かる。
ガード節のパターン
ガード節には主に 3 つのパターンがある。
引数や変数が None でないことを確認する。最も基本的なガード。
処理を続行するための条件(認証済み、権限あり、データ有効など)を確認する。
空リスト、範囲外の値、不正な状態などをチェックする。
def calculate_average(numbers):
# None チェック
if numbers is None:
raise ValueError("numbers cannot be None")
# 境界条件チェック
if not numbers:
return 0.0
# 本題
return sum(numbers) / len(numbers)
例外を使ったガード
エラー時に値を返すのではなく、例外を投げるパターンもある。
def process_order(user, order):
if user is None:
raise ValueError("User cannot be None")
if not user.is_authenticated:
raise PermissionError("User is not authenticated")
if not user.has_permission("create_order"):
raise PermissionError("User lacks create_order permission")
if order is None or not order.is_valid():
raise ValueError("Invalid order")
if not order.items:
raise ValueError("Order has no items")
total = sum(item.price for item in order.items)
return {"status": "success", "total": total}
例外を使うと、呼び出し側でエラー処理を一元化できる。
ループ内での早期 continue
ループ内では continue を使って早期に次のイテレーションに進むことで、ネストを減らせる。
# ネストした例(悪い例)
def process_items(items):
results = []
for item in items:
if item is not None:
if item.is_active:
if item.price > 0:
results.append(item.price * 1.1)
return results
# 早期 continue を使った改善(良い例)
def process_items(items):
results = []
for item in items:
if item is None:
continue
if not item.is_active:
continue
if item.price <= 0:
continue
results.append(item.price * 1.1)
return results
複合条件の分解
複雑な複合条件も、ガード節として分解できる。
# 複合条件(読みにくい)
def can_access(user, resource):
if user and user.is_authenticated and (user.is_admin or resource.owner == user.id):
return True
return False
# ガード節で分解(読みやすい)
def can_access(user, resource):
if user is None:
return False
if not user.is_authenticated:
return False
if user.is_admin:
return True
if resource.owner == user.id:
return True
return False
注意点:過度な早期リターン
早期リターンを使いすぎると、逆に読みにくくなることもある。
# 過度な早期リターン(やりすぎ)
def get_discount(price, user):
if price <= 0:
return 0
if user is None:
return 0
if not user.is_member:
return 0
if user.membership_years < 1:
return price * 0.05
if user.membership_years < 3:
return price * 0.10
if user.membership_years < 5:
return price * 0.15
return price * 0.20
この場合、後半の条件分岐は早期リターンより if-elif の方が意図が明確だ。
# 適切なバランス
def get_discount(price, user):
# ガード節
if price <= 0:
return 0
if user is None or not user.is_member:
return 0
# 本題の条件分岐は if-elif で
years = user.membership_years
if years >= 5:
rate = 0.20
elif years >= 3:
rate = 0.15
elif years >= 1:
rate = 0.10
else:
rate = 0.05
return price * rate
早期リターンと単一出口原則
かつては「関数の出口は 1 つにすべき」という単一出口原則(single exit point)が推奨されていた。しかし現代では、早期リターンを使う方が可読性が高いとされることが多い。
関数の最後でのみ return する。制御フローが予測しやすい。ネストが深くなりやすい。
異常系を先に処理して return。ネストがフラット。出口が複数になるが、各ガードが明確。
リソース管理が必要な場合(ファイルハンドルのクローズなど)は、with 文や try-finally を使えば、早期リターンでも安全にリソースを解放できる。
まとめ
早期リターンとガード節は、深いネストを解消する強力なテクニックだ。
コードを書くときは「ハッピーパス(正常系)をネストの外に出す」ことを意識すると、自然と読みやすい構造になる。