Skip to content

Archive

Error Handling

24 articles
Software Engineering 15 Sep 2026 6 min read

Savepoints Create Partial Rollback Boundaries

A database transaction does not have to choose only between keeping every statement and discarding the entire unit of work. In systems that support transaction savepoints, a transaction can mark an intermediate boundary, perform additional operations, then roll back changes made after that boundary while keeping the transaction itself active. That behavior makes a savepoint more than a convenience for error recovery. It creates a local rollback boundary inside a larger atomic unit, with semantics that remain tied to the surrounding transaction. Nothing before the final commit becomes durable merely because a partial rollback succeeded.

Software Engineering 10 Sep 2026 8 min read

Saga Pattern for Multi-Step Workflows

Saga Pattern for Multi-Step Workflows A workflow reserves inventory, charges a payment, and schedules delivery. Each step is handled by a different component with its own state. The inventory reservation succeeds, but payment fails. What should the system do with the reservation that already committed? A single database transaction can’t usually roll back work that has already been committed by independent components. The saga pattern handles this kind of workflow by treating it as a sequence of local transactions. When a later step fails, the workflow runs explicit compensating actions for earlier steps where business reversal is possible.

Software Engineering 10 Sep 2026 8 min read

Parse at Boundaries to Protect Domain Invariants

Parse at Boundaries to Protect Domain Invariants A request arrives with a string that is supposed to be an order quantity. One function checks that the string contains a number. Another checks that the number is positive. A third assumes both checks already happened. Months later, a new caller reaches the third function directly and passes zero. The problem isn’t simply missing validation. The program keeps carrying a weak representation after it already knows something stronger about the value.

Software Engineering 10 Sep 2026 10 min read

Designing Graceful Degradation for Partial Failures

Designing Graceful Degradation for Partial Failures A page needs product details, recommendations, reviews, and delivery estimates. The product service is healthy, but the recommendation service times out. Should the whole page fail? Sometimes yes. If the missing dependency is required to produce a correct result, failing the operation is the right behavior. But when the missing part is genuinely optional, turning one local failure into a complete outage throws away useful work.

Software Engineering 09 Sep 2026 9 min read

Designing Operations to Avoid Partial State

A function can report an error and still leave trouble behind. The first few steps may have changed state before a later step failed, so the caller receives a failure while the system now contains a mixture of old and new values. This is partial state: an operation did not complete, but some of its intended changes became visible. Partial state makes retrying, debugging, and reasoning about invariants harder because “the operation failed” no longer tells you what state remains.

Software Engineering 08 Sep 2026 9 min read

Testing Resilience with Fault Injection

A service can pass every normal-path test and still behave badly when a dependency times out, a write fails halfway through, or a connection disappears at an inconvenient moment. The problem is often not missing error handling. It is that the team has never observed whether the error handling produces the behavior they expect. Fault injection is the deliberate introduction of a controlled failure into a system or test. Instead of waiting for a real dependency to fail, you make a specific failure happen and observe the consequence.

Software Engineering 08 Sep 2026 10 min read

Making Invalid States Hard to Represent

A function receives an Order and immediately checks whether its total is negative, its currency is missing, and its status is compatible with its payment state. Another function repeats some of those checks. A third forgets one. The codebase contains validation everywhere, yet invalid combinations still appear. The deeper problem is not a shortage of if statements. The program allows states that its own rules say should never exist, then asks every consumer to defend itself against them.

Software Engineering 07 Sep 2026 9 min read

Testing Failure Semantics, Not Just Errors

A test that expects an error can still miss the most damaging part of a failure. An order operation may return payment declined correctly while also marking the order as paid. A file import may report that parsing failed after writing half of its records. A retry may succeed but send the same notification twice. In each case, the visible error is correct. The failure semantics are not. Failure semantics describe what the system promises about state, side effects, and subsequent operations when something goes wrong. Testing them means asking more than “did this fail?” This article shows how to identify those promises and turn them into focused tests.

Go 07 Sep 2026 12 min read

Read Large Line-Oriented Input Safely in Go with bufio.Scanner

Line-oriented input looks simple until one record is much larger than expected. A program may process thousands of ordinary log lines correctly, then stop on a generated stack trace, a large JSON record, or a malformed input that contains no newline for megabytes. Go’s bufio.Scanner is convenient for this job because it handles tokenization and defaults to scanning lines. But that convenience comes with an important boundary: a scanner has a maximum token size. If a token cannot fit within that limit, scanning stops with an error.

Go 07 Sep 2026 9 min read

Handle Go HTTP Response Bodies Without Leaking Connections

An HTTP request can appear to work while quietly making later requests more expensive. A common cause in Go clients is mishandling http.Response.Body: forgetting to close it, returning before cleanup is arranged, or assuming that an HTTP error status is returned as a Go error. The important mental model is that a successful Client.Do call gives your code ownership of a stream, not a byte slice. The response headers have arrived, but the body is consumed as you read it. Your code must decide how much of that stream it needs and must close it when finished.

Go 07 Sep 2026 8 min read

Handle Buffered Write Errors Correctly in Go

Buffering output can reduce write overhead, but it also changes when an I/O failure becomes visible. With an unbuffered writer, a call to Write normally reaches the underlying destination immediately. With bufio.Writer, a successful call may only mean that the bytes were accepted into memory. The actual write to a file, socket, pipe, or other destination can happen later, often during Flush. That distinction creates a common production bug: code checks every apparent write, defers Flush, and still returns success after the final flush fails.

Go 07 Sep 2026 7 min read

Combine Independent Failures in Go with errors.Join

A function sometimes performs several independent operations and more than one can fail. Cleanup is a common example: closing one resource should not prevent the program from attempting to close the next. Validation can have the same shape when callers benefit from seeing several independent problems at once. Returning only the last error loses information. Returning only the first error may hide failures that happened later. Go’s errors.Join provides a standard way to return one error value that still wraps multiple underlying errors.

Cybersecurity 06 Sep 2026 12 min read

Separate Public Errors from Diagnostic Details

An application fails while processing a request. The easiest implementation is often to return the exception message to the caller. That feels helpful during development because the response explains exactly what went wrong. In production, the same detail can expose information that the caller did not previously know: filesystem paths, database structure, dependency names, internal service addresses, object identifiers, configuration values, or fragments of sensitive input. A stack trace can reveal even more about how the application is assembled.

Software Engineering 05 Sep 2026 9 min read

Designing Idempotent Operations for Safe Retries

A caller sends a request to create a payment. The server processes it, but the response is lost because the connection closes. The caller now has a difficult choice: retry and risk charging twice, or stop and risk leaving the payment incomplete. This is not mainly a networking problem. It is an operation-design problem. When a caller cannot tell whether an attempt succeeded, retrying is only safe when the system has a way to recognize that the new attempt represents the same intent.

Software Engineering 04 Sep 2026 8 min read

Translating Errors at Abstraction Boundaries

A function can hide how data is stored and still leak the storage technology through every error it returns. When that happens, callers become coupled to details the abstraction was supposed to contain. Suppose an order service asks a repository to load an order. The repository promises to find orders; it does not promise that callers understand SQL drivers, HTTP clients, file formats, or whichever mechanism happens to sit underneath it. If a missing order appears as a driver-specific NoRows exception today and an HTTP 404 exception after a migration, callers must change even though the repository’s meaning did not.

Cybersecurity 04 Sep 2026 9 min read

Keep Internal Error Details Out of Client Responses

When an application fails, developers need enough detail to diagnose the problem. The client usually does not. If the same exception text, stack trace, database error, filesystem path, or upstream response is sent to both places, an ordinary failure can become an information leak. The consequence is not that every leaked error immediately compromises a system. The problem is that internal details can reveal data, identifiers, software structure, trust relationships, or assumptions that were never meant to cross the application’s public boundary. They can also expose secrets when sensitive values have been included in an exception or diagnostic message.

Software Engineering 04 Sep 2026 10 min read

Designing Failure Containment Boundaries

A component fails. Soon unrelated requests become slow, worker queues stop moving, and healthy features begin returning errors. The original defect may be small, but the system has allowed its effects to spread. Failure containment is the design practice of limiting how far a fault can propagate. The goal is not to prevent every failure. That is unrealistic. The goal is to make a local failure stay local enough that the rest of the system can continue useful work or fail in a controlled way.

Software Engineering 03 Sep 2026 8 min read

Validating at Boundaries to Contain Invalid Data

Many software failures begin far from the place where they are eventually noticed. A malformed value enters through an API, configuration file, message, command, or user action. The program accepts it, passes it through several layers, and fails later when some unrelated operation assumes the value is valid. By then, the original cause is harder to see. A useful design principle is to validate data at the boundary where it enters a trusted part of the system. A boundary is any point where code receives information whose assumptions it does not yet control. The goal is not to scatter checks everywhere. It is to turn uncertain input into either a known-valid value or an explicit failure before the rest of the program relies on it.

Go 03 Sep 2026 10 min read

Use defer for Reliable Cleanup in Go

Resource cleanup is easy to get right on the happy path and easy to miss on an early return. A function opens a file, acquires a lock, or starts a trace span; a later check fails; the function returns before reaching the cleanup statement. Go’s defer statement addresses this by scheduling a function call to run when the surrounding function returns. Used well, it places cleanup next to acquisition and makes every return path easier to reason about.

Go 03 Sep 2026 12 min read

Understand Nil Interface Values in Go

A Go program can print an error that looks empty, enter an if err != nil branch, and still be holding a nil pointer underneath. This behavior surprises developers because it seems to violate the simple rule that “nil means no value.” The rule is still consistent. The missing piece is that an interface value has two parts: a dynamic type and a dynamic value. An interface is nil only when neither part is set.

Software Engineering 03 Sep 2026 8 min read

Designing Errors for Actionable Failures

Errors are part of a program’s interface. They tell callers that an operation could not produce its promised result, but useful error handling goes further: it preserves enough meaning for the caller to decide what to do next. Weak error handling tends to fail in two opposite ways. Some code hides failures by returning defaults, logging and continuing, or catching exceptions too broadly. Other code exposes every low-level detail directly, forcing callers to understand implementation choices that should have remained private.

Rust 02 Sep 2026 6 min read

Recovering Safely from Poisoned Mutexes in Rust

A mutex protects shared data from concurrent access, but mutual exclusion alone does not guarantee that the data remains valid. A thread can panic halfway through a multi-step update and release the lock during unwinding, leaving the protected value in a state that other threads should not blindly trust. Rust’s standard Mutex records this situation through poisoning. A poisoned mutex is still lockable, but acquiring it returns an error that forces the caller to decide whether continuing is appropriate.

Go 01 Sep 2026 2 min read

Preserve Cancellation Causes with context.WithCancelCause in Go

Go’s context.Context propagates deadlines and cancellation across API boundaries. Traditional cancellation tells downstream work that it should stop, but ctx.Err() only reports context.Canceled or context.DeadlineExceeded. Sometimes the reason matters. Go 1.20 introduced context.WithCancelCause, and later releases added cause-aware deadline helpers. They preserve a domain error without changing normal cancellation behavior. Attach a cause to cancellation package main import ( "context" "errors" "fmt" ) var ErrSuperseded = errors.New("request superseded") func main() { ctx, cancel := context.WithCancelCause(context.Background()) cancel(ErrSuperseded) fmt.Println(ctx.Err()) // context canceled fmt.Println(context.Cause(ctx)) // request superseded } Code that only understands Context still sees ordinary cancellation. Code that needs diagnostic detail can call context.Cause.

Rust 01 Sep 2026 5 min read

Practical Error Handling in Rust with Result and the ? Operator

Rust makes recoverable failure part of a function’s type. Instead of relying on exceptions, operations that can fail commonly return Result<T, E>, forcing callers to either handle the error or propagate it. That explicitness can feel verbose at first. The ? operator and well-designed error types keep the code concise without hiding failure paths. Result represents success or failure Result<T, E> has two variants: Ok(value) Err(error) Reading a file therefore returns either the file contents or an I/O error: