Error Handling Patterns
Universal error handling patterns for robust applications.
Core Principles
1. Fail Fast
Detect errors early, before they cause more damage.
code
# Bad - Error propagates silently
def process(data):
result = parse(data) # Might return None
return transform(result) # Crashes later
# Good - Fail immediately
def process(data):
result = parse(data)
if result is None:
raise ValueError("Failed to parse data")
return transform(result)
2. Fail Loudly
Make errors visible, don't swallow them.
code
# Bad - Silent failure
try:
do_something()
except Exception:
pass # Error disappears
# Good - Log and handle
try:
do_something()
except Exception as e:
logger.error(f"Failed: {e}")
raise # Or handle appropriately
3. Handle at the Right Level
Catch errors where you can meaningfully respond.
Patterns
Result Type Pattern
code
def divide(a, b):
if b == 0:
return Result.error("Division by zero")
return Result.ok(a / b)
Guard Clause Pattern
code
def process_order(order):
if order is None:
raise ValueError("Order cannot be null")
if not order.items:
raise ValueError("Order must have items")
# Happy path
return submit_order(order)
Retry Pattern
code
def with_retry(operation, max_attempts=3, delay=1):
for attempt in range(max_attempts):
try:
return operation()
except TransientError:
if attempt == max_attempts - 1:
raise
sleep(delay * (2 ** attempt))
Error Messages
For Users
- •Clear and actionable
- •No technical jargon
- •Suggest next steps
For Developers
- •Detailed context
- •Stack traces
- •Relevant data
Checklist
- • Expected errors handled explicitly
- • Unexpected errors logged and reported
- • Resources cleaned up in finally/defer
- • User-facing messages are helpful
- • No swallowed exceptions