例外階層って何?一言でいうと「エラーを“種類ごとに整理する”ための仕組み」
例外階層(Exception Hierarchy)は、
「エラーを種類ごとに分類して、親子関係で整理する」ための設計です。
Python にはすでに標準の例外階層がありますが、
自分のアプリでも 「どんなエラーが起きうるか」 を整理して階層を作ると、
どこで何を捕まえるべきか
どのエラーは握りつぶしてよくて
どのエラーは上に伝えるべきか
が一気に分かりやすくなります。
例外階層は、テスト・設計・品質のすべてに効く“基礎体力”のようなものです。
Python標準の例外階層をざっくり理解する
Pythonの例外は「木構造」になっている
Python の例外は、こんな感じの階層になっています。
BaseException
├── Exception
│ ├── ValueError
│ ├── TypeError
│ ├── KeyError
│ ├── RuntimeError
│ └── ...
└── SystemExit / KeyboardInterrupt など
重要なのは、
except Exception: は「ほとんどのエラー」を捕まえるexcept ValueError: は「値が不正なときだけ」を捕まえる
というように、親を捕まえれば子も捕まるという仕組みです。
この仕組みを、自分のアプリでも活用するのが「例外階層の設計」です。
例外階層を自作するメリットを体感する
悪い例:例外がバラバラで扱いにくい
例えば、ユーザー登録処理でこんなコードがあるとします。
def register_user(name: str, email: str) -> None:
if not name:
raise ValueError("name is empty")
if "@" not in email:
raise ValueError("invalid email")
if email_exists(email):
raise RuntimeError("email already exists")
Pythonこれだと、
どれが「入力エラー」で
どれが「業務ルールのエラー」で
どれが「システムのエラー」なのか
が分かりません。
呼び出し側も困ります。
try:
register_user("Taro", "taro@example.com")
except Exception:
print("なんかエラー")
Python「なんかエラー」では困りますよね。
良い例:例外階層を作って整理する
まずは「アプリ専用の基底例外」を作ります。
class AppError(Exception):
"""アプリ全体の基底例外"""
Python次に、種類ごとに例外を分けます。
class ValidationError(AppError):
"""入力値が不正"""
class DomainError(AppError):
"""ビジネスルール違反"""
class DuplicateEmailError(DomainError):
"""メールアドレスが既に存在する"""
Pythonこれを使って書き直すとこうなります。
def register_user(name: str, email: str) -> None:
if not name:
raise ValidationError("name is empty")
if "@" not in email:
raise ValidationError("invalid email")
if email_exists(email):
raise DuplicateEmailError("email already exists")
Python呼び出し側は、種類ごとに正しく扱えます。
try:
register_user("Taro", "taro@example.com")
except ValidationError as e:
print("入力エラー:", e)
except DomainError as e:
print("業務ルールエラー:", e)
Pythonこれが例外階層の威力です。
例外階層の設計で特に大事なポイントを深掘りする
「基底例外」を必ず作る
アプリ全体の例外の親を作るのは、ほぼ必須です。
class AppError(Exception):
pass
Pythonこれがあると、
「アプリ内のエラーだけをまとめて捕まえる」
「外部ライブラリのエラーは捕まえない」
ということができます。
try:
do_something()
except AppError:
print("アプリ内のエラーだけ処理する")
Pythonこれがないと、ValueError や TypeError などの標準例外まで巻き込んでしまい、
バグを隠す危険があります。
「分類」を意識して階層を作る
例外階層は、次のような分類で作ると整理しやすいです。
入力の問題(ValidationError)
ビジネスルールの問題(DomainError)
外部サービスの問題(ExternalServiceError)
永続化の問題(RepositoryError)
例えば、注文処理ならこうなります。
class OrderError(AppError):
pass
class OrderValidationError(OrderError):
pass
class PaymentError(OrderError):
pass
class InventoryError(OrderError):
pass
Pythonこれで、
「注文に関するエラーだけまとめて捕まえる」
「支払いエラーだけ個別に扱う」
といった柔軟な制御ができます。
「例外メッセージ」も設計の一部
例外メッセージは、ログに残る大事な情報です。
悪い例:
raise ValidationError("invalid")
Python良い例:
raise ValidationError(f"invalid email: {email}")
Python未来の自分がログを見たときに、
「何がどうダメだったのか」が分かるように書くのがポイントです。
例外階層とテストの関係
テストは「どの例外が投げられるか」を確認できる
例外階層があると、テストも書きやすくなります。
import pytest
def test_duplicate_email():
with pytest.raises(DuplicateEmailError):
register_user("Taro", "taro@example.com")
Python「このケースではこの例外が投げられるべき」という仕様が明確になります。
「広く捕まえる」「狭く捕まえる」を選べる
例えば、ユースケース層では「アプリ内のエラーだけ」捕まえたい。
try:
register_user(...)
except AppError as e:
logger.error("user registration failed: %s", e)
Python一方、ドメイン層では「標準例外はバグなので捕まえない」。
def validate_email(email: str) -> None:
if "@" not in email:
raise ValidationError("invalid email")
# TypeError などはバグなので捕まえない
Python例外階層があると、こうした「層ごとの方針」が明確にできます。
例外階層と品質の関係
バグと仕様エラーを区別できる
例外階層がないと、すべてのエラーが「ただの例外」になります。
例外階層があると、
ValidationError → ユーザーの入力ミス
DomainError → 業務ルール違反
RepositoryError → DBの問題
AppError → アプリ内の仕様エラー
TypeError / ValueError → バグ
というように、「これはバグか?仕様か?」 が明確になります。
これは品質に直結します。
ログの読みやすさが段違いになる
例外階層があると、ログも整理されます。
ERROR DomainError: email already exists
ERROR ValidationError: invalid email
ERROR RepositoryError: DB connection failed
種類が分かれているので、
「どの層で問題が起きているか」が一目で分かります。
初心者が例外階層を作るときのおすすめステップ
まずは「アプリ基底例外」を作る
class AppError(Exception):
pass
Pythonこれだけで、例外設計の第一歩です。
次に「入力」「ドメイン」「外部」の3つに分ける
class ValidationError(AppError):
pass
class DomainError(AppError):
pass
class ExternalServiceError(AppError):
pass
Pythonこれだけでも、かなり整理されます。
必要になったら「細かい例外」を追加する
例えば、メール重複だけ特別扱いしたいなら、
class DuplicateEmailError(DomainError):
pass
Pythonというように、必要になったときに追加していけばOKです。
まとめ(例外階層は「エラーを種類ごとに整理して扱いやすくする設計」)
初心者向けにまとめると、例外階層はこういうものです。
例外階層は、エラーを種類ごとに分類し、親子関係で整理することで、「どこで何を捕まえるか」「どれがバグでどれが仕様か」を明確にする設計。
アプリ基底例外 → 入力エラー / ドメインエラー / 外部エラー → 個別のエラー、という階層を作ると、テストしやすく、ログも読みやすく、品質が安定する。
最初は「AppError」「ValidationError」「DomainError」だけでも十分で、必要に応じて細かい例外を追加していくと、自然と扱いやすい例外体系が育っていく。
