How Software Development ChangedLesson 33 of 87

Contents

How Software Development Changed

Lesson 33 of 87. Sign in to keep your place.

Module 4 · Saying what you want

Writing down the why

9 min read

Code says what, never why

Read any line of code and you can work out what it does. You cannot work out what else was considered, what was tried and abandoned, or which constraint made the obvious approach impossible. That information exists only in someone’s memory, and memory leaves the company.

This was always a problem. It became a sharper one, because the memory used to belong to a person who had spent a day on the decision, and it now sometimes belongs to a chat session that was closed. A decision record is the only durable place for it.

The format, which should stay small

An architecture decision record is one file, in the repository, numbered, immutable once accepted. The original form from Michael Nygard is five headings and nobody has improved on it:

Markdown
# 0014 — Store converted amounts on the invoice line

Status: accepted (2026-08-14)

## Context
Finance needs invoices in the currency charged, with the total in
the account's base currency. Rates move daily. Invoices are legal
documents in three of our markets and must not change after issue.

## Decision
Store both the original amount and the converted amount on each
line, plus the rate and the timestamp used. Compute at write time.

## Consequences
+ Invoices are immutable; a rate change cannot alter a past document.
+ Reprinting an old invoice needs no rate lookup.
− Three new columns and a backfill over 4.1M rows.
− If we discover a rate was wrong, correcting it means a credit note,
  not an update. Finance has confirmed this is what they want.

## Alternatives considered
- Compute at read time from a rate table. Rejected: past invoices
  would silently change when rates were corrected.
- Store only the base-currency total. Rejected: customers in Jakarta
  need to see what they were actually charged.

Under a page. Fifteen minutes to write. Readable in ninety seconds in two years.

The section that matters most is the one people skip

Alternatives considered. A record without it is a description of what happened, which the code already provides.

The purpose of an ADR is to stop a future engineer, or a future you, from spending three days rediscovering why the obvious approach does not work. The alternatives section is where that saving lives. Two lines per rejected option: what it was, and the specific reason it failed.

The second-most-skipped section is the negative half of Consequences. A record that lists only benefits is advocacy. Writing the costs down is what makes it possible to revisit the decision honestly when the costs turn out to be larger than expected.

What earns a record

Not everything. A rough test: would somebody reasonably assume this is a mistake?

  • Fail open rather than fail closed on a rate limiter
  • Storing money as an integer of minor units rather than a decimal type
  • Not using the framework’s built-in mechanism for something it does provide
  • Choosing one database, queue or provider over another
  • Deliberately duplicating code rather than extracting a shared abstraction
  • Any decision where the team argued for more than twenty minutes

That last one is the most reliable trigger. If it took an argument, it needs a record, because the argument will recur and the second one will be less informed than the first.

Where they live and how they die

In the repository, in docs/adr/, in the same pull request as the code they describe. Not in a wiki, not in a ticketing system, not in a shared drive. The reason is simple and empirical: a document in a separate tool is not read by anyone who is reading the code, and it drifts within weeks.

Records are immutable. You do not edit 0014 when the decision changes. You write 0021, mark it as superseding 0014, and add one line to 0014 pointing forward. The history of a decision is often more informative than its current state (it tells you what changed and what you learned) and editing in place destroys exactly that.

The specific reason this got more valuable

Generated code is locally sensible and globally uninformed. It will helpfully “fix” a deliberate oddity, because the oddity looks like an error and the reason for it is not in the file.

The fail-open branch on your rate limiter looks like a missing else. The duplicated function looks like something that should be extracted. The manual retry loop looks like it should use the library’s decorator, which will retry your non-idempotent POST.

A one-line comment pointing at the record is the cheapest defence:

Python
# Fails open by design — see docs/adr/0009-rate-limiter-availability.md
except RedisUnavailable:
    return allow()

That comment costs eight words and it is the difference between a future change respecting a decision and a future change silently reversing it. It works on human collaborators too, and it always did: what changed is how many collaborators there are and how quickly they arrive.

Every lesson here is open. Your progress is recorded once you pass the AI at Work exam.

Check your understanding

A team writes decision records containing Context, Decision and Consequences, but omits Alternatives Considered to keep them short. What specifically is lost?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minWhat the tool can actually know about your codebase

Stuck on something?Ask Larissa, your AI tutor

How to cite: Addaly (2026) ‘Writing down the why’, in How Software Development Changed. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/how-software-changed/decision-records (Accessed: 13 October 2026).