AI Coding assistants provide little value because a programmer's job is to think
Written code is a strikingly poor representation of a running program. Consider this event listener:
const thing = document.querySelector("#thing");
thing.addEventListener("click", () => {
console.log("this is: ", this);
});
Almost none of what we need to reason about this effectively is represented in the text of this little script:
- Nothing indicates how this should be run. Basic programming intuition tells us that we should run something that interprets this, but the process with browsers is far more circuitous than that.
getElementById
,addEventListener
, andconsole.log
are not functions whose definitions are available within the script. Without external context, we don't know what they do.- The script is most likely intended to run in E environments with V versions, which really means ExV environments. Whether or not it will work in every environment is not directly discoverable by any means (it requires human reasoning and judgement).
- The event handler will run in response to a click event which is an inversion of control that means execution doesn't flow from top to bottom
- In the handler, it's completely unclear that
this
is thewindow
, which is also the global object that can be referenced implicitly, so we could end up defining values here that could be naïvely referenced from another scope. - The handler implicitly ignores arguments
- Odds are good that this will be bundled, meaning that without additional effort for source mapping, stack traces on runtime errors will be incorrect.
#thing
references a DOM element that's out of context here. The syntax used refers back to CSS selectors - a concept defined in an entirely different language.- This won't work if it's executed before the DOM has loaded.
These are four lines of code from a script that does next to nothing. Obviously, any real application will be far more complex.
I hear you saying, "Just because JavaScript is trash doesn't mean that every programming language is." And, I'll admit that I chose JavaScript because it's on the more extreme side of written languages underrepresenting their runtime considerations. But, between finite and unreliable resources, concurrency, different environments, integrations, allocation, provisioning, functional requirements, and so on, I think you'll find that the point stands: Written code is not reflective of running code.
Of course, we humans, experienced engineers, are very capable of understanding and reasoning about how the resulting program will run. But we can see that thinking through all of these things that are not represented by the code is critical and complex.
It's easy to write bad code that looks like it might work; writing good code that does work is much more time consuming. So, engineering workflows should be more about thinking and discussing than writing code. And, given the plethora of battle-tested packages and example code out there, and given that most code applications (packages, programs, web apps, etc.) are fairly similar to each other, reasoning about our code application and filling in the gaps for the 10% of our work that's actually different from everything else is really what software engineering is really about.
This, then, is the problem with AI coding: Someone says, "Eureka! I just made a bot that will churn out code chunks that look like they might go together!" But AI doesn't think -- it predicts patterns in language. In other words, it writes bad code that looks like it might work. When we use AI generated code:
- We have to go back and verify what the AI did
- It's nearly always wrong for non-trivial things because it's not an engineer
- Even if we keep some of what the AI did, it's harder to verify what it did
than what we would have done because:
- It can't really explain it
- It has slapped together pieces incoherently
- It'll prefer inlining idioms over building useful abstractions, meaning there's just more to read
The result is that we get a lot of bad code (the easy part) at the expense of understanding and quality (the hard part). So, all in all, it's a pretty shitty tradeoff.
On the other hand, modules offer interfaces, documentation, and well thought out abstractions that get reusable code out of your hair. Example code from project documentation and SO questions provide context on how code samples work. Open source projects provide, at a minimum, relevant context and project documentation. All of these are better alternatives to AI generated code that enable engineers to do the hard things well.
I'm reminded of something Linus Torvalds wrote on the subject of using debuggers in the Linux kernel:
It's partly "source vs binary", but it's more than that. It's not that you have to look at the sources (of course you have to - and any good debugger will make that easy). It's that you have to look at the level above sources. At the meaning of things. Without a debugger, you basically have to go the next step: understand what the program does. Not just that particular line.
And quite frankly, for most of the real problems (as opposed to the stupid bugs - of which there are many, as the latest crap with "truncate()" has shown us) a debugger doesn't much help. And the real problems are what I worry about. The rest is just details. It will get fixed eventually.
Writing code is easy, but programming well is hard, and it's a thinker's game.