Failing by Design

Error handling done at the end is a chore. Error handling done at the start is just design. This chapter is about moving failure to the front of the process, where it's cheap.

The reason error handling feels like drudgery is that we usually add it last — bolting recovery onto code that was shaped entirely around success. But failure states aren't an afterthought to the design; they are part of the design. A "save" feature isn't done when it saves; it's done when you've decided what happens when it can't.

Design the Failure States With the Feature #

Design also means deciding which states are acceptable, which are recoverable, and which must be prevented. A checkout system can honestly support payment_failed, paid_waiting_for_fulfillment, and email_confirmation_delayed. It should not support "charged twice," "paid but no order exists," or "access granted without a payment record."

The same is true when software touches the physical world. If you ship products, lost_in_transit, delivery_delayed, wrong_address, and refund_pending are real states. They are not edge cases just because they happen outside your code. If the business can enter that state, the product needs a way to represent it.

Make Illegal States Unrepresentable #

You cannot make every real-world failure impossible. You can make the dangerous internal contradictions impossible, or at least visible. "Payment failed" is a normal state. "Payment succeeded but the order disappeared" is a design failure.

Pre-Mortems and Failure Inventories #

Budget for Failure Work #

Once you know which failure states are possible and desirable, you can draw the boundaries that keep those states honest. That is the next chapter.

Front End vs. Back End Notes #


Related chapters: Failure Modes · Whose Fault Is It Anyway? · Boundaries & Transactions