Unthought.

Specification before code

The document that names every entity, every state, and every failure path — and why a project that cannot produce it should not start.

The least popular document in a software engagement is the one that decides whether it finishes. Before code exists, we write a build specification: every entity the system holds, every state a record can occupy, every transition between those states, and what happens on each path where something goes wrong. It is dull to write and duller to read, and skipping it is the most reliable way to spend twice the budget.

The specification is not a wish list and it is not a wireframe. It is a description precise enough that two people reading it would build the same thing. Where it is vague, the vagueness is not a stylistic problem — it marks a decision nobody has made yet, and that decision will have to be made eventually, either deliberately now or accidentally later by whoever hits it first while writing code.

Entities come first. What does the system store, what identifies each thing uniquely, what does it own, and what owns it. Most confusion in a business application traces back to two entities that were treated as one because they looked similar at the start — a customer and an account, an order and a shipment, a booking and a payment. Separating them later means a migration; separating them at the specification stage costs a paragraph.

Then states. A record in a business system is almost never a static row; it is something moving through a lifecycle, and the lifecycle is where the actual rules live. Which states exist, which transitions are legal, which are irreversible, and who or what can cause each one. A state diagram that fits on one page is usually a sign the domain is understood. One that cannot be drawn at all is a sign that the business has different answers depending on who is asked, which is worth discovering before it is encoded.

Failure paths are specified alongside success paths, not after them. What happens when the payment provider times out after taking the money. What happens when two people edit the same record. What happens when an import contains a row that violates a constraint halfway through. These are the questions that determine whether a system is operable, and they are systematically the ones deferred because the happy path is more satisfying to build.

The specification also names what the system will not do. A boundary written down at the start is a boundary; discovered in month three it is a disappointment. Scope stated in the negative is more useful than scope stated in the positive, because the positive list is always understood to be partial and the negative list is understood to be deliberate.

Access and permission are part of the specification, not a later hardening pass. Who can see each entity, who can change it, and what the system does when someone attempts an action they are not entitled to. A permission model retrofitted onto a working application is one of the more expensive changes available, because it touches every query rather than every screen.

Reporting requirements are captured at the same time, because they constrain the data model. A figure the business will need to produce monthly has to be derivable from what the system stores, and discovering after launch that a required breakdown was never recorded means the history simply does not exist. The cheapest moment to decide what must be measurable is before anything is written.

Where an existing system is being replaced, the specification includes what the old system actually does rather than what it was documented to do. Those differ more often than not, and the difference is usually a behavior somebody depends on. Reading the code and watching the system in use are both part of the work.

The document is short. A specification that runs to eighty pages has stopped being a decision record and become a substitute for the conversation it was supposed to conclude. What matters is that every open question is either answered or explicitly marked as open with a named owner and a date.

It is also revisable. Specifications change as the work exposes things nobody could have known at the start, and pretending otherwise produces the failure mode where the document is quietly abandoned in week two and the project proceeds on memory. A change to the specification is a change like any other: written down, dated, and visible.

The commercial effect is that scope disputes become factual rather than adversarial. When a request arrives that was not specified, the conversation is about whether to add it and what it costs, not about whether it was always implied. Both parties are reading the same document, which is a materially different negotiation from both parties recalling the same meeting.

Writing it is also the cheapest estimate available. A specification detailed enough to build from is detailed enough to price honestly, and the parts that resist estimation are visible as the parts that resist description. A number produced before the document exists is a guess with a contract attached to it, and the gap between the guess and the work is absorbed by somebody — either the client pays for a change order or the builder eats the difference and quietly reduces care elsewhere.

If the specification cannot be written, we say so instead of starting anyway. The part that cannot be written is the part nobody has decided, and building around an undecided thing is how a project acquires the architecture nobody chose.

Exclusions

What this does not cover.

  • Fixed-price quotes issued before a specification exists. A number produced without one is a guess with a contract attached.
  • Discovery engagements that end in a slide deck, not a document the work can be built from.
  • Rewrites recommended before the existing system's actual behavior has been read.