DEV Community

丁久
丁久

Posted on • Originally published at dingjiu1989-hue.github.io

Error Handling Patterns

This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.

Error Handling Patterns

Error Handling Patterns

Error Handling Patterns

Error Handling Patterns

Error Handling Patterns

Error handling is a cross-cutting concern that affects system reliability, debuggability, and user experience. Different languages and paradigms offer different error handling approaches: exceptions in Java and Python, Result types in Rust and Swift, and error boundaries in UI frameworks. This article examines these patterns and provides guidance for building resilient error handling.

Exceptions

Exception-based error handling uses try/catch blocks to handle errors that occur in a different part of the call stack. When an error occurs, the code throws an exception that propagates up the call stack until a matching catch block handles it. Unhandled exceptions typically crash the program.

Exceptions are appropriate for errors that cannot be handled at the point of occurrence and need to propagate to a higher level. A database connection failure, for example, may need to propagate to the request handler rather than being handled in the data access layer.

Exception handling best practices include catching specific exception types rather than generic ones, using finally blocks for cleanup, not using exceptions for control flow, and logging exceptions with full stack traces at the appropriate level. Overly broad catch blocks that swallow exceptions are a common source of hard-to-debug issues.

Result Types

Result types (also called Either types) represent the possibility of success or failure as a return value. A function returns a Result that is either an Ok value or an Err value. The caller must handle both cases—there is no way to ignore the error.

Rust's Result type is the most prominent example. The ? operator propagates errors to the caller. Pattern matching on Result values ensures all cases are handled at compile time. This explicit error handling makes error paths visible and forces the programmer to consider them.

Result types provide type safety for error handling. The compiler ensures that errors are handled at some level—they cannot be silently ignored. This is a significant advantage over exceptions, where unhandled exceptions crash at runtime.

Error Boundaries

Error boundaries are a UI pattern, popularized by React, that catch errors in component subtrees and display fallback UI instead of crashing the entire page. An error boundary wraps a section of the UI and catches errors thrown by its children.

In React, class components implement componentDidCatch to become error boundaries. React 16 introduced this pattern for recovering from rendering errors. The App Router in Next.js uses error.tsx files to define segment-level error boundaries automatically.

Error boundaries allow partial page crashes rather than full page crashes. A broken sidebar does not take down the main content area. This significantly improves user experience in complex applications where some components may fail independently.

Resilience Patterns

Beyond basic error handling, resilience patterns prevent errors from causing system-wide failures. Circuit breakers stop calling a failing service to give it time to recover. Bulkheads isolate resources so a failure in one component does not exhaust shared resources.

Retry with backoff handles transient failures. Timeouts prevent waiting indefinitely for a response. Fallbacks provide degraded functionality when a dependency fails. Rate limiting prevents a single client from overwhelming the system.

These patterns are composable. A typical resilience pipeline wraps a service call with a timeout, retries with exponential backoff, a circuit breaker, and finally a fallback. Libraries like Resilience4j (Java), Polly (.NET), and resilience4s (Scala) provide these patterns as reusable components.

Error Propagation

How errors propagate through a system affects debuggability. Errors should include context about what went wrong, where it happened, and what the system was doing at the time. In distributed systems, errors should include correlation IDs that tie them to the originating request.

Errors that cross service boundaries should be translated to appropriate HTTP status codes and structured error responses. Internal error details should not leak to external clients. Consistent error response formats make client-side error handling predictable.

Error Recovery

Not all errors


Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.

Found this useful? Check out more developer guides and tool comparisons on AI Study Room.

Top comments (0)