“Program specification is the process of taking a requirement and reducing it down to the point where a programmer’s skill can take over. It is an act of communication, explaining and clarifying the world in such a way as to remove major ambiguities” –The Pragmatic Programmer
Update 2022-06-03: I’ve published a refined version of this idea
Specification Docs (Spec docs) connect high-level requirements to implementations. They clarify thoughts and gather information needed to do the work right (and do the right work).
This clarification improves design, communication, and project longevity.
Fundamentals (Rational Model)
Specification is really part of the design process, and the ideal design process is known as the ‘rational model’. Fred Brooks, David Parnas, and others suggest that this model forms a useful basis for reasoning about our design process, though it is not what actual processes look like.
The rational design process specifies that every design has:
Goal(s): The primary objective of the design. Outcomes that must be met for the design to have meaning.
Desiderata / Secondary Goals: Desired outcomes that are not necessarily essential for the design success. These goals are subject to tradeoffs when evaluating design decisions.
Constraints: Criteria that are not necessarily desired outcomes but must be met anyway. For example: budgets, timelines, limited space, performance
Utility Function: The measure of design fit to the goals and constraints. Used for comparing solutions
The designer first identifies these guiding concerns. Then, they enumerate all possibilities for each of the criteria (goals and constraints) and the ways they can be combined. Each of these decisions can be represented as a different design branch. Together all these possible branches form the design tree. Finally, the utility function can be used to pick a “best” design branch from this tree.
These concerns may not be explicitly addressed in a spec doc. However, it provides a useful framework for classifying and prioritizing design activities.
Real designs are rarely so clean as to fit the rational model. It is not practical to enumerate every possible design. Rather, the design space is explored bit at a time.
Design (and thus specification) cycles between synthesizing info and running tests. Each iteration breaks some smaller problem out of the whole. A few likely solutions are identified, the tradeoffs of each are weighed against the design priorities, and the findings are folded back into the whole.
The appropriate test will differ for each problem. It may mean talking to the customer, running a survey, researching a framework, drawing a diagram, or even writing some code.
The specification encodes understanding developed from these tests and decisions. Thus, the spec will often evolve as the system design uncovers new understanding.
“Specification doc” may sound like pages of dry technical prose, but this isn’t necessarily the case. Spec docs are a tool for formalizing thoughts and can adapt to any scale.
Fixing a bug may require a few minutes to identify expected and current behavior. A simple behavior tweak may only need a sketch. On the other end, critical or large tasks may require extensive specification. In some cases, it is even appropriate for the entire team to contribute to a specification.
The only levels of detail that are certainly wrong are none and all (Code Complete p. 119). To find the balance in between, consider
- Task importance
- Code lifespan
- Team experience
- Team familiarity with problem
Less formally, the pragmatic programmer says “Some things are better done than described.” When you reach that point, stop specifying. If you are uncertain, favor the side of detail. Some of the most expensive errors come from a false sense of confidence. Erring on detail will uncover discrepancies whereas vague specs will go unchallenged.
The content of a specification depends on the problem. However, here are some likely sections to get things started.
Motivation: The high level of what is being done, for whom, why, and why now. This should probably always be considered.
Impact: What existing work will be affected and how much. This is especially important for large changes.
Existing work: What code, resources, or other work has already been done. Key for situations that might introduce duplication or have significant research already.
Likely Changes: What aspects of the current work are most likely to change, what are the reasonable changes, how likely are they, and how impactful would they be to accommodate later. This consideration is especially important for new features and critical workflows.
Research: Any surveys, interviews, feedback, or other info that would impact the decision process. Especially important for large and high-impact tasks.
Major components: Diagrams, interface signatures, or other indications of the implementation structure. Especially important when the code structure will be atypical or unclear.
Failure cases: An enumeration of ways the expected task could fail and the recovery behavior. Especially important for completion-sensitive tasks like payment.
A spec doc clarifies the problem, which also makes an excellent catalyst for communication.
For the writer, the spec doc forces formalization and record of their thought process. This improves understanding and reduces time to resume a task after a break.
Spec docs also provide context when adding collaborators or when the implementer just has a tough question. Contributors can skip the rediscovery or “it depends” phase and provide targeted feedback.
When appropriate, spec docs also enable a design review before any code is written. This can prevent unnecessary work on hard problems or with inexperienced developers.
Spec docs should be preserved in a shared and searchable medium, even the simple ones. Common preservation methods include wiki’s or drive shares. This allows future contributors to understand motivations behind changes in the system, which enables more consistent work, speeds future development, and prevents accidental backtracking in solution functionality.
Spec docs link the design process from high-level requirements to implementations by adding enough clarity for a programmer to code it.
Specification is an iterative process that can be scaled for the need of each task and project. Specifications facilitate communication between team members and preserve reasoning for future contributors.
A Rational Design Process: How and Why to Fake It, David Parnas, http://www.laputan.org/pub/papers/Fake.pdf
Design of Design, Fred Brooks, Ch 2 The Rational Model, Ch 3 What is Wrong with This Model, Ch 5 What are Better Design Process Models, Ch 9: User Model – Better Wrong than Vague, Ch 11: Constraints are Friends
The Pragmatic Programmer, Andy Hunt and David Thomas, Section 39 The Specification Trap, Section 40 Circles and arrows
Code Complete, Steve McConnell, Ch 5.1 Design Challenges, Ch 5.4 Design Practices