Introductory Note to Chapter 5
I started this chapter maybe a few days before Halloween, and shelved it temporarily to take care of something, thinking I'd get back to it in a week. This, I sheepishly admit, did not happen as soon as I'd have liked it to.
What I was working on lay at the nexus of probably three separate decade-spanning threads of inquiry. My professional focus is, and has been for at least this long, about comprehension: helping people understand complex situations so they can make intelligent decisions and operate as a unit.
Now, I write documents and do slide presentations just like the next tech consultant, but I have had the growing impression, especially as the Web matures and we tuck into the early-mid 21st century, that the capabilities of digital media are still going miserably underutilized.
I have remarked in a number of places that if you take a garden-variety webpage and you shave off the navigation bar and the various insets, headers, footers etc., what you get is scarcely distinguishable from a Word document, which itself might as well be a printed piece of paper. Exceptions to this are mainly when the Web is being used as a delivery mechanism for software, rather than content. The latter is my source of interest, specifically dense hypermedia content, where traversing links—which themselves take many forms—is a major part of the experience.
When I say dense hypermedia, I mean it in contrast to sparse hypermedia, which is what we get with the Web as it is currently tooled: a lot of text and not a lot of links, and moreover a lot of things that could be links but aren't.
- Hiving off details, digressions, parentheticals: hypermedia (text or otherwise) could be unparalleled in its ability to get to the point. When you write an ordinary document, you have to include all this ancillary information inline: em-dash asides, parentheses, insets, marginalia, footnotes, endnotes, and so on. Making this content optional means busy people don't have to read any more than they need to. - Providing background information: the same mechanism used to abridge digressive details also works to discreetly help the reader get up to speed if they aren't familiar with some aspect the subject matter—say a particular concept, term, or entity. No need to be seen asking “dumb questions”. - Reusing content: not just to save time, but to avoid creating duplicates, ensuring all referring documents remain accurate when said reused content is updated. - Mixing media: something the Web is already pretty good at: integrating text, graphics, audiovisual, and interactive elements. Although… - Providing computable representations of structured data: mixing media, especially interactive visualizations, depends on data that can be used directly by the computer without any additional (human-powered) formatting or interpretation. Structured data may be represented in a table, record set, or chart for human consumption, but without formal structure and labels, it can't be repurposed for computation. We solve this by generating variants—ways of asking for the same content in a different container—and by embedding this formal structure into the documents themselves.
When I made a new website in 2007-2008 (after not having one since around 2002), I was determined to write it under a “proto” dense-hypermedia paradigm (I hadn't yet come up with the term): short documents, never more than a screen worth of content, completely stuffed with links—every thought gets its own page. This, it turns out, is really hard, so I eventually gave up and started writing ordinary essays—apparently hundreds of them.
The problems I was running into at the time reduce to the fact that the overhead of managing the hypertext got in the way of actually writing it. Specifically:
- If I wanted to link to something that didn't exist yet, I had to stop and think up a URL for it. Now, wikis handle this, but they do it badly, as URL naming is consequential, and half the reason for so much link rot on the Web is people renaming them willy-nilly. - Then, I had to create a stub document so there was something to link to. Or otherwise, disable the inbound links so there is no route to a nonexistent or otherwise unfinished document. Again wikis do handle this, but also badly. The real problem here though was just keeping track of the stubs that I had started but not yet finished. - The surface area of hypertext is surreptitiously huge. I would set out to write something without the faintest sense of how much effort it would take, in part due to raw overhead, but mainly because there's an invisible dimension that absorbs all your effort.
Conventional documents are simultaneously bigger and smaller—or, perhaps, thinner—than comparable hypertext networks. Due to the constraint that all parts of an essay, for example, have to show up on the page, it has a relatively well-defined scope. Hypermedia, by contrast, has an infinite number of nooks and crannies in which to sock away more stuff. It is actually reasonable to expect the amount of work involved in any hyperdocument to be proportional to the square of a comparable ordinary text. The formal reason has to do with graph theory, but the intuition is that there's another dimension that we can't see when we're looking at a document—itself strictly-speaking a one-dimensional object—because we're looking “down” at that second dimension edgewise.
In the last 15 years, I have developed techniques and tools that solved the bulk of the aforementioned problems, with the ultimate aim of returning to the dense hypermedia question. Outside of a few specialized engagements, though, I did not tackle it head-on until late 2022. (I had one of those “if not now, when” moments.) Remarks:
- Content reuse is essential, if for no other reason than a big chunk of linkable content can and absolutely should be reused. It's a whole universe of extra work otherwise, and a wild one at that. Microcontent and metadata is individually small enough to deem it not worth the candle to look up instead of writing anew, but (especially since it is linked together too) adds up to a formidable time suck. I spent about a week and a half just consolidating a bunch of duplicates I had generated over the last five years or so, a debt that had finally come time to repay. - The tooling and infrastructure I had written got quite a workout. In some cases it was because there was a particular effect I wanted to achieve that I hadn't previously anticipated in the tooling, in others because I was trying to fit together parts that hadn't previously been put together, and others still because of reconciling changes with third parties. As I wrote elsewhere, I probably stepped on every rake in the system, but the good news is, I'm now out of rakes. - There is all sorts of hidden work in the final presentation. Once you create all the parts and gather them up, it might not be immediately obvious how to arrange them. You will need time to do a final curatorial packaging of the artifact. This could be palliated somewhat by establishing a set of archetypal hypermedia artifacts, but those archetypes are only going to be identified by stumbling around in the dark. - I'm actually inclined to say that the logic of
deliverable = artifact
is anathema to the spirit of hypertext. It costs asymptotically nothing to add a single connection or piece of content to a hypermedia network, such that to cut all that activity off because the product is “done” seems like a bad idea. While there should perhaps be checkpoints of “done-ishness”, these artifacts (if we can call them unitary artifacts) should never really be considered fully “finished”.The way I did this dense hypermedia project—and the wisdom of this is still debatable—was to write out the full-length document, and then start bucking it up, separating it into gloss versus details. There is also a completely stripped-down, structured version of the rationale itself, which is linked throughout the document(s).
What I have found in my career—and what ultimately motivated me into this line of inquiry—is that technically-inclined readers insist on details that cause the rest of the population—which includes business leaders—to bristle. What little technical discussion you absolutely cannot scrub away is going to have to be nerfed with a like-I'm-five explanation of what the detail is, and why I insist on bothering you with it. The technical people will naturally find this boring and remedial. You just can't reconcile these two audiences with the same document, but you should be able to with a hyperdocument, even if it's just to share content common to both audiences without having to make a duplicate copy of it.
What does this mean for you, a Nature of Software subscriber?
- Well, for one, the resumption of a monthly publishing schedule, as the thing that has been eating my waking life for the last several weeks is now done, and monthly seems to be what I can ordinarily manage. - But more importantly, since I just ironed out the kinks on a whackload of new capability, I'm setting up a second archive on
the.natureof.software
where I'm going to put augmented versions of published chapters and other resources, to which all subscribers will have access.This project is, at root, an attempt to make Christopher Alexander's The Nature of Order relevant to software development, while at the same time abridging the reading task by an order of magnitude. This is still on track. Indeed, I suspect that part of the reason for friction around the uptake of Alexander's ideas is that there is just so damn much material to read. While the main artifact here will still be a book-like thing serialized as a newsletter, there is still plenty of opportunity to both compress and enrich the message even more. Alexander's work is furthermore full of concepts and processes that can be spun out as structured data to remix and build upon.
In general I endeavour to move into the hypermedia space and live there permanently, such that conventional documents like the one you're reading are projections of higher-dimensional structures, flattened and desiccated for less dynamic media. The newsletter will continue, of course, but it will be a snapshot of what's happening online. That's the goal, at least.
At the time of this writing, there is nothing on
the.natureof.software
, because I just bought it. There are a few things I need to do before I have something minimally viable up there, like setting something up to sync accounts, and making an index page. Then I can work on certain data products like a concept scheme (the bulk of which I have almost certainly written down elsewhere), and interactive capabilities like collaborative annotation. Definitely by next issue there will be something there to log into.And now, enjoy Chapter 5 of The Nature of Software: Positive Space.
The quintessential figure-ground inversion: the Rubin vase.
The third and final chapter in the cluster I am calling “the zone between two centers is also a center” is positive space. This refers to figure-ground inversion, or the shaping of what is typically called negative space—that is, the part on the page without the ink—into something meaningful. The phrase positive space is an Alexanderism, and he meant something a little more specific, that (in the case of architecture) if you could magically swap wall with air, the resulting shape would still be coherent, and more or less convex. Think the three-dimensional analogue to the Rubin vase, the FedEx logo, or an Olly Moss poster.
The rightward-facing arrow in the space between the E and the x in the FedEx logo is a staple topic of discussion in elementary graphic design courses.
Olly Moss has built a reputation as the go-to guy for movie posters that do clever things with figure-ground inversion.
What I call positive space occurs when every bit of space swells outward, is substantial in itself, is never the leftover from an adjacent shape.
Christopher Alexander
This is the first sentence of section 2.5 of Book 1 of The Nature of Order. I wish he had done this for all the other ones, because you almost don't need to read the thousand or so words that follow it. Positive space is about treating air the same way as you treat wall: you want the shape to be a thing, not a non-thing. Positive space is about geometric hygiene—not littering the area with misshapen non-entities between identifiable centers.
Alexander opens with the Nolli map as his first example. By far the most accurate surveying project of its time, we can see that 18th-century Rome was brimming with positive space. Nigh-everything but the narrowest passages are socked in, well-traced shapes. The streets themselves serve as boundaries, circumscribing small, irregular blocks, consistent with a place that grew, filled in, and replaced itself incrementally over centuries.
The Nolli map, of which this detail is one small part, catalogues the twists and turns of eighteenth century Rome.
The question, as it always is in the latter part of these chapters, is how do we translate this property into software? Just like all the other properties, it doubles as a structure-preserving transformation, an operation that you apply to the space, introducing new differentiation without destroying that which is already present. Given a system is in a certain state, and we do positive space to it, what does that look like?
When I say system here, I mean it in the real, old-school, system-theoretic sense: the mess of heterogeneous mutually-interacting parts, every one of which has a role to play.
I was on a call recently where one of the Building Beauty matriculants, ChaCha Heshmat, tongue-in-cheekily suggested that programmers achieve positive space by fiddling with the whitespace in their code so the expressions line up nicely. He was mostly joking but it's an interesting habit to interrogate for a moment. The code runs the same no matter what configuration the whitespace is in, and we have code editing programs that do the indenting for us, yet there is something almost meditative about balancing lines of code in a way that is aesthetically appealing, and yet departs ever so slightly from that which could be expressed (or perhaps more accurately, worth trying to express) by the mechanical rules of an indenting function.
This practice is significant in the sense that nudging the whitespace around the source code absolutely is a structure-preserving transformation that is soothing to carry out, and makes the experience of working with the code easier to deal with.
I'm going to continue to repeat that with the occasional exception, graphical user interfaces can almost certainly use the original fifteen geometric properties from The Nature of Order unchanged, and likewise—if not especially—the eleven colour properties in Book Four. We are once again concerned here with finding analogous properties in the fabric of the software itself, which is a lot harder to see.
I conjecture that the semiotic-topological essence of positive space with regard to software is that the complement of an entity is also an entity. This may lead us places that are taboo, if not outright anathema to current fashions in software development.
Imagine, for example, that you are tasked with writing a function that turns out to be invertible—that is, it's a pure function that has a complement whose input is the output of the function you're tasked to write. In other words, if you chained the two functions together, you would always get the same thing out as you put in—an isomorphism. Now, the popular doctrine of You Ain't Gonna Need It asserts that you must not write the inverse function if all that you know that you need at the moment is the initial one. The arguments against proactively writing the inverse function go something like this:
- Writing code you don't obviously need is always a waste of time. - You will be maintaining that code until the heat death of the universe.
With respect to the first argument, I resoundingly disagree, but I'm going to have to articulate my disagreement carefully. I have witnessed—and been party to—arguments about whether or not to write a particular piece of code that have taken much more time and energy than it would have taken just to write the code, and which were ultimately resolved by pointing out this very fact. One might be inclined to argue here that if you had never written the code in the first place, there wouldn't be anything to argue about, but I submit that not writing an obvious complement to a given piece of code is a landmine for (you or potentially somebody else) having that argument on some later date. On the other hand, if it turns out that in the future you do need that inverse function, it's going to take (you or) whoever is assigned easily an order of magnitude more effort than you would to just jot it down right now. At least put in a stub if you genuinely do not have time for it.
As for the second argument, we have to be careful about what we mean by maintenance, because code in isolation does not decay per se; what happens is that the rest of the world (especially other code) shifts and changes around it. I will also go so far as to venture that only certain species of code need to be “maintained”, where that is defined by reconciling the artifact in question with its environment, and we have to know what species of code we are talking about for that argument to hold.
I won't get into details here but the paper to consult is Programs, Life Cycles, and Laws of Software Evolution (1974) by Meir Lehman and László Bélády. They posit three classes of software program (which we can just think of as being synonymous with “procedure”):
- S-programs that implement a spec,
- P-programs that aggregate and choose between different S-programs as strategies for solving a problem, and
- E-programs that are agglomerations of S-programs and P-programs, situated in an environment.
The gist of the paper, for this context, is that while S-programs and P-programs can be bench-tested (and therefore isolated), E-programs—which we usually see in the wild as the apps and websites we actually use—generally cannot. My argument here is that a pure, invertible function is likely to be an S-program—or maybe a P-program, but never an E-program, and thus is more likely to be a write-once proposition, or at least close to it. For an existence proof, all you need to do is look at the innards of R. A bunch of its core functions are implemented in C, which have actually been translated from FORTRAN routines, that themselves date back to the 1980s. If you look at the revision history, you can see that over the last two decades for which there's a record, somebody pokes at these files not even once a year on average, and when they do, it's not the code they edit, but the comment block at the top of the file. In other words, the prospective maintenance overhead for these things is effectively a rounding error, as the only time you'll ever do a significant overhaul is when there is something internally wrong, or at least glaringly suboptimal, with the code.
So I think invertible functions are one concrete exception to the YAGNI rule. If you have a procedure ƒ for which there exists a (similarly scoped, reasonably polynomial-time-computable) procedure ƒ-1, putting aside the extremely high likelihood that you will eventually need ƒ-1, you're invariably setting up a psychological hazard by leaving it unwritten.
All software can be said to have two structures: one while it is idle (ultimately represented by the source code), and another while it is running. What is of interest to us here, is that the former may not fully describe the latter, and the latter may or may not completely subsume the former. I propose this discrepancy reduces to two modes:
✱ Of course something like a programming language interpreter describes only a sliver of the behaviour that it emits, because most of that information is in the input. So perhaps “description at rest plus input (including all current state information)” is a better delimiter.
Another way to slice this typology is “vestiges and regressions” (with credit to Kartik Agaram), which in one case has to do with the system having described behaviour that used to get used but now doesn't, and in the other, things that used to work as expected but now no longer do. That is, characteristics of the software that change over time—almost like the derivative of the previous dichotomy. These are likewise candidates for eliminating junk non-entities, and thus “non-positive space”.
Analogously:
- The software has capabilities and behaviours that are not documented; - the documentation claims capabilities and/or behaviours that the code doesn't actually do.
Even with all the best tools, this state of affairs still ultimately has to be policed by hand. Perhaps it can be helped somewhat by machine learning, but we actually have a few distinct problems here. Undocumented features can be alerted by automated tests; undocumented behaviour, not as well. Unsupported claims in the documentation are probably untouchable from an automated basis, though I remark they are either well-intentioned (i.e., you're planning to implement them) or they are disingenuous, in which case you have an ethics (and possibly legal) problem that I can't help you with.
This may be job bias, but I see this as a data problem. All software behaviour (except of course for emergent macro-phenomena) is represented in specific lines of code. Prescriptions and proscriptions about specific behaviour in prose can be given an address which can be used to point at the place in the code where that behaviour occurs. Every material claim about behaviour can therefore resolve to wherever that claim is implemented. We don't have the infrastructure for this at the moment, but we could.
It is (still, despite sophisticated languages, frameworks, and tools) possible, if not easy, to create code that never gets executed. Consider:
- Statements that never get reached, - entire procedures/libraries that never get called, - entire features that are surfaced in the interface but nobody ever uses.
These are roughly the same thing happening at different levels of scale. The first is part of the reason why goto
is “considered harmful”: the code is telling you what would happen in a given situation, except in reality it never happens. A compiler can typically identify and warn about this under certain conditions, but nowhere near all of them, so they will have to be identified and pruned (ultimately) by hand. The second situation—that entire procedures go untouched—is kind of an extension of the first, and can also be heavily policed through static analysis: just look for subroutines with no references; this will get most of them. But again, not all of them. Dynamic loading at runtime triggered by input, as well as any eval()
-like scenario, will give you plenty of symbols with nothing explicitly pointing at them, so their inventory will have to be managed some other way.
PHP is particularly troublesome because it was originally intended to be dropped directly into a Web host, meaning that (without deliberate countermeasures) every PHP file has a corresponding URL, and every URL (and then some) potentially has somebody and/or something that accesses it, so you can't be sure—probably more than any other programming language—what can be discarded. This is on top of more conventional mechanisms for referencing code, that we're used to seeing elsewhere. In this way PHP blends this current scenario together with the following one.
Where things get really hairy, though, is the “entire features” part, because you may not know why, or even if people are using them (or not).
- People might not use the feature because they don't know where to look for it. - Or, they might not use the feature because despite it being right under their noses, they don't know what it can do for them. - Or, they might not use the feature because despite knowing where it is and what it can do for them, they don't know how to use it and need to be shown. - Or, there does in fact exist a rabid fanbase for the feature, you just never hear from them.
The hazard in this scenario is the same as the other two: you want to remove code but it turns out somebody is using it. Whereas the other two reduce to mere clutter in the codebase, however, this one has sterner implications, because it impacts relationships with the outside world.
When you expose a piece of code to the interface, you're making an informal pledge of the sort that we routinely make when we offer to meet somebody somewhere at a certain time, or do them some small favour. It isn't a legally-binding contract (unless it is), but it sets an expectation, and if you habitually disappoint people's expectations, they stop trusting you.
Endlessly tinkering with user interfaces is just another way software companies undermine the public's trust. Assuming they are operating in good faith, I understand how this can be the case: unlike symbolic or command-line interfaces that can stretch off to infinity, any one screen on a GUI has not only a finite amount of real estate, but a tightly constrained set of ways it can be laid out. It “makes sense”, by a certain logic, that only the most popular knobs and dials are guaranteed a place.
What does this have to do with a structural property like positive space? Well, if the geometric property conceived by Alexander is about addressing the feelings of the user by ridding their environment of janky overhangs and misshapen non-entities, then rearranging that environment every time users look the other way will not contribute positively to that outcome. There are likewise legitimate reasons for eliminating features altogether, but there are responsible ways of handling that, and you have to recognize that no matter how much you palliate that situation, you will probably screw somebody in the process. This is an open problem.
A feature is a rather slippery concept. To users it represents a capability, while to developers it represents (at least we hope) a discrete scope of work. The problem is that scope of work is arbitrary. Features get clumped together and shorthanded as the vocabulary to delineate them evolves. “E-mail”, for example, could be a feature of an enterprise groupware system, but what does that actually mean? Reading e-mail, sending e-mail, sorting and grouping e-mail? By “e-mail” do you literally mean standard internet e-mail, or some proprietary messaging thing? Sure, all those things and then some. And this is why the “feature” is slippery because in addition to being agglomerated upward, it can also be sliced arbitrarily finely. As a unit of discourse it leaps over what the user is actually trying to do with some concrete proposal for how (maybe) to do it.
Again, there are legitimate reasons for making destructive changes to features (up to and including eliminating them entirely), such as when there is a cleaner, more parsimonious way to organize them, or when the functionality they provide is redundant. I argue here, as I have in the past, that when we speak in terms of features, we speak in terms of the code when it is idle. We lack the language to express the software's behaviour when it is running. We can always describe features in terms of behaviour, but we can't describe behaviour in terms of features. One key behaviour often overlooked is that you can use the damn thing to do the same work from one day to the next, and it hasn't been moved, changed, or deleted.
On the flipside of the positive space equation, we have behaviour exhibited by the (dynamic) system that doesn't show up anywhere in its (static) definition:
- Leaky abstractions, - emergent phenomena, including “undefined behaviour”, - software tectonics (i.e., genuine regressions).
A leaky abstraction is when you have to know something about some underlying code that has been papered over—putatively for your convenience—by a gentler, more familiar representation. The quintessential leaky abstraction is perhaps the desktop metaphor, wherein the user has to learn all the weird, idiosyncratic ways a computer is emphatically not at all like a desktop. Closer to the metal we encounter plenty of situations where there's an inherent semantic gap between the abstracted representation and the underlying behaviour. But a gap isn't necessary: sort
functions, for instance, are perfectly mathematically isomorphic to one another—at least in terms of the output they produce—but have wildly different computational cost profiles. Composite operations that should be algebraically equivalent can be cheap in one configuration and expensive in another—you see this in e.g. SQL all the time.
Indeed, that is what query optimizers are theoretically supposed to do: rearrange expensive configurations of the algebra into cheaper ones. How well they manage to pull that off—or not—is just another leaky abstraction.
We tend to see emergent phenomena particularly in concurrent and distributed systems, when components interact in ways that nobody anticipates. Oscillations, for example, happen when a pile-up of messages causes a pile-up of responses, which causes a pile-up of messages, only to drain out and pile back up again. Cascading failures likewise: consider when an expired certificate causes an error log to fill a hard drive that kills a DNS server that takes down an entire network. Real “for the want of a nail” stuff. “Undefined behaviour”, which I leave in scare-quotes, refers to real-life situations that were left unspecified by the specifiers, and the implementers of those specifications had to do something, they just didn't tell you what. And then we have security breaches, where a bad actor (from the outside or otherwise) has managed to bamboozle the system into doing something its owners did not intend for it to do.
Finally, we have something I'm (for now) calling software tectonics, referring to the slow-moving phenomena out in the environment that are barely perceptible from day to day, but remind you of their existence at random, in the most violent and disruptive possible way. The majority of software relies on a stack of third-party code, from a number of sources, several layers deep. Despite no single package changing in a consequential way very often, put together it's like building a castle on quicksand. Even the occasional fully self-contained software product that is naturally immune to this particular effect faces an inexorable erosion to its relevance as tastes change and new entrants arrive on the scene. A particular software product may itself not decay, but its connection to its surroundings—on all levels—absolutely does.
I often think about ways to track and monitor dependencies. It would be useful to have some kind of mechanism for aggregating notifications around when the inputs to your particular software system change, but doing so would be hard to span across programming languages (for instance). Moreover, you would only likely to be able to aggregate that a given software package was updated, not what about it was updated. There's an open spec called DOAP to help with this kind of thing, but barely anybody uses it.
There is a talk by Clojure's architect Rich Hickey, where he indicts the way software is packaged, stating that any dependent piece of software typically only depends on at most a few small parts of any given upstream dependency. But since you can only address those dependencies at the package level, the downstream dependent has to include the entire package. You also get situations where you have two dependencies that independently of each other, depend on two different versions of the same third dependency, so you have no choice but to package the whole thing twice. He posits that finer-grained addressability in software components, as well as some kind of standardization to notifications of changes in capability and/or behaviour, would enable only the parts that were needed downstream to be included in those packages.
The techniques for solving the problem so described already exist. Order-of-magnitude gains in software packaging and dependency tracking are, from a technical perspective, easily within reach. Fixing it in any meaningful way, however, would be terrifically hard to coordinate. The main countervailing force is cheap-enough storage and bandwidth against a massive indexing (not to mention standardization) effort. It isn't getting done anytime soon.
I feel compelled to make a remark about local symmetries, since complementarity is a sort of symmetry. But the emphasis here is on the complementarity itself rather than the fact that it can be viewed as a sort of symmetry. No doubt we will revisit this matter when we get to that chapter.
There is a reason why the tongue-in-cheek suggestion to fiddle with whitespace resonates. Everybody does it to some extent, because the code “looks nicer” when you do. It doesn't make a lick of difference to the software product—it certainly doesn't make it run faster—and there may be some debate over which configuration looks nicest, but there wouldn't be huge decade-spanning flame wars over it if it didn't matter to people.
I knew a guy who wouldn't accept patches to his repositories until they were formatted to his exact specifications—which happened to be stylistically poles apart from my own (and for that matter, most people). In the long run it didn't help his case for me continuing to supply him with free labour to fix his software. Funnily, I'm not completely unsympathetic to his position, but I think if I was a minority in my opinions about how code ought to be formatted, I'd try to find a way to automate it.
Whitespace (or, I suppose, with current fashion, dark grey space) is the complement of code—at least at the raw lexical (typographical) level. When we look at software at other levels and through other lenses, we can see other complementary pairs, some of which I've already mentioned.
- We already talked about whitespace, - we have pure invertible functions and their inverses, and there are plenty of other structures that aren't exactly that but are analogous, - we have static code versus its dynamic behaviour (and maintaining congruency between those is kind of like self-complementarity),
And then we have a few I haven't discussed yet, at least not in this chapter:
- We're familiar with the duality of a prose spec versus an executable implementation, - however, sound arguments have been made for also having an executable spec and a prose implementation—or at least implementation rationale—complements on top of complements on top of complements.
Finally, no piece of software is truly complete without considering its user. We can bundle all of the coarser-grained design work, identifying and modelling users and their goals (user research, personas), as well as modelling the processes they need to undertake to achieve those goals (scenarios, storyboards, paper prototypes, et cetera), into one big complement to the actual interventions that need to be made to satisfy it. This very much hearkens back to Alexander's PhD dissertation, Notes on the Synthesis of Form, which had context—the part of the system you don't change, complementing form—the part you do. Except I will point out two more items that get taken for granted in commercial software but particularly important to remember for open-source:
- An advertisement that the capability exists, and I mean this word in the loosest possible interpretation—it can manifest as a humble line item in an index (but if you have more than a few of those, you'll want to figure out how to organize them). - A demonstration showing how to use the capability and what results to expect from it. This could be as simple as an inline example.
Some languages have testing frameworks that take their content from examples in inline documentation. In practice it's a little more skewed toward ensuring that the user-facing examples are correct (where “user” is another developer) than about putting the code through the wringer.
This stuff may sound like extra work, but I submit that what makes the work “extra” is not budgeting for it at the outset. It is so easy, especially nowadays, to do small but impressive things in code. The code itself, though—the executable implementation—is like a jewel that sits in a pavé of copious other activity.
What I imagine for the future of development processes is something akin to embryogenesis, that satisfies these myriad complementarities through something that looks like cell division. This is very much in line with what Christopher Alexander came up with himself by the time he wrote The Nature of Order.
We make tools to support our process all the time—after all, that's what compilers and build tools and version control and testing frameworks and bug trackers and continuous integration systems are—why not a tool for software embryogenesis? What would such a system look like?