実行時エラー処理
5,000行規模のプロジェクトにおいて、すべての関数に try-except を書くとコードが重複し、見通しが悪くなります。そこで、**「エラー処理を一箇所に集約する」**仕組みとして、FastAPIを例に「例外ハンドラ」の実装方法を解説します。
1. 共通エラーハンドリングの概念図¶
個別の処理(Routes)でエラーが発生した際、それをフレームワークがキャッチして、あらかじめ定義した共通の形式に整えてからユーザーに返します。
2. カスタム例外クラスの作成¶
まず、アプリケーション独自の例外を定義します。これにより、「バリデーション失敗」なのか「権限不足」なのかをプログラムで判別しやすくなります。
Python
# app/utils/exceptions.py
class AppException(Exception):
"""すべてのカスタム例外のベースクラス"""
def __init__(self, message: str, status_code: int = 400):
self.message = message
self.status_code = status_code
class ResourceNotFoundError(AppException):
"""データが見つからない場合のエラー"""
def __init__(self, item: str):
super().__init__(message=f"{item}が見つかりませんでした。", status_code=404)
3. 例外ハンドラの実装¶
FastAPIなどのフレームワークでは、特定の例外が発生したときに実行する**「共通の関数」**を登録できます。
Python
# app/main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import ValidationError
from app.utils.exceptions import AppException
app = FastAPI()
# 1. 独自例外(AppException)のハンドラ
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content={"status": "error", "message": exc.message}
)
# 2. Pydanticのバリデーションエラーのハンドラ
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
# 前回のステップで紹介した整形ロジックをここで呼び出す
errors = [{"field": e["loc"][-1], "msg": e["msg"]} for e in exc.errors()]
return JSONResponse(
status_code=422,
content={"status": "validation_error", "errors": errors}
)
4. ミドルウェア(Middleware)による共通処理¶
ミドルウェアは、リクエストが届く直前と、レスポンスが返る直前に実行される処理です。**「予期せぬエラー(サーバー内部エラー)」**をすべて拾うために使われます。
Python
# app/main.py に追加
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
try:
response = await call_next(request)
return response
except Exception as e:
# ここでログ出力(Sentryへの送信など)を行う
print(f"致命的なエラーが発生しました: {e}")
return JSONResponse(
status_code=500,
content={"status": "server_error", "message": "予期せぬエラーが発生しました。管理者にお問い合わせください。"}
)
5. この構成のメリット¶
- ビジネスロジックが綺麗になる:
routes内ではif error: return {"msg": "err"}と書く必要がなくなり、単にraise ResourceNotFoundError("備品")と投げるだけで済みます。 - レスポンス形式が統一される: どんなエラーが起きても、クライアント(フロントエンド)は常に同じJSON構造を期待できます。
- 保守性の向上: エラーメッセージの文言を変えたいとき、一箇所の修正で全画面に反映されます。
次のステップへの提案¶
5,000行規模のプロジェクトでは、これらのエラーを**「ログファイル」や「外部監視ツール(Sentryなど)」に出力する**ことも重要になります。
「ログの出力方法(Loggerの設定)」**や、「エラーが発生したときに開発者のSlackに通知する方法」**について興味はありますか?