I'm always in search of good metaphors for software design. Good or not, here's one:
A Design is a Mold for Code
In manufacturing, a mold is a hollow shape that fills with fluid material which expands into the space and sets to form a casting.
Image: A useful mold. Generated by Dall-E
Like in manufacturing, code fills the negative space of a software design. Over time, code sets and becomes harder to change.
Imagine two engineering teams who independently "fill" (implement) a design, and the design is the set of standards for a web browser. The two implementations are Chrome and Firefox. The two browsers have independent codebases[1] which take vastly different shapes, but as far as web standards are concerned, the two are equal. You could switch from Chrome to Firefox (which I recommend) and still browse compliant web pages with no discernible change.
Programmers who also like math sometimes call two pieces of code which can be swapped isomorphic with respect to the spec.
It's important not to reverse the causality: a design causes the work product, not the converse. A mold causes a casting, and a software design causes code. Nevertheless, to some extent, one can go in reverse, i.e. reconstruct a mold from a casting or infer a design from code.
The reconstructed mold might lose some fidelity, like sharp edges or precise dimensions, but it's far worse for code. Looking at code without supplemental information, a new dev can only infer a dim, vague notion of its design. She might even sense the mind(s) behind the design. However, it would be costly if even possible to losslessly reconstruct the design, i.e. to such a fidelity that it could be the basis for an isomorphic implementation.
In pseudocode:
[Test]
fn Reverse_ProducesIsomorphicDesign()
let design1 : ...
let code1 = implement(design1)
let design2 = reverse(code1)
let code2 = implement(design2)
assert(isomorphic(code1, code2)) // Fails
Above: The roundtrip from a design to code back to design is lossy.
There are a few reasons for this:
- A codebase maybe too large to fit into a dev's head. The codebases for Chrome and Firefox are huge. Like the limited context window of ChatGPT, a human has limited working memory and forgets. Though with time and repetition, knowledge transitions to long-term memory.
- It's hard to infer high-level concepts from low-level details. For example, decompiling assembly back to C.
- Design and code have a many-to-many relationship.
- There can be many implementations that satisfy a design.
- There can be many designs that result in the same code.
This is a problem for software that outlives its creator(s). The software will languish unless knowledge is retained and replicated.
There are some things developers can do to keep their software alive:
- Write down the design. Use natural language
- Make the design apparent in the code
- Use precise abstractions
- Use effective metaphors
- Make the code fit in your head
Notes
- Firefox and Chrome are not truly independent by this definition. Plenty of people who have worked on one browser have also worked on the other. Moreover, as open source projects, both teams can see the other's code. Chromium source and Firefox source