Foundations of Software · Brick 3
Foundations 10 min read
Do One Thing Well
The third brick — boxes inside boxes, the six lines that answered ten pages, and why the best systems are made of small things that fit together
Listen · 10 min
Download for laterIn 1986, Jon Bentley, who wrote a much-loved column called Programming Pearls, asked Donald Knuth for a favour. Knuth is one of the giants of computer science, the author of The Art of Computer Programming, and he had recently invented a style he called literate programming: writing code as a beautifully explained essay. Bentley wanted a demonstration.
The task was simple to state. Read a text, and print the k most common words, with how often each appears.
Knuth delivered a gem: several pages of carefully reasoned, beautifully explained code, including a clever data structure built from scratch to count the words efficiently. It was a genuine work of craft.
Bentley then asked another Bell Labs veteran, Doug McIlroy, to review it. McIlroy admired it. And then he wrote his own solution. Here it is, all of it:
tr -cs A-Za-z '\n' |
tr A-Z a-z |
sort |
uniq -c |
sort -rn |
sed 10q
Six lines. No new data structures, no new code at all, really. Just six small programs that already existed, joined together. His verdict on Knuth’s program became famous: it showed how to program intelligibly, but not wisely.
To be fair to Knuth, and many have since pointed this out, he’d been asked to demonstrate a style of writing, not to win a contest. But the contrast has stayed in programmers’ minds for forty years, because it shows something deep. This brick is about that something.
The six lines, slowly
If you’ve read Remember, Change, Send, you know every system is made of remembering, changing and sending. McIlroy’s six lines are six little changers, each doing one thing, each sending its result to the next through a pipe (the | symbol).
-
Split into words. Everything that isn’t a letter becomes a line break (
tr -cs A-Za-z '\n'), so there’s one word per line. -
Lowercase. The and the become the same word (
tr A-Z a-z). -
Sort. Alphabetical order puts identical words next to each other (
sort). -
Count. Each run of identical lines collapses into one, with a count in front (
uniq -c). -
Rank. Sort again, by that number, biggest first (
sort -rn). -
Take the top. Print the first ten lines, then stop (
sed 10q).
None of these tools was written for counting words. sort doesn’t know what a word is. uniq has never heard of frequency tables. Each one does a small, general job well, and they all speak the same simple language: lines of text. That shared language is what lets them snap together like bricks.
I ran McIlroy’s six lines over this journal (every article that existed when I sat down to write this one: about 28,500 words). The top of the list was what you’d expect from any English text: the, a, and, of, to. The glue words.
Then I added one more small tool to the pipe, grep -E '^.{6,}$', which lets through only words of six letters or more. The question changed completely. The two most common long words in the whole journal turned out to be things (88 times) and people (87).
Things and people. I couldn’t have summarised it better if I’d tried. And it took one more line, not one more program.
Where it came from
The idea goes back to the early days of Unix at Bell Labs. McIlroy had been arguing for years that programs should connect together like a garden hose: screw one onto another and let the data flow. In 1973 Ken Thompson added pipes to Unix, in what McIlroy later described as one feverish night, and the people around them quickly began discovering what they could build by joining small programs together.
In 1978, McIlroy wrote down the thinking behind it. Peter Salus later boiled it down to three lines that have become the most quoted summary of what’s called the Unix philosophy:
Write programs that do one thing and do it well.
Write programs to work together.
Write programs to handle text streams, because that is a universal interface.
That last line deserves a second look. A universal interface. Text is the one thing every program can read and write, so programs written decades apart, by people who never met, can still work together. It’s why a pipeline from 1986 still runs perfectly on the laptop you’re reading this on.
Boxes inside boxes
In the first brick, I promised we’d come back to an idea: you can use a thing without knowing what’s inside it. You don’t need to understand a kettle’s heating element to make tea. This is called abstraction, and it’s how anyone builds something too big to hold in one head.
An abstraction is a box with two sides:
- an outside, or interface: the small set of things you can ask the box to do, and what it promises in return;
- an inside, or implementation: everything it does to keep that promise, which you’re allowed not to know.
sort is a perfect box. Its outside is tiny: give it lines, get them back in order. Its inside is a carefully tuned algorithm that can sort files far larger than memory, juggling temporary files on disk. You will never need to know. That’s the gift.
John Ousterhout, a Stanford professor who has spent decades building and teaching systems, calls the good kind a deep module: a small, simple interface hiding a large amount of useful work. His favourite example is how Unix handles files. A handful of calls (open, read, write, seek, close) hide an enormous amount of machinery: disks, caches, permissions, directories, devices. The opposite, a shallow module, has a big complicated interface and does little behind it. All door, no room.
Where should you draw the walls of a box? In 1972, David Parnas wrote one of the most influential papers in software, and his answer still holds. Don’t split a program by the order of its steps. Split it around the decisions most likely to change, and hide each one inside its own box. Then, when the decision changes, only one box has to change with it. Each module keeps a secret.
The price of a box
There’s a catch, and you met it already in Mechanical Sympathy.
In 2002, Joel Spolsky wrote down what he called the Law of Leaky Abstractions: all non-trivial abstractions, to some degree, are leaky. Memory looks like a simple street of boxes, until the cache makes some boxes a thousand times faster than others. The network looks like a reliable pipe, until a cable is cut and the reliable pipe simply stops. A box can hide its inside most of the time. Never all of the time.
And a second, sneakier law comes from Google, named after the engineer Hyrum Wright: with enough users, every observable behaviour of your system will be depended on by somebody. Not just what you promised. Everything people can see: the order results come back in, how long something takes, the exact wording of an error. Every visible detail quietly becomes part of the interface.
Put those two laws together and you get the practical lesson. Keep the outside of every box as small as you can. The less there is to see, the less there is to leak, the less there is for others to depend on, and the freer you are to improve the inside. It’s economy of mechanism from The Fewest Doors, wearing different clothes.
Grow it, don’t build it
One more law, from a little 1975 book called Systemantics by John Gall, a doctor who wrote about why systems fail:
A complex system that works is invariably found to have evolved from a simple system that worked.
He added the other half: a complex system designed from scratch never works, and can’t be patched up to make it work.
Unix itself grew this way: small tools, each working, joined together into something no one had planned. McIlroy’s pipeline grew this way too: each stage can be tested on its own, and the whole was built by adding one working piece at a time. So does every good codebase you’ll ever work in.
Your reading room is a pipeline
Here’s the aha that made me smile while writing this.
The app you’re reading this in follows the same philosophy, almost by accident. Every article is a plain text file in a folder: a universal interface. Any tool on earth can read it, which is exactly how I was able to run McIlroy’s six lines over the whole journal.
And the listening feature is a pipeline of small tools that each do one thing:
article.md → speech text → say → afconvert → .m4a
One small Elixir module turns Markdown into speakable text. The Mac’s say turns text into sound. afconvert packs the sound into a small file. None of them knows about the others. Swap say for another voice tomorrow and nothing else needs to change. That’s Parnas’s secret-keeping, McIlroy’s pipe and Gall’s growth, quietly at work in a reading room.
Beyond software
It’s hard to spend time with this idea and not notice it everywhere.
A good kitchen has a few sharp knives, not a drawer of single-use gadgets. A good toolbox holds simple tools that each do one job well and combine for a thousand others. A good day might be built the same way: a handful of small, well-made habits — a walk, a meal cooked from scratch, an hour of focused work, a phone that sleeps outside the bedroom — each simple on its own, composing into a life far richer than any single grand plan.
And when something goes wrong, small pieces are easy to inspect and easy to fix. You can test them one at a time. You can replace one without tearing down the rest.
Do one thing well. Make things that fit together. Grow from something simple that works.
What this brick adds to the house
- Small tools, composed. Power comes from the relationships between simple pieces, not from one big piece doing everything.
- A universal interface (in Unix, lines of text) lets things built by different people at different times work together.
- Abstraction is a box with a small outside and a large inside. Deep boxes are good; shallow ones are mostly door.
- Draw the walls around decisions likely to change, so each box keeps a secret.
- Every abstraction leaks, and every visible detail gets depended on, so keep interfaces small.
- Grow complex systems from simple ones that work.
Where the next bricks go
- The post office: how a message actually crosses the world, one small hop at a time — another pipeline, on a planetary scale.
- The careful librarian: how databases hide enormous machinery behind a few simple questions.
- Letters between villages: what happens to all our tidy boxes when the pipes between them can fail.
Sources
- Hillel Wayne, Donald Knuth was framed, on the 1986 Programming Pearls exchange
- The pipeline and what each stage does, explained at evalapply.org
- Unix philosophy and Pipeline (Unix), Wikipedia
- David Parnas, On the Criteria To Be Used in Decomposing Systems into Modules (1972), discussed by Adrian Colyer
- John Ousterhout, A Philosophy of Software Design (2018)
- Joel Spolsky, The Law of Leaky Abstractions (2002), summarised at Wikipedia
- Hyrum’s Law
- John Gall, Systemantics (1975), via Wikipedia