But, with AI, the cost of code has gone down more than it already has. Well, cheap things are easy to throw away. So you prototype, prototype, prototype, and close the loop as much as possible with the customer. True agile development, not big A Agile.
The problem is this requires alignment from management, and we're just not seeing it at many company. They can't grasp that things have changed, and that throwing away code is free. They don't trust engineers to close that gap, so customers and stakeholders are still waaaaaay over there and we're delivering features they don't want.
Until managers give up on Agile, we'll never get time to actually write specs
To be honest, the models are getting so good that they do most of this unprompted now.
At this point if you can’t get the agent to write good code then either I) you are in a very specific niche (like Karpathy trying to write NanoGPT) that is extremely out-of-distribution, or II) skill issue, you need to learn how to prompt better.
It’s fine to have a skill gap! Just don’t delude yourself that the tools are bad and everyone claiming they are good is wrong.
So despite its importance much of it is actually pretty in-distribution.
Well put. This insight is worth repeating in every discussion on the subject, from software engineering to mathematics.
The problem is that understanding is not the product being sold. The business model is for everyone to become consumers of what the magical genie generates, where the "understanding" is kept on the side of the model providers. This ensures a future generation of consumers dependent on someone else to provide the understanding.
Otherwise, you can create your own answers based on actually understanding the code, theorem, proofs, etc. Smart consumers of LLMs will use them to increase their own knowledge and understanding, so that the service is augmenting their intelligence, not replacing it.
But to be fair human code reviews have the same problem. It's like reviewers feel they have not done their job if they don't find something wrong.
I do the same for LLM code review comments: some changes are out of scope or could be moved to a separate PR; some edge cases don't happen in practice and should just fail noisily instead of writing more code to maintain. When these are the only issues it's raising, then I know it's done.
I wonder if the same trick works with AI?
Perhaps someone more knowledgable could jump in here to clarify?
To be clear I mostly only use Opus and Gemini Flash but this might work for others too.
Disabling your C compiler warnings works too. You get to ship then leave work early!
If LLMs are, as you stated, really good at catching potential issues, then they are, almost by definition, really good at producing code without potential issues, if guided correctly: all they need to do is inspect and iterate, until they do not find any more potential issue in the code they produced.
But that doesn't mean they're useless either. I use them all the time for review as you mentioned or to knock out one-off scripts that don't go anywhere near source control. There's just no world where I don't need to understand every line of code that I'm responsible for getting into our project.
The key I’ve found is human peer review. The reviewer jumps on a live call with the developer, pulls up the PR with transcription on, and asks questions. At the end of the call, the transcript passes back into the coding agent and the PR is polished up, becoming more self-documenting, and the humans are left with some degree of common understanding of what’s going on.
I’ve been operating my team of ~15 this way for 9mo to great effect… there is simply no going back to the stone ages.
Can I watch/observe one of your review sessions?
Every few weeks, I hear the beginnings of a great approach towards working with LLMs but I rarely see it in practice.
If you're open to this, remote or in person, ping my username at gmail.
This was my stance a couple of years ago, but now I've given it up.
It turns out writing actually was the bottleneck. You can understand perfectly well what you want, but writing it is long and tedious to the point where you find excuses not to do it. Particularly with version 2, the step where you have an OK system and you want to improve it. Quite a lot of changing the code is just useless busywork: re-wiring old functions, moving imports around, searching for locations that benefit from extracting a common piece of code. And each time you do one of those, there's a decent chance you did something even more trivial like forgetting a semicolon or calling the wrong function.
Now that I have an LLM helping me, I can see why. The critical decision is a terse declarative like "we need to have several TCP connections instead of one, and just use the sequence number to arbitrate". A human junior programmer could perfectly well understand what this meant, but he would have to go through all of the above to get to the final product. Now, I can just tell the LLM and I will get what I want, even with the things I didn't explicitly state, without spending attention.
This means I can use my attention on the things that matter. So instead of spending today thinking about how to arbitrate between the TCP connections and tomorrow thinking about pre-calculating my outgoing orders, I can just do both today. I don't waste the good waking hours chasing minor bugs, I just think about the large structure.
I get the feeling the best programmers of years past were actually masters of the little things, which led them to be able to look at the big things. Essentially it was cheaper for them to get to the top of the mountain, where you can see the landscape. Kinda like how the kid who was good at mental arithmetic in primary school was also good at calculus at the end of high school: if you don't have to concentrate on the little things, you have time for the big things.
how did you decide to pick the most trivial kind regression for this example? do you compile your code before checking it in?
> A human junior programmer could perfectly well understand what this meant, but he would have to go through all of the above to get to the final product. Now, I can just tell the LLM and I will get what I want, even with the things I didn't explicitly state, without spending attention.
the main efficiency you have described here is offloading the verification of a change onto the LLM. that is the bottleneck. readers can decide whether a non-deterministic statistical model is a good tool for this job
> I get the feeling the best programmers of years past were actually masters of the little things, which led them to be able to look at the big things
the best programmers understand that their job is to automate workflows, and that includes their own. if you're worried about missing a semicolon, I'm sorry to say that's a skill issue
Why would this be a regression? You might just be writing a new line of code.
> do you compile your code before checking it in?
Well obviously. That is generally how you discover that a semicolon is missing.
> the main efficiency you have described here is offloading the verification of a change onto the LLM. that is the bottleneck. readers can decide whether a non-deterministic statistical model is a good tool for this job
No, it's the time between you deciding something needs to be done, and it being done, that is the bottleneck. You cannot avoid trying to compile the code and testing it. Now you can get to that test without paying attention, which is time you can use productively.
> readers can decide whether a non-deterministic statistical model is a good tool for this job
Somehow, the non-deterministic model has built me the deterministic code that I want, very fast, pretty much all the time. A year ago it would get stuck. Now it doesn't, for me at least, and for competent programmers that I know.
> the best programmers understand that their job is to automate workflows, and that includes their own. if you're worried about missing a semicolon, I'm sorry to say that's a skill issue
Well yeah, and I've automated my workflows completely. I don't have the problems I used to have. If you haven't caught on to the new way of working, well, that's a skill issue...
I still find the models get stuck or go on _massive_ side quests. Just today, I asked claude to write a hello world C++ program using import std; I interrupted it when It decided I needed a new toolchain installed, and started checking for docker installations. This is super basic stuff, it hadn't even generated a plan, it just started searching for LLVM versions rather than running clang --version.
> If you haven't caught on to the new way of working, well, that's a skill issue...
Honestly, it feels like the emperor has no clothes on this topic, and the crowd defending LLMs to death are way too quick to call it a skill issue.
I use LLMs, but they're just a tool in the workflow, and I make sure to review the output. they might remember semicolons but they make much more pernicious mistakes that are harder to detect
If other people are dissatisfied with LLM output quality while it seems to work fine for you, you might want to consider that the quality of code you produce is closer to the quality of code the LLM produces than what those other people are producing.
What you posted there, for example, about most of changing code being busy work is a pretty big red flag for a codebase. One of those "large structure" things that you're supposed to be paying attention to is the architecture of the code. There's always the chance that some change you need to do goes against the grain of the solution you architected, and you need to make changes all across your codebase to fit it in, but in general the point of modularity and good architecture is that when you make a change you just have to make that one change, ideally just changing the logic of the one responsible function with only minor changes required anywhere else in the codebase. If you're consistently having to hunt throughout the code for related functions that you need to rewire that's a sign that your architecture does not fit with the direction your codebase is evolving, or alternatively that you don't have much of an architecture to begin with and your code is highly interconnected.
Actually one habit you mention at the end of that quote can worsen this issue: "searching for locations that benefit from extracting a common piece of code". Tautologically this is a good thing as you define it as only working on locations that will benefit, but given the frequent need for rewiring of functions I would hazard to guess that you've "deduplicated" code a bit overzealously. Just because two functions share some common code does not necessarily mean it is appropriate to pull that out into a function. Deduplicating is good if conceptually the code is a single thing that you would always want to keep in sync, as it means that when you need to make a change to it you don't have to hunt down all the places it's used. On the other hand, if you find yourself frequently needing to delve in to these functions to rework them because you need to make a change to how it's used by just one caller, your "deduplication" has added to your workload, and probably created some overcomplicated code in the function that is in reality handling multiple distinct needs.
I hope this doesn't come across as too condescending, and if I've just wasted your time explaining principles you already understand I apologize. I don't know you or the code you're working on so I can't exactly confidently judge your work solely on a few paragraphs. It's just that your mention of how your experience of coding has been different from what others have described, and specifically that, for you, writing has been the bottleneck rather than understanding, combined with the specific issues you describe facing, imply to me that you may not realize that the approach you are taking to producing code yourself may be significantly different from how other Software Engineers are producing code, and that may account for some of the differences you note in your personal experiences programming.
1) It was good for me to spend years learning the little stuff. Loops, variables, if conditions, how to import stuff, git, debugging things, reasoning about the flow of control. Classic coding.
2) I had a false dawn at about 10 years in. I thought I understood a lot.
3) I learned I had a lot to learn. Very wide areas of programming I'd never touched, ways of thinking that started to click.
4) I spent another ten years covering holes, building a different type of experience. My guesses about how to do a project are much better now. My guesses about what really matters have changed.
5) Now the small stuff is actually just bothering me. I'm not going to learn much more from staring at little things. There are larger architectural things to think about, and the little things are just friction.
So that's where I'm coming from. I get that a lot of pushback is going to be from 10-year-me, who thought he'd gotten to a high level of understanding by slogging through the little stuff.
Speak for yourself. Writing code has never been a bottleneck for some of us. I can't speak for everyone, and neither should you.
>Now, I can just tell the LLM and I will get what I want, even with the things I didn't explicitly state, without spending attention.
This should worry you. All too often the LLM invents things I didn't ask for and implements things I didn't need. YMMV, I guess. If slop gets the job done, and nobody notices, then who should care?
Are they as good as handcrafted code by 0.1% of top software engineers. Generally no. But neither is 99.9% of real code.
LLMs also are good at code reviews. What they'll miss is often the big picture but they can still catch plenty of issues. I still want to see a human in the loop in my domain.
Totally agree that writing the code was never the bottleneck. We're not seeing massive productivity gains even if some code is written faster. It's not just about understanding but also various other activities that happen in large companies and teams.
Also agree LLMs can be used to gain quality but realistically most orgs are going to aim for "fixed or decreasing" quality at lower costs.
However, sometimes then I tell it to write an app with detailed instructions and it spits out garbage so your mileage might vary.
Debugging code step by step is how I understand complicated code.
I do know what good code looks like, but does that even matter anymore? All I know is that now, I get to focus on endless UX polish, which is the only thing the matters.
I feel like we are living through something like the Protestant Reformation, where priests once spoke Latin, and then started to speak in plain local language. The old guard did not like this.
I've been using it for a unity game for the past few years. Nowadays it will go sleuthing into packages and assembly and make decisions based upon what it sees there.
It will make comments about why it's doing something based upon a function call 3 methods deep.
God forbid any of these details change in a minor version update.
I find that depends on the target language. They can be good at writing maintainable code, but not consistently across every language.
The languages beginners usually gravitate towards are especially hard for LLMs to produce quality output for. Presumably this is due to the training data including all the unmaintainable codebases written by beginners in those languages, which hasn't allowed the LLM to converge on recognizing what a maintainable codebase looks like in those languages.