Building Software

Engineering Fundamentals for the Agent Era

Contents Section 6, Design

Error Handling and Failure Paths

Mistakes to catch in review

  1. A catch-all handler that logs the error and returns a default value, so failures look like successes.

  2. Errors re-raised without context, leaving a message like 'failed' with no input, cause or location.

  3. Connections, locks and temporary files left open because cleanup only runs on the success path.

Deciding what happens when things go wrong: which errors to handle, which to pass up, and which should stop the program.

Topics

Failing Fast and Loudly
Stopping at the first sign of an impossible state instead of continuing with bad data.
Exceptions and Error Values
Thrown exceptions versus returned result types, and making failure visible in function signatures.
Adding Context to Errors
Wrapping errors with what was being attempted and on which inputs, without leaking secrets.
Translating Errors at Boundaries
Turning internal failures into the right status codes, user messages and retry signals.
Resource Cleanup
Finally blocks, defer statements, context managers and ownership rules that release every acquired resource.
Assertions and Invariant Checks
Code that checks for impossible states before writing data, so bugs surface where they start.

You understand it when you can

  • Trace every failure path through a function and state what the caller sees on each one.
  • Sort a list of error conditions into those to handle locally, those to propagate and those that should crash the process.
  • Rewrite a function so its cleanup runs on every exit path.

Drill

An agent wrapped a payment-settlement function in a handler that catches every error, logs it and returns a status of 'ok', and it opens a database connection before the protected block. Find what callers see when settlement fails and what leaks each time it does.

Start here

Watch

Scott Wlaschin — Railway oriented programming

Scott Wlaschin, 2021. 57-minute talk.

Shows how returning Result values instead of throwing makes every failure path visible in function signatures, and how validation and error cases compose without nested try/catch.

Read

The Pragmatic Programmer: Your Journey to Mastery

David Thomas and Andrew Hunt, 2019, 20th Anniversary Edition (2nd edition).

The topics 'Dead Programs Tell No Lies', 'Assertive Programming' and 'How to Balance Resources' cover crashing early, asserting impossible states, and releasing every resource on every path.

Release It!: Design and Deploy Production-Ready Software

Michael T. Nygard, 2018, 2nd edition.

Case studies of production outages that spread through unhandled failure paths, plus stability patterns (timeouts, circuit breakers, fail fast) that decide what a caller sees when a dependency breaks.

Code Complete: A Practical Handbook of Software Construction

Steve McConnell, 2004, 2nd edition.

The Defensive Programming chapter separates assertions for impossible conditions from error handling for expected ones, and covers exceptions, barricades at trust boundaries, and robustness versus correctness.

Primary sources

  • Reference

    OWASP Error Handling Cheat Sheet

    How to translate internal failures at the boundary into a generic client response with the correct status while logging the detail server-side, so stack traces and internals never reach callers.

  • Reference

    Working with Errors in Go 1.13 (The Go Blog)

    A clear primary explanation of wrapping errors with context (%w) while keeping the cause inspectable with errors.Is and errors.As. The same principle applies in any language that has error chaining.