On the cover of Hofstadter’s famous Godel, Escher, and Bach, there’s a photo of an artifact he made, called a "trip-let". The trip-let, when lit from three different angles, produces shadows that spell out "G", "E", and "B".
Let’s talk about software design.
Before we dig in: I love to think & talk about geekery, but it’s comfort food, not my most important story. Take a break, enjoy this thread, but please stay in the larger game with me, which isn’t changing code, it’s changing the world.
Black Lives Matter.
I was recently asked to prepare some content around the topic of software design. There are a lot of ideas out there about "good" software design and "bad", and about rules or the lack thereof. It’s a rich topic, a lifetime game.
I’ve spent four decades learning, and I still think of myself as merely "pretty good" at software design. There’s a difference between being pretty good at something and knowing how to talk and teach about it, tho.
I was thinking of lots of little videos, about composition, naming, patterns, immutability, avoiding implementation inheritance, all the little things. To do it well, of course, I’d need to hang all those little things off a frame, a big thing.
Uhhh. Whoops.
Look at the image again. It’s interesting, the lighting is such that it doesn’t even look real, but it is. You can make trip-lets from wood, or with a 3d printer. The interwebs will tell you how, in detail. There are apps for it. 🙂
A program is like a trip-let.
It is a single artifact casting three shadows, each different, and each important: behavioral, interpretive, and reactive. Software design is shaping that artifact to create various effects in one, some, or all of those shadows.
We’ll look at each shadow separately, then zoom back out to the larger metaphor after. We can characterize a shadow by the question at its center, its notion of felicity, and the images and tools it relies on most heavily.
The behavioral shadow of a program asks the question: "Does the program do what we want?"
Programs exist to be run, and the behavioral shadow is about what they do while running. Felicity, in the behavioral shadow, is about the satisfaction of the users.
Here, we concern ourselves with the detailed state of the program and how that state causes — or fails to cause — the behavior we wanted when we created the artifact. Of course, it’s not usually a yes/no question in real life, but a point on a continuum.
The classic image of the behavioral shadow is a sequence or object diagram, with arrows from object to object indicating ordering, messages, and replies. Sequences of wireframes serve this purpose, too, and of course, user stories and requirement documents.
Given two designs, one that works and one that doesn’t (ceteris paribus), the behavioral shadow’s ideal will always take the one that works, or more usually, the one that works "better".
The intepretive shadow asks the question, "Do we understand the program?"
Humans make programs, and felicity in the interpretive shadow is about whether and how the human makers can grasp the meaning of the text that casts it.
This is the domain where we talk about naming, and chunking, about local computational complexity, about Dijkstra’s famous "GOTO" letter, Knuth’s literate programming, signal/noise ratios, function length, interface width, and even, yes, bracket placement. 🙂
The classic image of the interpretive shadow is a class diagram, showing the parts, their ISA and HASA relationships. A middle ground tool between behavioral and interpretive is using CRC cards. Architectural documents are often interpretive in intent.
One way to think of interpretation is to think of "sense-making". Given two designs, one that is easy to make sense of and one that is hard to make sense of (ceteris paribus), the interpretive shadow’s ideal will always take the one that makes the most sense to us.
The reactive shadow asks the question, "Can we change the program?"
The ecology of software is rarely fixed or final, nowadays, but undergoes nearly constant change. Felicity in the reactive shadow is about how hard or easy it is for us to respond to those changes.
In the reactive shadow, we concern ourselves with ideas like dependency, coupling, isolation, and sequencing. We seek a ranking of the abstractions in our code, from deepest & most-stable to shallowest & least-stable, based on our domain & technology.
The ways we envision the reactive shadow are the least well-known and coherent. Though class diagrams capture some of it, so do backlogs and to-do lists. The tools we use here are nearly all just "in our minds". It’s a particularly fruitful area of study just now.
(My own work on MMMSS, see the site for the ongoing series, comes from my realization that the ideas & techniques of the reactive shadow apply throughout development, not only after some initial release. Give it a look, if you like.)
The reactive shadow’s ideal of felicity says, given two designs, one of which will flex under change and one of which won’t, we’ll take the design that flexes.
These are just the lightest sketches of these three shadows, each of them can and does call for reams of material, not just a few sentences.
But armed even with these short, loose, descriptions, let’s step back again to the larger features of the metaphor.
-
"Shadow-ness" is important. These shadows are always and only the indirect consequence of the particular shape of a particular artifact, the text, code. We can visualize them, think in their terms, and speak of them, but we can’t touch them, we can only touch the artifact.
-
The questions each time use the word "we", and for good reason. Modern software design always takes place in a multi-human collaborative world. This collaboration forms a central facet of all three shadows.
-
Are there more or other shadows, or just three? Beats me. Probably? These are the three that seem to loom largest when I consider design, and are not intended to limit or finalize the conversation. I’m sharing work-in-progress here, not pronouncing final conclusions.
-
The shadows are not "dimensions", in the ordinary usage of that term, outside of advanced mathematics. They are not orthogonal, for one thing. And for another, each, itself, includes multiple elements, not a single measurable value.
-
But these shadows are also not "layers", one being more urgent than another, or preceding one another. They are reasonably separable, but not absolutely so. They intertwingle.
Software design is shaping an artifact — the code — to fit the contextualized needs — the environment — of its makers — the team — so that three shadows cast by that code — behavioral, interpretive, and reactive — form a felicitous balance.
Though each shadow has its own felicity, and tho those ideals can line up, they don’t always. We’ve all used performance optimizations, for instance, heightening our behaviorial shadow and shorting our interpretive and reactive shadows.
The artifact of the software trip-let is a text. Every addition or alteration we make to that text has impact on one, some, or all of its shadows. Design, then, is altering the text so its shadows fall in a certain way: We seek to get more from the artifact than we put into it.
It is a vast subject, software design.
Exploring its corners has been vocation and avocation for me for a very long time, and it has been an immensely rewarding endeavor, providing great joy to me over the years.
Look at the artifact!
Look at the shadows!
You feel that? I do.
Does the GeePaw Blogcast add value?
If so, consider a monthly donation to help keep the content flowing. You can also subscribe for free to get weekly posts sent straight to your inbox. And to get more involved in the conversation, jump into the Camerata and start talking to other like-minded Change-Harvesters today.