""" Exception Handling Best Practices for EvoSpikeNet
This document is part of the EvoSpikeNet project. Define best practices and guidelines for exception handling. """ 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] For the latest implementation status, please refer to Functional Implementation Status (Remaining Functionality).
1. Using custom exceptions
=================================================================================
❌ Bad example: Generic Exception
def load_model(path): if not os.path.exists(path): raise Exception("File not found") # Unclear
✅ Good example: specific custom exceptions
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. Avoiding Bare Exceptions
=================================================================================
❌ Bad example: Bare except (catch all)
try: data = json.loads(response_text) except: # Also catches KeyboardInterrupt and SystemExit data = {}
✅ Good example: specifying a specific exception type
try: data = json.loads(response_text) except (json.JSONDecodeError, ValueError) as e: logger.warning(f"Failed to parse JSON: {e}") data = {}
=================================================================================
3. Using exception chains
=================================================================================
❌ Bad example: Losing the original exception
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 exception Exception: pass model = torch.load(path, **load_kwargs) exception Exception: raise ModelLoadError("Failed to load model") # original error information is lost
✅ Good example: using exception chaining (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 exception 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 # keep the original exception
=================================================================================
4. Unified logging
=================================================================================
❌ Bad example: print statements and inconsistent logs
try: result = process_data(data) except Exception as e: print(f"Error: {e}") # print is deprecated logging.error(str(e)) # exc_info is missing
✅ Good example: structured logging and 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. Resource management with context manager
=================================================================================
❌ Bad example: Manual cleanup
file = None try: file = open("data.txt") process(file) except Exception as e: logger.error(f"Error: {e}") finally: if file: file.close()
✅ Good example: context manager
try: with open("data.txt") as file: process(file) except IOError as e: log_exception(logger, e, "Failed to process file")
=================================================================================
6. Rethrowing the exception
=================================================================================
❌ Bad example: suppressing exceptions
try: critical_operation() exception Exception: pass # errors are completely ignored
✅ Good example: Process properly and resend
try: critical_operation() except CommunicationError as e: logger.error(f"Communication failed: {e}") # Cleanup process cleanup() # Resend and tell the caller raise
=================================================================================
7. Handling multiple exception types
=================================================================================
❌ Bad example: Extensive Exception catch
try: result = api_call() exception Exception as e: # catch everything too much logger.error(f"API failed: {e}")
✅ Good example: Handle specific exceptions individually
try: result = api_call() except ev_exceptions.APIConnectionError as e: log_exception(logger, e, "Failed to connect to API") # Reconnection logic retry_connection() except ev_exceptions.APITimeoutError as e: log_exception(logger, e, "API request timed out") # Timeout processing handle_timeout() except ev_exceptions.APIResponseError as e: log_exception(logger, e, "Invalid API response") # Response error handling handle_invalid_response()
=================================================================================
8. Early return in validation
=================================================================================
❌ Bad example: deep nesting
def process_data(data): try: if data: if isinstance(data, dict): if "key" in data: return data["key"] exception Exception: return None
✅ Good example: early returns and guard clauses
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. Exception handling in asynchronous code
=================================================================================
✅ Good example: exception handling with 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. Common error handling using decorators
=================================================================================
from functools import wraps
def handle_errors(default_return=None, log_level="error"): """Error handling decorator""" logger = get_logger(name)
def decorator(func): @wraps(func) def wrapper(args, kwargs): try: return func(args, **kwargs) except ev_exceptions.EvoSpikeNetError as e: # Project-specific errors log_exception( logger, e, f"EvoSpikeNet error in {func.name}", level=getattr(logging, log_level.upper()) ) return default_return except Exception as e: # unexpected error log_exception( logger, e, f"Unexpected error in {func.name}", level=logging.ERROR ) raise # Unexpected errors are reraised
return wrapper
return decorator
Usage example
@handle_errors(default_return=[], log_level="warning") def get_user_data(user_id: int): # Implementation pass
=================================================================================
Checklist
=================================================================================
""" Exception handling checklist:
□ Are you using Bare except (except:)? □ Are you avoiding extensive Exception catches? □ Are you using custom exception classes? □ Are you using exception chaining (from)? □ Do you include enough context information in your logs? □ Are stack traces recorded with exc_info=True? □ Are resources managed by a context manager? □ Are you unnecessarily suppressing exceptions? □ Are errors in the destructor (del) handled appropriately? □ Are you using the appropriate exception types in your asynchronous code? """