04

Errors as values

#
  • heuristic
  • context-dependent
  • fragment

An error is part of the signature, not a side channel for control flow. Exceptions break locality: any call can jump anywhere.

Code example (fragment)
fragment
Java
// checked exceptions: a historically failed attempt at the same thing;
// the modern form is a Result-like sealed type for domain errors
sealed interface ChargeResult permits Charged, Declined, GatewayDown {}
record Charged(String txId) implements ChargeResult {}
record Declined(String reason) implements ChargeResult {}
record GatewayDown(Duration retryAfter) implements ChargeResult {}

// exceptions remain for programming errors (NPE, IllegalState)
// — that is the correct split
Rust
// Result + the ? operator: the reference form of the approach
fn charge(card: &Card, amount: Cents) -> Result<TxId, ChargeError> {
    let token = tokenize(card)?;          // early return, the type stays honest
    gateway::charge(token, amount)        // ChargeError: an enum of every outcome
}

// thiserror for libraries, anyhow for applications: the settled boundary
Go
// (T, error) is the language idiom; the key is wrapping with context and errors.Is/As
func (s *Svc) Charge(ctx context.Context, c Card, amt Cents) (TxID, error) {
    tok, err := s.tokenize(ctx, c)
    if err != nil {
        return "", fmt.Errorf("tokenize card ****%s: %w", c.Last4, err)
    }
    id, err := s.gw.Charge(ctx, tok, amt)
    if err != nil {
        if errors.Is(err, gateway.ErrDeclined) { return "", ErrDeclined }
        return "", fmt.Errorf("gateway charge: %w", err)
    }
    return id, nil
}
Python
# exceptions are the language idiom; fighting it costs more than accepting it.
# Compromise: domain outcomes as values, infrastructure failures as exceptions
from dataclasses import dataclass

@dataclass(frozen=True)
class Declined: reason: str

@dataclass(frozen=True)
class Charged: tx_id: str

def charge(card: Card, amount: int) -> Charged | Declined:
    ...  # Declined is not an exception: it is an expected business outcome
    # ConnectionError stays an exception: it is a failure, not an outcome

Last technical review: 2026-08-04 · Reference · 112 principles