Wander mode

A constellation of ideas

Programming as Discovery

I want to talk about two views on the purpose of programming, which I’ll call programming-as-discovery and programming-as-output. These are not exhaustive views but they are two views that have been on my mind a lot lately, especially with the rise of LLM-assisted and vibe-coding.

programming-as-discovery is the view that the purpose of programming is not necessarily to produce code as an output. Rather, the desired end of programming is the building of understanding, a shared language, a coherent mental model, intuition about a system (which leads to ability to debug, insight into performance, etc), hypothesis formation, new techniques and insights to doing things better, amongst many other similar ends. And yes, code often is a vital end in the task of the programmer. Code is the proof to our thinking, validates our understanding, and provides many goods to real users (if done well). I take this to be what engineers like Ryan Fleury, Casey Muratori, and others think, along with what I take to be the real meaning of the original agile manifesto.

programming-as-output is the view that the purpose of programming is that of producing code-artefacts to, ideally, satisfy some user need. Although more commonly I have seen the code-artefact itself appear to be the end goal. I wouldn’t necessarily say there are any specific proponents of this view, but I see it becoming a more common implicit view recently due to the advances in LLM models and harnesses, which have allowed engineers to jump straight to the sole end goal of producing code.

I don’t want to say that either view is correct. They are both useful views depending on what our purposes are. Practicing programming-as-output can be very useful when used as part of a hypothesis-formation/testing flow; for example when creating throw-away prototype code, we don’t care about building understanding during this specific activity of programming, we just want to test a theory and then throw it away. Another example can be found in the recent Bun rewrite into Rust. It was completely an LLM-generated rewrite into Rust from Zig. Based on what I’ve read I think it’s fair to say that during the course of this rewrite there was minimal development of understanding, shared language, clear intuition and mental models, etc. The end sole end goal was simply to replace Zig with Rust for an already well-understood domain. And it must be said that LLMs are remarkably good at porting from one language to another.

But I do worry that we, as an industry, are forgetting the value in programming-as-discovery, and I am worried that even if we proclaim to still care about those particular ends associated with programming-as-discovery we will in practice jettison them in the name of “productivity”.

So I do want to argue that the increasing use of and reliance on LLMs in coding makes programming-as-discovery very hard to do. I think LLMs, especially as used indiscriminately today, are incredibly harmful to programming-as-discovery. In my experience, they lead to railroaded solutions which are not deeply thought through, a lack of shared understanding, a lack of a coherent and complete mental model, a loss of familarity with the code and domain, and a general de-skilling of engineers. I have had conversations with very talented engineers who now feel like they are becoming much less intelligent and able to think independently. (There is certainly an argument to be made around human-flourishing and excellence, but that’s not in scope here.)

My worry is amplified by some recently examples; for example, a SpaceX/Cursor employee wrote about how she is shipping 1,500 PRs a month. The API token cost of the LLM usage was estimated to be several million dollars a month. I can’t imagine this is sustainable economically, but regardless I cannot believe this is remotely sustainable from a product or user-perspective. And let’s not forget, as Fleury wrote here, programmers are also users. And we see this drive more and more with the so-called “software factories”. What more vivid a picture can we see with the word “factory”? What is a factory, after all, other than something which produces standardised items at industrial scale? There’s no room for programming-as-discovery here, the factory must grow and must churn out more and more output. We just want code, we don’t need to understand, to think, to model, to share, etc. The users will accept change at a million miles an hour and they will like it.

And this leads us to the next question when we consider programming-as-output running in an LLM loop. How fast is too fast? If the end goal is just to ship more and more code, then surely there isn’t a “too fast”. But if we take the end goal of programming to be the multi-faceted programming-as-discovery, then “too fast” is just when we begin to lose our ability to meet the ends of programming-as-discovery. And I see this to be a common occurance in engineering discourse; engineers moving too fast they no longer understand the systems they are working on, are able to build and hold a mental model of it, and are able to improve and refine it (without blindly trusting the output of an LLM).

Which brings me back to the agile manifesto. Why does it care about “responding to change over following a plan”? Well, the answer is easy to see if we think of programming through the lens of programming-as-discovery. We should prioritise change because discovery will lead us to many changes as our understanding grows, shifts, and changes. Of course, LLMs can make refactoring extremely easy to do, but the challenge is that knowing what and how to refactor becomes very hard when we lose deep understanding of the code.

And so as far as I can see this just cannot be a good thing to lose. We mustn’t mistake one end of programming - namely, code - to be the whole end of it. Sometimes this is fine, but sometimes it’s not, and I do worry the software industry is shifting too much to the side that says it’s always fine.