""" Exception Handling Best Practices for EvoSpikeNet
このドキュメントは、EvoSpikeNetプロジェクトにおける 例外処理のベストプラクティスとガイドラインを定義します。 """ import os import json import logging import asyncio import aiohttp import torch
from evospikenet import exceptions as ev_exceptions from evospikenet.logging_utils import get_logger, log_exception
================================================================================
[!NOTE] 最新の実装状況は 機能実装ステータス (Remaining Functionality) を参照してください。
1. カスタム例外の使用
================================================================================
❌ 悪い例: 汎用的なException
def load_model(path): if not os.path.exists(path): raise Exception("File not found") # 不明瞭
✅ 良い例: 特定のカスタム例外
def load_model(path): if not os.path.exists(path): raise ev_exceptions.ModelNotFoundError( f"Model file not found: {path}", details={"path": path, "cwd": os.getcwd()} )
================================================================================
2. Bare Exceptの回避
================================================================================
❌ 悪い例: Bare except(すべてをキャッチ)
try: data = json.loads(response_text) except: # KeyboardInterruptやSystemExitもキャッチしてしまう data = {}
✅ 良い例: 特定の例外型を指定
try: data = json.loads(response_text) except (json.JSONDecodeError, ValueError) as e: logger.warning(f"Failed to parse JSON: {e}") data = {}
================================================================================
3. 例外チェーンの使用
================================================================================
❌ 悪い例: 元の例外を失う
try: import inspect load_kwargs = {"map_location": "cpu"} try: sig = inspect.signature(torch.load) if "weights_only" in sig.parameters: load_kwargs["weights_only"] = True except Exception: pass model = torch.load(path, **load_kwargs) except Exception: raise ModelLoadError("Failed to load model") # 元のエラー情報が失われる
✅ 良い例: 例外チェーン(from)を使用
try: import inspect load_kwargs = {"map_location": "cpu"} try: sig = inspect.signature(torch.load) if "weights_only" in sig.parameters: load_kwargs["weights_only"] = True except Exception: pass model = torch.load(path, **load_kwargs) except (FileNotFoundError, RuntimeError) as e: raise ev_exceptions.ModelLoadError( f"Failed to load model from {path}", details={"path": path} ) from e # 元の例外を保持
================================================================================
4. ログ記録の統一
================================================================================
❌ 悪い例: print文や不統一なログ
try: result = process_data(data) except Exception as e: print(f"Error: {e}") # printは推奨されない logging.error(str(e)) # exc_infoがない
✅ 良い例: 構造化ログとexc_info
logger = get_logger(name)
try: result = process_data(data) except ev_exceptions.DataValidationError as e: log_exception( logger, e, message="Data validation failed", extra_context={"data_size": len(data)} ) raise
================================================================================
5. コンテキストマネージャーでのリソース管理
================================================================================
❌ 悪い例: 手動でクリーンアップ
file = None try: file = open("data.txt") process(file) except Exception as e: logger.error(f"Error: {e}") finally: if file: file.close()
✅ 良い例: コンテキストマネージャー
try: with open("data.txt") as file: process(file) except IOError as e: log_exception(logger, e, "Failed to process file")
================================================================================
6. 例外の再送出
================================================================================
❌ 悪い例: 例外を握りつぶす
try: critical_operation() except Exception: pass # エラーが完全に無視される
✅ 良い例: 適切に処理してから再送出
try: critical_operation() except CommunicationError as e: logger.error(f"Communication failed: {e}") # クリーンアップ処理 cleanup() # 再送出して呼び出し元に伝える raise
================================================================================
7. 複数の例外型のハンドリング
================================================================================
❌ 悪い例: 広範なException catch
try: result = api_call() except Exception as e: # すべてをキャッチしすぎ logger.error(f"API failed: {e}")
✅ 良い例: 特定の例外を個別に処理
try: result = api_call() except ev_exceptions.APIConnectionError as e: log_exception(logger, e, "Failed to connect to API") # 再接続ロジック retry_connection() except ev_exceptions.APITimeoutError as e: log_exception(logger, e, "API request timed out") # タイムアウト処理 handle_timeout() except ev_exceptions.APIResponseError as e: log_exception(logger, e, "Invalid API response") # レスポンスエラー処理 handle_invalid_response()
================================================================================
8. バリデーションでのアーリーリターン
================================================================================
❌ 悪い例: ネストが深い
def process_data(data): try: if data: if isinstance(data, dict): if "key" in data: return data["key"] except Exception: return None
✅ 良い例: アーリーリターンとガード節
def extract_key_from_data(data): if not isinstance(data, dict): raise ev_exceptions.DataValidationError( f"Expected dict, got {type(data).name}" )
if "key" not in data:
raise ev_exceptions.DataValidationError("Missing required key: 'key'")
return data["key"]
================================================================================
9. 非同期コードでの例外処理
================================================================================
✅ 良い例: async/awaitでの例外処理
async def fetch_data(url: str, timeout: float = 10.0): try: async with aiohttp.ClientSession() as session: async with session.get(url, timeout=timeout) as response: return await response.json() except asyncio.TimeoutError as e: raise ev_exceptions.EvoTimeoutError( f"Request to {url} timed out after {timeout}s", details={"url": url, "timeout": timeout} ) from e except aiohttp.ClientError as e: raise ev_exceptions.APIConnectionError( f"Failed to connect to {url}", details={"url": url} ) from e
================================================================================
10. デコレーターを使った共通エラーハンドリング
================================================================================
from functools import wraps
def handle_errors(default_return=None, log_level="error"): """エラーハンドリングデコレーター""" logger = get_logger(name)
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except ev_exceptions.EvoSpikeNetError as e:
# プロジェクト固有のエラー
log_exception(
logger,
e,
f"EvoSpikeNet error in {func.__name__}",
level=getattr(logging, log_level.upper())
)
return default_return
except Exception as e:
# 予期しないエラー
log_exception(
logger,
e,
f"Unexpected error in {func.__name__}",
level=logging.ERROR
)
raise # 予期しないエラーは再送出
return wrapper
return decorator
使用例
@handle_errors(default_return=[], log_level="warning") def get_user_data(user_id: int): # 実装 pass
================================================================================
チェックリスト
================================================================================
""" 例外処理のチェックリスト:
□ Bare except(except:)を使用していないか? □ 広範な Exception catch を避けているか? □ カスタム例外クラスを使用しているか? □ 例外チェーン(from)を使用しているか? □ ログに十分なコンテキスト情報を含めているか? □ exc_info=True でスタックトレースを記録しているか? □ リソースはコンテキストマネージャーで管理しているか? □ 例外を不必要に握りつぶしていないか? □ デストラクタ(del)内のエラーを適切に処理しているか? □ 非同期コードで適切な例外型を使用しているか? """