1. 深いネスト
if や for が何重にもネストすると、コードのインデントが右に伸びて読みにくくなる。目安はネスト 3 層まで。超えたら Early Return(ガード節)や関数分割を検討する。
def process_order(order):
if order:
if order.is_active:
if order.items:
for item in order.items:
if item.in_stock:
ship(item) # 本来やりたい処理がここにある
def process_order(order):
if not order: return # ガード節①
if not order.is_active: return # ガード節②
if not order.items: return # ガード節③
for item in order.items:
if item.in_stock:
ship(item) # ネスト 2 層で済む
2. 長大な関数
関数が 100 行を超え始めると単体テストが書きにくくなり、デバッグも困難になる。関数は 1 つのことだけを行う(単一責任)という原則を守り、20〜30 行以内を目安にする。
💡 分割のヒント
関数内に「バリデーション」「データ変換」「永続化」のフェーズがあったら、それぞれを別の関数に切り出せる。コメントで「# ここから〇〇処理」と書いているなら、その区切りが関数の境界だ。
def register_user(data):
validated = _validate_user_data(data) # 検証
hashed = _hash_password(validated) # 変換
user = _save_user(hashed) # 保存
_send_welcome_email(user) # 通知
return user
def _validate_user_data(data): ... # 各フェーズは単独でテスト可能
def _hash_password(data): ...
def _save_user(data): ...
def _send_welcome_email(user): ...
3. DRY 原則の違反(コードの重複)
同じロジックを複数箇所にコピー & ペーストすると、仕様変更時に「直し忘れ」が生じる。DRY (Don't Repeat Yourself) — 同じことは一箇所でのみ書く。
❌ NGclass RegistrationService:
def register(self, email):
if not email or "@" not in email: # バリデーションをコピペ
raise ValueError("Invalid email")
...
class ProfileService:
def update_email(self, email):
if not email or "@" not in email: # 同じロジックをコピペ
raise ValueError("Invalid email")
...
def validate_email(email: str) -> None:
"""メールアドレスの基本バリデーション。不正な場合は ValueError を送出。"""
if not email or "@" not in email:
raise ValueError(f"Invalid email address: {email!r}")
class RegistrationService:
def register(self, email): validate_email(email); ...
class ProfileService:
def update_email(self, email): validate_email(email); ...
4. コメントのアンチパターン
| アンチパターン | 問題点 | 改善策 |
|---|---|---|
コードをそのまま訳すコメント# i を 1 増やす |
何も情報を追加していない | コメントを削除するか、なぜが必要なら書く |
嘘のコメント(コードと乖離)# ユーザーを削除する→ 実際は論理削除 |
コードより誤ったコメントを信じてバグを生む | コードを変更したらコメントも更新する |
| コメントアウトした死んだコード | 「なぜ残しているか」が不明で混乱を招く | Git の履歴に任せて削除する |
| メソッド名で伝わる内容をコメントに書く | 冗長で保守負担が増える | 名前を改善して、コメントなしでも読めるようにする |
✅ 良いコメントとは
「なぜこの設計にしたか」「なぜこの特殊ケースが必要か」を説明するコメント。コードを読んでも分からない 意図・背景・制約 を補足するために書く。
5. 空の catch / 例外の握り潰し
except: pass や空の catch ブロックは、エラーが起きても完全に無視してしまう。問題が起きたことを誰も知れず、その後のデータが壊れた状態で処理が続く。
def save_user(user):
try:
db.save(user)
except:
pass # エラーが起きても無視。ユーザーは「保存できた」と思っている
import logging
logger = logging.getLogger(__name__)
def save_user(user):
try:
db.save(user)
except DatabaseError as e:
logger.error("Failed to save user id=%s: %s", user.id, e, exc_info=True)
raise # または変換して上位に再送出
6. 例外の乱用(制御フロー代わりの例外)
例外は「予期しない異常事態」のために設計されている。通常の制御フロー(存在確認・条件分岐)に例外を使うと、パフォーマンスが低下し、コードの流れも分かりにくくなる。
❌ NGdef get_user_email(user_id):
try:
user = User.objects.get(id=user_id)
return user.email
except User.DoesNotExist:
return None # 「存在しない」は例外でなく通常ケース
def get_user_email(user_id):
user = User.objects.filter(id=user_id).first()
return user.email if user else None
7. 例外の種類を細分化しない
Exception や RuntimeError など広すぎる型でキャッチすると、意図しない例外まで飲み込んでしまう。例外はなるべく具体的な型でキャッチし、上位に再送出するか適切なエラー型に変換する。
try:
result = process_payment(order)
except Exception as e:
print(f"Error: {e}") # KeyboardInterrupt も吸収してしまう場合がある
try:
result = process_payment(order)
except PaymentDeclinedError as e:
logger.warning("Payment declined for order %s: %s", order.id, e)
return {"status": "declined", "reason": str(e)}
except NetworkError as e:
logger.error("Network error during payment: %s", e, exc_info=True)
raise PaymentServiceUnavailableError("一時的にご利用いただけません") from e
# その他の例外は意図的にキャッチしない → 呼び出し元に伝播
⚠️ カスタム例外クラスを活用する
PaymentDeclinedError・InventoryShortageError のようにドメイン固有の例外クラスを定義すると、エラーの種類を型で表現でき、上位のハンドラが判断しやすくなる。