DEV Community

Cover image for No Human Will Ever Write Code Better Than LLMs? Better at What?
Daniel Nwaneri
Daniel Nwaneri Subscriber

Posted on

No Human Will Ever Write Code Better Than LLMs? Better at What?

Deconstructs the flawed scalar metric of better

A tweet went around saying: "No human will ever write code better than LLMs. Accept this fact, stop pretending, and move on."

Here is my reply.

That's a lot of implicit "better" in that question. LLMs are trained to be plausible, not right. You can get as elegant/idiomatic/smooth-sounding as you want, and still miss the real constraints that matter (existing systems, team patterns, business considerations, failure modes, not-yet-real features).

Senior-level engineering is not just about "writing" code. It's knowing when not to. It's about recognizing real edge cases and not-baked-yet abstractions, knowing when a problem could be more simply solved, or not solved at all. That kind of judgment is hard to quantify, hard to prompt, and often non-obvious in a generated solution.

So yes, LLMs can be a handy tool, but not even close to the level of a real human expert.

The word that bothers me most in that tweet is "better". So I wrote down what it means.

"Better" is a word that we treat like a scalar value. In other words, there is one value that defines better. However, when you ask "better" at what are you measuring it? Lines per minute? Correctness? Maintainability? The courage to not build it? You quickly realize "better" means different things depending on the dimension you are measuring it along.

The productivity metric is lines per minute. It's a nice, simple metric, and it's easy to measure. There's a lot of momentum in this metric because it feels like improvement. However, it is only a measure of typing. It doesn't mean the resulting program is better.

A thousand lines of wrong code is worse than no code at all. A thousand lines of code that crumbles the second you look at it are even worse than that. So, instead we look at correctness. We want our code to be better because it's correct. It has the correct functionality, and it meets the spec. It passes the test. This is an improvement. But, that's not enough.

The real nightmare is that the LLM-generated code works until it doesn't, and it doesn't make sense to you. You'll get reasonable-looking code that somehow finds its way to production, and your payment pipeline will suddenly start silently discarding transactions because the model just decided to add a retry_on_timeout decorator to your transaction handler function that will catch the exception, log a WARNING, and return None. The code down the line will take the None and read it as a zero-dollar successful charge, so now your customers are getting the things they ordered for free.

Gramli tested a case like this in To Retry or Not to Retry. In one scenario, a payment request returned a 503 with a Retry-After header and no idempotency key. Most of the 8 models followed the header and said to retry. Only two said no.

Dex from HumanLayer explains why this happens in his talk "Harness Engineering Is Not Enough and Why Software Factories Fail". Many coding benchmarks give the model a pass or fail reward. The model wins when the tests pass, and nothing penalizes it for bad design. So it learns to add a try/catch or a type cast, because those make the test pass.

We can have a perfectly good piece of software that's unchangeable. This is the opposite of what we want. This is legacy code, and it is bad. We need to be able to make changes without pain.

So we improve it by making it maintainable. Maintainable code is readable, simple code, it's code that doesn't make anyone else want to burn down the office. It doesn't break as time goes on. Maintainable software just works. It meets new requirements and changes without breaking.

The last option is the best of all. It's the metric of wisdom. The "better" that allows us to say "no we shouldn't do that". It's better because it's less, but necessary.

In other words, code is a liability, it is something that we should keep at a minimum. This "better" is the most difficult to practice. It requires saying no to pressure, to budget, to a stakeholder who thinks they have a great idea. It is a less is more metric. However, the compounding interest it pays by not building something keeps growing.

So, "better" is not one thing. It's multiple things. It's a hierarchy, and it changes as we gain experience. We go from lines per minute to correctness to maintainability to not building.

Top comments (21)

Collapse
 
ingosteinke profile image
Ingo Steinke, web developer •

"Better" is a word that we treat like a scalar value.

I remember StackOverflow used to discourage asking for "best practices" and "best" solutions back in it's "better" days, for the exact reasons that you mentioned. Better at what? Better for whom?

Apart from questioning requirements and apparent best practices, and anticipating edge cases as experienced senior developers who take responsibility, we could even still write better (or worse) lines of code. Maintainability, readability, security, standards-compliance, code style etc. Developers have been arguing about ditching code comments in favor of descriptive naming, refraining form over-optimizing compact code-golf idioms in favor of the former goals. Many of those principles have been documented and standardized already. Still, AI keeps mixing idioms, styles and strategies and it keeps stating the obvious redundantly in abundant lines of comments, as if AI was a living in the 1990s and getting paid per line of output.

Thanks for sharing, and especially thanks for mentioning maintainability and questioning assumptions!

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thanks Ingo. Excellent second question, “better for whom?”

I also like the StackOverflow point. Your comment about AI often generating comments that just repeat the code is spot on. It’s just the lines-per-minute metric all over again, as if it gets paid by the line.

Collapse
 
gramli profile image
Daniel Balcarek •

Nice write-up. Whoever said "No human will ever write code better than LLMs" probably doesn't spend much time reviewing AI-generated code or maintaining a real codebase. 😅

I have multiple examples from just the last few weeks where AI made wrong assumptions or introduced unnecessary complexity.

GPT Sol 6.1, last week, was supposed to implement a feature in an existing codebase. The generated code was readable, but it introduced unnecessary database reads and made the whole feature far more complex than it needed to be.

GPT Astra 6.1, in the same week, was asked to refactor some code. The result was actually beautiful, readable code, something I'd expect from a senior engineer. But it also made assumptions and implemented functionality that wasn't needed. And as we know, dead code in a codebase is poison.

I could write about my battles with AI every single week, and I haven't even started talking about code ownership and responsibility. 🤣

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thanks for the feedback, Daniel. I actually think your examples illustrate my point. It’s possible to write code that is perfectly readable but performs additional, unnecessary DB reads. It’s also possible to write code that will someday become a maintenance issue because somebody else will have to figure out what it’s doing and won’t know that it’s safe to remove.

It’s just that the model isn’t aware of these things, so it does the best it can to solve the problem as it sees it. Maybe I should write a post about those too.

Collapse
 
maame-codes profile image
Maame Afua A. P. Fordjour •

Such a nuanced and necessary take! Outputting lines per minute is easy, but knowing what not to build and designing for long-term maintainability is where real software engineering happens. Great posts as always Daniel!

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thank you Maame, you did a great summary of my article. It is easy to measure output but hard to measure what not to include. I am happy that this helped you.

Collapse
 
leob profile image
leob •

"No human will ever write code better than LLMs" - yeah that's a ridiculous statement to make, even when it's just a tweet aimed at generating clicks & views - a statement this absolute (and devoid of context) is meaningless ...

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thanks. This is such an absolute statement that it sounds cool. However, it doesn’t really address the issue I was getting at in the original post... which was, “better at what?”

Collapse
 
leob profile image
leob •

Yeah, define "better" :P

Collapse
 
paullovelace profile image
Paul Lovelace •

The "courage to not build it" rung is the one I keep coming back to — lines per minute is easy to measure, which is exactly why it wins arguments it shouldn't. Your payment retry example is the scary version of that: code that passes its tests and still silently gives stuff away for free. Do you think teams ever actually reward the not-building decision, or does it only ever show up as the absence of a launch?

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

It is true, thanks Paul. If you don’t ship it, you can’t see it.

Most teams measure success by the things they can measure.

It is probably helpful to keep an explicit record of the positive things that resulted from not doing something (saving time, effort, bugs, maintenance, etc.), so that your “no” is recorded. Has anyone done this successfully?

Collapse
 
alexshev profile image
Alex Shev •

"Better" needs a task and a verification budget attached to it. Generating a plausible implementation is different from choosing the right constraint, noticing a missing requirement, or owning the consequences of a deploy. The useful comparison is often human-plus-model versus an explicit baseline, not human versus model in isolation.

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thanks, Alex. I like the term “verification budget.” You’re right that the comparison should be human + model against some reasonable benchmark.

Models can’t choose the appropriate constraint or own the consequences of a deploy (though they can help!)

Collapse
 
marius_panaitescu_416bdd4 profile image
Marius Panaitescu •

Adding my 2 cents here... The sooner we accept that coding as we know it is dead, the better. I just accepted it and moved on, started building stuff, bringing to life ideas I sat on for years.

LLMs are still in their infancy. Two years ago we couldn't even imagine what happens today. What will happen in another 2 years? 5 years? 10?

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thanks for your comment, Marius. I appreciate knowing you are actually putting into practice some of the ideas you’ve held for years.

This is a good thing. As you say, these kinds of models will get better, but the question is what will they get better at? Someone will still be the creator, the owner.

Collapse
 
ascendra_ventures_92c21f2 profile image
Ascendra Ventures •

Breaking "better" into separate axes is the useful move here. Lines per minute is the axis where models clearly win, and it is also the one clients care about least once something breaks in production.

The retry_on_timeout example makes the point better than any argument. A decorator that returns None and gets read as a successful zero dollar charge looks fine in review and passes most tests. Kartik's story of a wrapper that returned the failed attempt is the same bug in different clothes.

Alex Shev's verification budget is a good name for where the cost has moved. Generation got cheap, so the real price of a feature is now the time someone spends proving it does what it claims.

The point about keeping a record of decisions not to build is underrated. On fixed scope work, the features we talked a client out of are often worth more than the ones we shipped. Do you write those down anywhere the next engineer will find them?

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thanks. A concise way to put it is that the cost of a feature is the time to verify it.

In many cases I agree that it’s the features you’ve talked clients out of developing that are most valuable.

The honest answer is: not as a formal process yet, but I think I ought to after your question.

Collapse
 
kartik-nvjk profile image
Kartik N V J K •

That retry_on_timeout decorator returning None is close to a bug I wrote up from my own agent, where a retry wrapper handed back the third failed attempt as an ordinary result and the trace stayed green. Nothing raised, so nothing looked wrong. The pass or fail reward Dex describes explains why the model keeps reaching for it, since a try/catch makes the test pass.

Collapse
 
dannwaneri profile image
Daniel Nwaneri •

Thank you, Kartik. Your example perfectly illustrates the point. The wrapper, when faced with three failures, returns the third one as a successful output, which means the log remains green and nobody will investigate it.

It's much easier to deal with a very loud fail than with a silent incorrect output. Do you have a write-up on this topic? I'd love to read it.

Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

A retry decorator that swallows the exception and returns None is a bug with good manners.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.