1. N+1 問題(アプリケーション層)

N+1 問題は SQL 編でも触れたが、ORM 利用時に最も頻発する問題として再度確認する。1 回の「全件取得」クエリの後、ループ内でレコード数 N 回のクエリが発生するパターン。

❌ NG
Python (Django ORM) — N+1 が発生するコード
# クエリ 1 回で全 Order を取得
orders = Order.objects.all()

for order in orders:
    # ← ループのたびに User を取得するクエリが発行される(N 回)
    print(order.user.name)
✅ OK
Python (Django ORM) — select_related でクエリを 1 回に抑える
# JOIN で一括取得。ループ内で追加クエリは発生しない
orders = Order.objects.select_related('user').all()

for order in orders:
    print(order.user.name)   # キャッシュ済みデータを参照
ORM一対多 (FK)多対多 (M2M)
Djangoselect_related()prefetch_related()
SQLAlchemyjoinedload()subqueryload()
ActiveRecordincludes() / eager_load()
Prismainclude: { user: true }

2. ループ内の重複処理

ループのたびに同じ計算や関数呼び出しをすると、無駄な処理がN回実行される。ループ外で計算できるものは事前に変数に持つ。

❌ NG
Python — ループのたびに len() と DB呼び出し
items = get_items()
for i in range(len(items)):   # len() が毎回呼ばれる
    config = load_config()    # 毎ループで設定をDBから読み込む
    process(items[i], config)
✅ OK
Python — ループ前に計算・読み込みを済ませる
items  = get_items()
config = load_config()   # ループ前に 1 回だけ読み込む
count  = len(items)      # ループ前に計算

for item in items:       # Python では直接イテレートするのがベスト
    process(item, config)

3. ループ内での文字列結合

多くの言語で文字列はイミュータブル(不変)だ。+= で結合するたびに新しい文字列オブジェクトが生成され、大量のループでは O(N²) のメモリコストになる。

❌ NG
Python — += でループ内に文字列結合
result = ""
for word in words:
    result += word + ", "   # 毎回新しい文字列オブジェクトを生成
✅ OK
Python — join() でまとめて結合
result = ", ".join(words)   # O(N) で効率的

# HTML / SQL 生成なら同様のパターン
rows = "\n".join(f"<tr><td>{row.name}</td></tr>" for row in data)

4. 早すぎる最適化

「プログラムにおける悪の根源は、早すぎる最適化にある」(Donald Knuth)。プロファイリングでボトルネックを特定する前に可読性を犠牲にした最適化を行うと、メンテしにくいコードだけが残る。

⚠️ 最適化の正しい順序

① まず正しく動くコードを書く → ② プロファイリングでボトルネックを計測する → ③ ボトルネックだけを最適化する。計測せずに直感で最適化すると、速くなっていない場所だけ読みにくくなる。

Python — cProfile でボトルネックを計測する
import cProfile
import pstats

profiler = cProfile.Profile()
profiler.enable()
my_function()   # プロファイリングしたい処理
profiler.disable()

stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(20)   # 上位 20 件を表示

5. キャッシュを使わない

同じ計算・クエリ結果を毎回取得するのは無駄だ。特に変化しない設定値・マスタデータ・計算コストの高い結果はキャッシュが有効。ただし「キャッシュの invalidation」を考慮しないまま使うと古いデータが残るバグを生む。

✅ OK
Python — functools.lru_cache で計算結果をキャッシュ
from functools import lru_cache

@lru_cache(maxsize=128)
def get_exchange_rate(from_currency: str, to_currency: str) -> float:
    """為替レートを外部APIから取得(結果をキャッシュ)"""
    return external_api.fetch_rate(from_currency, to_currency)

# 同じ通貨ペアへの 2 回目以降の呼び出しはキャッシュを返す

シリーズ全体まとめ

SQL アンチパターン(PART 01〜03)

カテゴリ主なアンチパターン対策の核心
インデックス無効化関数適用・型変換・前方ワイルドカード・NOT IN・OR多用インデックスが使える形でカラムを参照する
不要な読み込みSELECT *・大 OFFSET・DISTINCT 乱用必要な列・行だけ取得する
結合・サブクエリN+1・相関サブクエリ・デカルト積JOIN で一括取得・結合条件を明示する
ロック競合長大 TX・デッドロック・過剰 FOR UPDATETX を短く・ロック順序を統一する
集計・ソートHAVING 誤用・COUNT の使い分けWHERE で先に絞る・COUNT の違いを理解する
その他大 IN リスト・EXPLAIN 未確認一時テーブル活用・実行計画を毎回確認する

コーディングアンチパターン(PART 04〜06)

カテゴリ主なアンチパターン対策の核心
設計ゴッドクラス・散弾銃手術・特性の横恋慕単一責任・データとロジックを同居させる
命名マジックナンバー・意味不明な変数名・深い継承定数化・意図を示す名前・継承よりコンポジション
可読性深いネスト・長大関数・DRY 違反ガード節・関数分割・共通化
エラー処理空 catch・例外乱用・広すぎるキャッチログを残す・制御フローに例外を使わない
パフォーマンスN+1・ループ内重複処理・文字列結合・早すぎる最適化計測してからボトルネックだけを直す

アンチパターンを覚えることの価値

アンチパターンは「こう書いてはいけない」という知識ではなく、「なぜそれが問題を引き起こすか」を理解するための道具だ。原因を理解することで、応用の利いた設計判断ができるようになる。コードレビューでも「〇〇パターンになっているので〜」と言語化できるようになると、チーム全体のコード品質が上がる。