Code Fable: The Philosophy of Error Handling

The Fable

Chapter 1: The Trap of Perfectionism

Once upon a time, there was a programmer who pursued perfect code.

His functions were always correct. His algorithms were always optimal. His programs never threw errors.

Until one day, when the user’s network went down.

# His code
def send_data(data):
    if network.is_connected():
        send(data)
    # else? Nothing done

The user waited ten minutes, and nothing happened.


Chapter 2: Words from the Old Programmer

“Young man,” the old programmer said, “Do you know why airplanes can fly?”

“Because of engines.”

“No. Because they can survive engine failure.”

The old programmer showed them their system:

func sendDataWithRetry(data []byte, maxRetries int) error {
    for i := 0; i <= maxRetries; i++ {
        err := send(data)
        if err == nil {
            return nil
        }
        log.Printf("Attempt %d failed: %v", i, err)
        if i < maxRetries {
            time.Sleep(time.Duration(i+1) * time.Second) // Exponential backoff
        }
    }
    return fmt.Errorf("failed after %d attempts", maxRetries)
}

Fault tolerance is not tolerating errors, but allowing errors to exist without crashing.


Chapter 3: The Value of Errors

One day, the young programmer asked:

“If errors are inevitable, why write try-catch?”

The old programmer smiled:

“Do you know why diamonds are precious?”

“Because they’re rare.”

“No. Because under high temperature and pressure, carbon becomes diamonds.”

# Errors are not enemies, they're opportunities for growth
try:
    result = risky_operation()
except TransientError as e:
    # Temporary error - retry
    return retry_with_backoff()
except PermanentError as e:
    # Permanent error - log and handle gracefully
    log.error(f"Permanent failure: {e}")
    return fallback_value()
except Exception as e:
    # Unknown error - capture but don't overhandle
    logger.exception("Unexpected error")
    raise  # or return a default value

Errors are part of system evolution.


Chapter 4: The Wisdom of Circuit Breakers

The old programmer pointed to the distance:

“Do you see those skyscrapers?”

“Yes.”

“Do you know why they have fire doors?”

“To prevent fire from spreading.”

The old programmer nodded:

type CircuitBreaker struct {
    failures    int
    threshold   int
    state       string // "closed", "open", "half-open"
    lastFailure time.Time
}

func (cb *CircuitBreaker) Call() error {
    switch cb.state {
    case "open":
        if time.Since(cb.lastFailure) > resetTimeout {
            cb.state = "half-open" // Try to recover
        }
        return errors.New("circuit open")
    case "half-open":
        // Allow limited requests
        err := doCall()
        if err != nil {
            cb.state = "open"
            return err
        }
        cb.state = "closed" // Return to normal
        return nil
    default: // closed
        err := doCall()
        if err != nil {
            cb.failures++
            cb.lastFailure = time.Now()
            if cb.failures >= cb.threshold {
                cb.state = "open" // Trip the breaker
            }
        } else {
            cb.failures = 0 // Success, reset
        }
        return err
    }
}

Circuit breakers don’t escape errors, they prevent errors from cascading.


Chapter 5: Graceful Degradation

When disaster struck, the entire system almost crashed.

But the old programmer’s system survived.

# Graceful degradation - keep core functionality
def get_user_profile(user_id):
    try:
        # Try full data
        return full_profile_from_database(user_id)
    except DatabaseError:
        pass
    
    try:
        # Try cache
        return cache.get(user_id)
    except CacheError:
        pass
    
    # Last resort - return basic info
    return BasicProfile(user_id=user_id, 
                        name="User", 
                        status="degraded_mode")

Perfection is the goal, graceful degradation is wisdom.


The Moral

  1. Perfectionism is a trap - It’s impossible to never make mistakes; learn to coexist with errors
  2. Errors are information - Every error contains valuable feedback
  3. Retry is wisdom - Temporary errors can be solved through retry
  4. Circuit breakers prevent collapse - Prevent small errors from becoming disasters
  5. Graceful degradation is survival - Keep core functionality, abandon flashy features

Code Implementation

import logging
from functools import wraps
from time import sleep

def resilient(max_retries=3, backoff=1):
    """Resilience decorator"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except (ConnectionError, TimeoutError) as e:
                    if attempt < max_retries - 1:
                        sleep(backoff * (attempt + 1))
                        logging.warning(f"Retry {attempt+1}/{max_retries}")
                    else:
                        logging.error(f"All retries failed: {e}")
                        raise
            return None
        return wrapper
    return decorator

@resilient(max_retries=3)
def fetch_data(url):
    # Your network request logic
    pass

Conclusion

Fault tolerance is not tolerating errors, but keeping running through errors and finding growth in failure.

A real system doesn’t not make mistakes, but knows how to coexist with errors and how to recover from them.


Mingjian, compiling the code fable collection