Meditation on Software 1

[October 31, 2021]

A rant, about how something is very wrong with how we write code.


1

For instance:

It takes an unreasonable amount of effort to do anything with software, and we still don’t do anything particularly well. How many millions of person-hours are spent every year on fixing bugs, and understanding confusing code?

And every significantly-sized project has enough complexity to require a team of specialists to support it, and still there is no useful overlap with the complexity of any other project.

And every company and organization is duplicating each other’s work because almost none of the solutions can be shared.

And every advance in hardware efficiency is canceled out by inefficiencies in software, so that everything is barely performant enough, all the time, and gobbles up any energy we have available to give it.

If you work in the industry this should all be obvious. If you write code and don’t feel like most of your time is wasted dealing with problems that shouldn’t exist in the first place… I don’t know, wake up?

It is all working – kinda – in that humanity is churning out software and solving problems and making money. But it can’t be ideal. This human race of ours is spending too much of its human effort to make software that doesn’t work very well and doesn’t do very much, slowly.

I like to fantasize about how to do better.


2

Really it is that there is something medieval about how we write code. We are still in the software dark ages, like mathematics before algebra and calculus were discovered. Or perhaps thought, before philosophy. The way software is written in five hundred years – if we haven’t run out of breathable air or microchips or whatever – will, I expect, be mostly unrecognizable compared to how it’s done today, and at best we are, as a species, 20% of the way along that path. (My guess is that we’re at like 15% in most places and then React.js pushes the number to 20%. More on that another day.)

Here’s a test for assessing how good humanity is at writing software:

Suppose that humanity builds out a spaceship of 1000 colonists is traveling to another star system, many light-years away. And suppose this ship has to be totally self-sufficient, including having the ability to support all of its software, fixing bugs and improvising solutions to whatever comes up on the journey, and likely building out whatever is needed once they get there. Can the colonists confidently expect to be able to handle whatever software challenges comes up?

The answer is: hah! definitely ‘no’. They will all die. RIP.

The answer needs to be ‘yes’ if we are to colonize other star systems. I don’t want to ship off to another star system only to die partway of an unfixable bug in the life-support system. There is no way you can fill out the roster of the ship with expert software engineers, and there is no way a roster of non-experts, even if they are geniuses in other fields, can be expected to understand even one part of the ship end-to-end.

So we have work to do. It’s probably possible, but it will take some serious advances to get there.


3

An analogy can be made to mechanical engineering. I don’t really know how my (gas-powered) car works. But if I open the hood and look at the engine, I feel like I have at least a hope of figuring it out. Apart from… the electronics… I can clearly tell which parts interact with which other parts, and approximately what they do to each other.

Presumably if I take those parts apart I can tell how they work, approximately, internally, although I may not be able to put them back together again, or machine new parts of the same quality, without a lot of specialization. But the fact I can make progress at all is valuable. If I took a long road trip away from civilization with just a box of tools and spare parts I have at least a hope of handling whatever comes up.

The difference, I think, is that physical machines are constrained by fundamental requirements of causality. For a widget to affect a gizmo, it has to, like, touch it, and there has to be some motive force between the two, which I can view and manipulate myself. Its physical interaction affords it a property of scrutability that allows me to make progress on understanding it. And if you take the widget apart, its internal components have the same property. Of course this falls apart when chemistry gets involved; you actually do need some specialized knowledge to make sense of, say, the actual combustion process. But for mechanical systems, it’s a start.

Software today has no such property. It works exactly how it works, and good luck figuring it out from the outside. It is stable and functional like a spinning top balanced on the tip of a needle.

The best Scrutability I know of is web browser DevTools, which let you view any website and see how it’s organized and styled… but it barely counts. I hope that someday, figuring out how any software works is as natural as taking apart and tinkering with an old car engine.

As should be clear from this comparison, it’s not enough that software is open-source (although that’s a start). It must also be conceptualized and built in a way that makes the causation Scrutable. It needs to be split into Scrutable modules that ‘push’ and ‘pull’ on each other in a way that we can follow. Most importantly, it needs to be constructed in such a way that allows for the digital equivalent of ‘opening up the hood and looking inside’, and we need to have the tools at hand to do so.


4

I don’t think any of what I’m looking for exists today, outside of, perhaps, one-off proprietary solutions. But if I had to throw out some ideas, here’s where I think progress is happening:

The most Scrutable system I know of is viewing a website in Chrome Devtools, except for the fact that it doesn’t let you write code (or really search for it, or really modify anything in a way that doesn’t get reversed the next time a callback is triggered). But it does something the rest of them don’t, which is let you record every piece of code that’s run on a page and inspect it to see what happened. Nevermind that this process is janky and error-prone, and obfuscated, and the backend is invisible, and recording execution via the Performance tab is a mindfuck– at least it exists. There is no future in having to add print() statements to find out what your code did, or in reading imperative code to figure out what state a UI ended up in. You have to be able to look at everything, as it exists.

The most Scrutable way of writing code that I know of is React. The declarative model is the right way to reason about UI code. React Devtools are reasonably good at looking at something while it’s running, and, in some cases modifying it. Hooks are better than any other way I’ve ever seen to reason about side effects, although in every case the whole philosophy is hamstrung by being implemented in Javascript and having to transpile to the DOM. And the problem of data processing and externalities is, as far as I know, still an unsolved problem, despite the efforts of the Redux/Flux/state managment ecosystem.

(Perhaps in the not-too-distant future there is a version of the React whose shadow DOM is the DOM, and which runs in a language that doesn’t require dependency arrays, and which has first-class types built-in instead of shimmed on top, and in which you can’t make the mistake of forgetting to bind a function to the appropriate this, and whose debugger lets you follow asynchronous effects that are scheduled on later render frames. Wouldn’t that be nice!)

At least when it comes to UI, there is a future where React Devtools, Figma, and your IDE are the same piece of software. And I think that in this world, user-facing code no longer has anything like unit tests, because it’s a waste of time to meticulously test code when you can look at it and observe it’s correct.

The best shell I know of is, I guess, Python. Bash and its descendants are a disaster and the world would be better off if they were entirely replaced. In the future there is no way that we’re going to be working in languages that use $PATH variables, that pipe unformatted string data through bizarrely-named commands inflected by obscure flags, or that require in-band signalling with strings like \u001b[31m to colorize text. I mean, my god. (Once upon a time I had high hopes for TermKit but it never really got off the ground.)

I am not sure what the future of type systems is, but I know three things about it: 1. Constructing natural numbers out of successor functions is an irrelevant gimmick. 2. There will no concept of ‘undefined behavior’ that survives the typechecker, because that’s insane. 3. Refinement types are going to happen at some point. It will be considered antiquated to use a language that can’t specify the type of ‘integers greater than 5” in some ergonomic way.

Finally, I know this: most of the code written today isn’t any good, compared to what will be possible in the future. It’s not possible in today’s ecosystems to write something scalable, maintainable, and resilient to errors. It’s up to the frameworks and paradigms to develop the art of programming to the point where it’s actually an efficient and accessible craft instead of a massive timesink for the whole human race.