Approach: extract the repeated validation and logging into a reusable decorator (or higher-order wrapper) that accepts validators and a logger. This removes duplication, makes the action functions single-responsibility, and improves testability (you can unit-test actions without logging by injecting mocks).python
from functools import wraps
# injectable logger for testability
def default_logger(action, data):
print(f"{action}: {data}")
def require_fields(*fields):
"""Return a validator function that raises ValueError if required fields missing."""
def validator(data):
missing = [f for f in fields if not data.get(f)]
if missing:
raise ValueError(f"Missing fields: {', '.join(missing)}")
return validator
def validate_and_log(validator, logger=default_logger, action_name=None):
"""Decorator to run validation and logging before the core action."""
def decorator(func):
name = action_name or func.__name__
@wraps(func)
def wrapper(data, *args, **kwargs):
validator(data)
logger(name, data)
return func(data, *args, **kwargs)
return wrapper
return decorator
# Core action implementations are now focused and easy to test
@validate_and_log(require_fields('email'), action_name='create')
def create_user(data):
# perform create logic
return {"status": "created", "email": data['email']}
@validate_and_log(require_fields('id', 'email'), action_name='update')
def update_user(data):
# perform update logic
return {"status": "updated", "id": data['id']}
@validate_and_log(require_fields('id'), action_name='delete')
def delete_user(data):
# perform delete logic
return {"status": "deleted", "id": data['id']}
Why this is better:- Removes duplication: validation and logging live in one place.- Single Responsibility: action functions focus on business logic.- Testability: inject a test logger or validator; unit tests can call create_user without side-effecting logs.- Clarity: decorator makes preconditions explicit (require_fields) and documents intent.- Extensible: add metrics, retries, or different validators easily without touching action bodies.Edge cases:- Validators should be pure and raise clear exceptions.- If logging must be async or external, replace default_logger with a mock/adapter.