Code is Cheap(er)
Carson GrossThere is no getting around the fact that, in the last year, code has gotten much cheaper to create. AI is able to generate reams and reams of code, often of reasonably decent quality, incredibly quickly. There is no point in pretending that this isn’t the case.
At times, when confronted with this admittedly uncomfortable fact, I have seen people I respect say something like “coding was never the problem.”
While I appreciate the sentiment, I don’t completely agree with that: certainly coding was at least part of the problem.
And that part of the problem has shrunk significantly with the advent of effective AI coding tools.
So what does raw coding becoming less important mean for software developers, people who, in the past, prided themselves (and often compared themselves) on their ability to code?
Understanding is Expensive(er)
One thing I see is that it means that understanding code has become more expensive. This is because when reams and reams of code are generated, rather than emerging painfully from a particular programmer’s fingers, there is no understanding of that code.
In as much as understanding that code needs to exist, it has to be done after the code is written, by reading the code.
Note that conventional wisdom is that reading someone else’s code is harder than writing your own code.
Some AI enthusiasts say “Who cares, you don’t understand the output of compilers.”
I think that is a category error for multiple reasons:
- Compilers are deterministic; LLMs are, by design, not
- Compiler workflows retain their original source code; LLM workflows typically do not
- Compiler output is to a narrowly constrained domain (machine code); LLM output is not (generalized software)
I maintain that, in most cases and certainly for mission-critical software, developers still need to understand the underlying code even if it is generated by an LLM.
And if code is generated by an LLM there is a stark danger: the LLM can produce code far faster than you, or anyone else, can understand it. This is why I recommend incremental use of LLMs rather than allowing them to generate massive changelists that neither you, nor anyone else, can understand.
(There are times when this can be appropriate, such as in a mechanical refactor, but it is extremely dangerous when new semantics are being introduced into a code base.)
The Sorcerer’s Apprentice Trap
One movie scene that has been consistently coming back to me as I have watched AI garner more and more attention is The Sorcerer’s Apprentice from Disney’s movie Fantasia.
In this scene the apprentice decides to use magic to assist in the drudgery of cleaning. He enchants a broom which then proceeds to start cleaning things up. Things appear to be going swimmingly for a while, until the broom starts cleaning more and more vigorously, reaching a point where things start going swimmingly literally.
The chaos is resolved when the Sorcerer reappears and asserts control over the situation, glaring at the apprentice for his foolishness.
This seems like an apt metaphor for the AI era: you want to be a sorcerer and not an apprentice.
And a sorcerer has to understand the code.
Complexity: Still Bad
Humans, generally, have a poor grasp of geometric and exponential curves.
(This is why they believe in fairy tales such as compound interest.)
The core danger of code being cheap is complexity, which I assert, without proof, tends to grow at least geometrically and often exponentially with the size of a system.
Before LLMs there were prolific human coders.
Perhaps you have worked with one: they can write a lot of code.
I have seen prolific coders who lack a proper fear of complexity heap more and more code on top of an existing problem until the whole system collapses into an unmodifiable steady state, where any change creates as many bugs as it fixes.
LLMs are incapable of fear of complexity, and are prolific coders.
Seems dangerous to me.
The Subtractive, Constraining Engineer
To address this danger of LLM-generated code, I propose the subtractive, constraining engineer:
This engineer says no, closely examines LLM output, suggests simplifications and generally retains a firm hand when dealing with LLM-generated code.
Rather than priding themselves on the code they create, they pride themselves on the code (and layers) they remove from or prevent from entering systems.
This ethos is more sculptor and less builder.
Where the builder ethos still applies, to an extent, is at the system design level: a good engineer will need to know how to compose components effectively to create systems. However, even here, I think that the subtractive mindset will be useful: removing unnecessary components and system boundaries to simplify system deployment and inter-component interactions, etc.
The subtractive engineer is a different kind of engineer than most coders have been in the past. I will admit that I have always been sympathetic to the subtractive engineer mindset: I don’t mind saying no, I don’t mind polishing existing systems rather than heroic rewrites, etc.
But, admitting my own biases, I believe this approach is a productive way to engage with LLMs that retains the art of computer programming and properly acknowledges a dual reality: code has gotten much cheaper to create and complexity remains our apex predator.