Skip to content

Error Escalation

Determine when to throw vs return, when to panic vs recover.

Panic for Programming Errors Only

Use panic for unrecoverable programming errors (nil pointers, index out of bounds). Return errors for expected failure conditions that callers can handle (network timeouts, file not found).


When to Throw vs Return Error

// Return error: recoverable, caller can handle
func fetchUser(id string) (*User, error) {
    if id == "" {
        return nil, errors.New("user ID required")
    }

    user, err := db.Query("SELECT * FROM users WHERE id = ?", id)
    if err != nil {
        return nil, fmt.Errorf("database error: %w", err)
    }

    return user, nil
}

// Panic: programming error, should never happen
func processUsers(users []*User) {
    if users == nil {
        panic("processUsers called with nil slice")  // Programming error
    }

    for _, user := range users {
        if user == nil {
            panic("nil user in slice")  // Data corruption
        }
        // ... process user
    }
}

Error Aggregation vs First-Error-Wins

// First-error-wins: fast feedback
func validateFast(config *Config) error {
    if config.Host == "" {
        return errors.New("host required")  // Stop here
    }
    if config.Port == 0 {
        return errors.New("port required")
    }
    return nil
}

// Error aggregation: complete picture
func validateComplete(config *Config) error {
    var errors []string

    if config.Host == "" {
        errors = append(errors, "host required")
    }
    if config.Port == 0 {
        errors = append(errors, "port required")
    }
    if config.Timeout < 0 {
        errors = append(errors, "timeout must be positive")
    }

    if len(errors) > 0 {
        return fmt.Errorf("validation failed:\n- %s",
            strings.Join(errors, "\n- "))
    }
    return nil
}

Panic vs Recoverable Errors

func divide(a, b int) (int, error) {
    // Return error: expected error condition
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

func arrayAccess(arr []int, index int) int {
    // Panic: programming error that should never happen in production
    if index < 0 || index >= len(arr) {
        panic(fmt.Sprintf("index out of bounds: %d (len=%d)", index, len(arr)))
    }
    return arr[index]
}

Exit Codes

#!/bin/bash

# Exit codes communicate error type to caller

# 0: Success
# 1: General error
# 2: Misuse of command
# 126: Command not executable
# 127: Command not found
# 128+n: Fatal error signal n

deploy() {
    if [ $# -ne 2 ]; then
        echo "Usage: deploy <environment> <version>" >&2
        exit 2  # Misuse of command
    fi

    if ! command -v kubectl >/dev/null; then
        echo "kubectl not found" >&2
        exit 127  # Command not found
    fi

    if ! kubectl auth can-i create deployments; then
        echo "Insufficient permissions" >&2
        exit 1  # General error
    fi

    kubectl apply -f deployment.yaml || exit 1

    exit 0  # Success
}

deploy "$@"

Back to Fail Fast

Comments