The Nature of 'Specification': Understanding Software through Requirements, Contract, and Implementation
Introduction: The Ambiguity of "Specification"
In software development, terms like "specification" and "feature" are used constantly, yet their meanings are surprisingly ambiguous.
When asked "What's the spec for this feature?", what should you answer? Are we talking about what users expect? The request/response format of an API? The internal data model and logic? The same word "specification" is used to refer to entirely different concepts.
This ambiguity is more than just a linguistic issue. Treating concepts from different layers interchangeably hinders our very ability to reason about software.
This article attempts to decompose what we call "specifications" and "features" into a framework of Requirements, Contract, and Implementation, clarifying the relationships between them.
Three Layers
Requirements
Requirements are the states or outcomes that stakeholders desire in the world.
- "Users can search for products"
- "An email is sent after placing an order"
- "Invoices are generated at the end of each month"
These exist outside the software. They are expectations about what results users or business owners want to achieve, independent of the software's structure or implementation.
In the Zave-Jackson framework from requirements engineering, Requirements are defined as desirable properties of the environment (the world). Software is merely a means to realize those properties.
Contract
A Contract is the promise of both Structure and Behavior at a module's boundary (Interface).
Contracts have two aspects:
- Structural aspect: The shape of the interface ā argument types, return types, permissible states. Statically guaranteed by the type system
- Behavioral aspect: Specific outputs for given inputs, correctness of state transitions. Dynamically verified by tests
Contracts exist at module boundaries, hiding internal implementation while declaring what is promised to the outside.
Implementation
Implementation is the substance of software that realizes the Contract. Its internals are composed of two aspects: Structure and Behavior.
Structure is the totality of constraints arising from coupling, dependencies, and boundaries between concepts. This includes inter-module dependencies, invariants between concepts (e.g., "a shipped order must always have a payment date"), and technical constraints.
Behavior refers to the output for a given input, or state changes resulting from operations.
The critically important relationship here is that Structure constrains Behavior. When structure limits the state space, the patterns of behavior that can occur within it are also limited. Good structure simplifies behavior implementation and reduces the cases that need verification.
Dependencies Between the Three Layers
Direction of Causality
Implementation (Structure + Behavior)
ā determines
Contract (promise at the boundary)
ā satisfies?
Requirements (expectations about the world)
- Structure constrains Behavior
- Structure and Behavior realize the Contract
- Contract (combined with Domain Knowledge) achieves Requirements
This corresponds to the Zave-Jackson formulation S ⧠D ⨠R. S (Specification/Contract) and D (Domain Knowledge) logically entail R (Requirements).
The Process Direction Is Reversed
In the development process, we often think in the order Requirements ā Contract ā Implementation. However, this is the order of thought, not the direction of causality.
In practice, design is a back-and-forth between structure and behavior, and when translating to code, "structure first, behavior second" is effective.
Contract Inferability
Here, there is an important criterion for Contract quality. A good Contract is one where the internal Structure can be inferred from the Interface.
Why Inferability Matters
Software users (developers of other modules, API callers) infer the internal Structure through the Contract, reasoning that "in this state, I should be able to interact in this way." They use the software based on that inference.
- Behaves as inferred ā Users feel it works "as expected"
- Expected behavior matches Requirements ā Judged as "correct"
The path to "correct software" is therefore:
Contract makes Structure inferable
ā Use based on inference
ā Behaves as inferred (= as expected)
ā Matches Requirements (= correct)
Contract inferability is the starting point of this entire chain.
Inferability and Type Expressiveness
Type systems are a powerful means of enhancing Contract inferability. For example, when states are expressed using discriminated unions, the structure of state transitions can be read directly from the type signature.
// Easy to infer ā types communicate Structure
type Order =
| { status: "draft" }
| { status: "paid"; paidAt: Date }
| { status: "shipped"; paidAt: Date; shippedAt: Date };
// Hard to infer ā Structure is hidden
type Order = {
status: string;
paidAt: Date | null;
shippedAt: Date | null;
};With the former, you can infer from the types alone that "a shipped order always has a payment date." With the latter, you'd need to read documentation or source code.
When the cognitive structure of the domain is directly reflected in the code, an observer with domain knowledge can correctly infer the Structure just by looking at the interface. That's why the feedback loop is fast.
Diagnosing "It Doesn't Match the Spec"
This framework enables structural diagnosis of ambiguous reports like "it doesn't match the spec" or "it's a bug."
| Symptom | What's Actually Broken | Resolution |
|---|---|---|
| Doesn't behave as inferred | Contract doesn't accurately convey Structure | Improve I/F design, increase type expressiveness |
| Behaves as inferred but not as expected | Requirements and Contract are misaligned | Review requirements and I/F design |
| Can't infer behavior at all | Contract inferability is low | Redesign I/F, introduce type-based state representation |
| As inferred and as expected, but incorrect | Requirements themselves are wrong | Reconfirm with stakeholders |
A single word like "specification" cannot distinguish between these cases. Having the vocabulary of Requirements, Contract, and Implementation enables precise identification of where the problem lies.
The Same Ambiguity in "Feature"
The word "feature" carries the same structural ambiguity.
| Layer | More Precise Term | Meaning |
|---|---|---|
| Requirements | Capability | The ability users want to gain |
| Contract | Protocol | The I/F promise that provides that ability |
| Implementation | Mechanism | The internal structure and behavior that fulfills the promise |
When someone says "please add this feature," whether it means adding a Capability, changing a Protocol, or implementing a Mechanism makes the task entirely different.
Conclusion
The everyday terms "specification" and "feature" refer indiscriminately to concepts from different layers: Requirements, Contract, and Implementation.
- Requirements are expectations about the world, existing outside the software
- Contract is the promise of Structure and Behavior at the boundary, with types and tests guaranteeing each aspect respectively
- Implementation realizes the Contract through internal structure and behavior, where Structure constrains Behavior
The key quality of a good Contract is inferability. When internal Structure can be inferred from the interface, when the system behaves as inferred, and when that matches Requirements ā that's when people feel the software is "working correctly."
Having this vocabulary transforms the ambiguous problem report of "it doesn't match the spec" into a structural analysis of what's broken at which layer. I hope this framework serves as a tool for thinking and communicating about software with precision.
