LLM code past a few pages is really unreadable. A workflow of LLM writes code, human reads/checks/corrects is IME very unproductive and frustrating, and you're usually better off writing by hand (YMMV).
If you want to reap some productivity improvements, it can't rely on you reading, grokkong and accepting the LLM code line by line. You just kind of need to suck it in.
So the best you can do is to indeed, constrain it heavily, maybe write the interface, then have the LLM write tests, then have it write the code.
I'm not saying I like the approach, but I agree manually verifying LLM code is very frustrating and unproductive.
The author of clean code (who makes his living consulting on how to write software) has pivoted to AI in a spectacular tweet about how to set up automated software development just in time to make his living consulting about how to set up automated software development.
Interesting read. To his defense: the argument is not that Optionals are too complicated, but that it’s a wrong path for language design — instead programmers have to test their code properly.
I think Uncle Bob worship by OOP adherents is crazy, but I also think dismissing him this way is also crazy. His work on popularizing methods for writing good OOP is solid.
But at the end of the day, he's a guy with a long career in programming, which makes him significantly better than the median, but it doesn't make him three sigma above.
In this case, he's got a pretty good short- to medium-term argument that if you have a robust testing and verification suite, AI code that passes it all is a terrific outcome.
I'm really not convinced about long-term. Possibly, AI ends up writing even better in the future and we never have to worry about human maintainability.
But it's also possible AI is at/near its limit, and we'll always need human maintainers. In which case, incomprehensible vibe coding might be a problem.
You just have to make maintainability and readability explicit criteria at the strategic level and hold the AI accountable yourself, same as with testing and linting and the tactical level. Fable can generate code that is absolutely readable by a middle school kid, it'll just be less efficient at it than full jargon-laced fanciness, so finding the sweet spot isn't too hard.
Which is a stupid argument that completely misses the point. If you let the compiler prove that nulls (or other invalid states) can't happen (because null isn't a value of that type), then you don't need to test these impossible cases. So it's easier to ensure your tests are solid because there's not a ton of noise checking what happens inside of invalid program paths.
This does not require a new language feature every time there is a bug, as he asserts. It requires language features to let you be descriptive in your type definitions so that invalid program states don't exist by definition. You literally cannot write one down. It's the same idea as saying you can't assign a Monkey to an int64. Scala's ZIO also shows that in fact you can type-infer whether a given path will produce errors or nulls, so you don't need to have perfect knowledge up front or go back and change tons of code if that changes. Errors can automatically propagate, and you need to handle them once, somewhere. Checked exceptions were a fantastic idea; you just need to let the compiler infer them everywhere.
You can both test your code for correctness, while also letting the compiler enforce correctness, and communicate important information to people reading your code.
I keep posting this but it keeps being relevant. I had an agent implement a feature completely backwards. It wrote a whole bunch of tests proving the correctness of the implementation. All the tests passed.
The really interesting thing to me is that formal verification wouldn't have helped there either -- it would have just written a mathematical proof of the correctness of the backwards feature.
A few models ago it was not unusual to find that Claude had written tests whose assertions looked correct, but were so thoroughly mocked out that they ran no real code at all — a Potemkin test.
I haven’t seen it do that in quite a while, but it was an interesting failure mode!
Wait, did you let the same agent write the tests that wrote the implementation? Or was it that the plan was the wrong way round, and both the generator of code and the generator of the tests followed that same incorrect plan?
Also, what were your mechanisms for reviewing the plan against the described business outcome? Anything else you could have done there to catch the backwardness?
And finally, did you run any of it across models from different foundation labs? I'll often run important stuff generated by codex across Anthropic, grok, and Gemini with an opus or fable judge...
This is the dude who ushered in the age of nonsensical boilerplate-ridden OOP code where you need to go down a bajillion of abstraction layers to see the actually implementation. Makes sense that he doesn't care about the bajillion lines of code AI produces as long as it looks good (on the surface).
Does he read his tests and constraints? You’d need lots of tests to have that kind of confidence (SQLite has 500+ lines of tests per line of code), at which point it’s not a lot of additional work to just read the code.
Right now there is about a 95% chance that when I manually review an agent's code I find nothing that needs changing.
I find it plausible that an extra agentic review pass and more testing can bring this number up to the point that one never needs to review code again. AI writes pretty good code nowadays.
(You still need to be diligent and decide the architecture during the planning, and read the gotchas and "things to note" that the agent will spit out at the end of implementation if it had to diverge from the plan.)
My current code base has more tests than runtime code. This sounds far more thorough than that. If you limit the possible productivity improvement to a small portion of total effort, it's inherently also going to be a small improvement.
Which, is fine, but not what most people are hoping for with these tools.
maybe the definition of 'clean code' in the agentic era should be changed to 'the code that complies to the specs, the design constraints and the exhaustive scenario test suite';
this is definitely a better outcome since all the human effort now shifts to spec, design, test scenarios tailored to the domain - as it should be.
having seen my share of human slop through decades (mine included), finally it's a relief to not have to deal with devs that don't have high standards, and the endless arguments/politics that ensue.
with llm it's just a text file away from compliance.
Appeals to authority aren't necessarily bad. I would hold an opinion on the internals of postgres from someone who worked heavily on postgres internals for forty years quite highly.
Robert Martin started coding in the late 60s and then became an author and a software design consultant - his statement defines the start date of his relevant experience but doesn't speak to the end date of that experience or the density of his experience. He has a wealth of experience but not in a field that's relevant to his comment.
I think an appeal to authority is a good basis for extending a bit of extra trust to statements - but you should always verify things yourself.
It was a pertinent response to the end of the tweet he was replying to.
> Started programming in 1983. Old?
I took that sentence as asking if he was old because he couldn’t trust AI and uncle bob brought up that he was much older and trusted the AI output because he trusted his constraints and test harnesses
>My current strategy is to not read any of the code written by my agents. That’s the only way I can take advantage of their productivity.
When you start getting good results from agents you soon realize you are the bottleneck.
Automating the verification of the code is the way to go, otherwise it's just not worth it. It takes longer to read and understand code than to write code, so why bother with agents if you are going to manually review it all anyway?
One thing I miss after ditching Copilot (it got too expensive) was how I could trivially ask for features to be written by one model and verified by another. Have Opus write it and GPT or Gemini verify it.
I figured they were entirely separate models and therefore unlikely to hallucinate in the same way, so it gave me a quick sense of confidence.
Currently I use claude code (different models but all variants of the same) so I have them do planning, review of the plan, implementation, review of the implementation, and unit tests. It's fine, but copilot felt easier.
At the moment I do it manually, but I've experimented with having claude code explicitly call out to codex headlessly for adversarial review, and it works great, I just prefer being able to interact with codex during the review.
If you want even greater fun, launch claude and codex in the same working tree and make them fight it out in real time.
I read the post slightly differently.
LLM code past a few pages is really unreadable. A workflow of LLM writes code, human reads/checks/corrects is IME very unproductive and frustrating, and you're usually better off writing by hand (YMMV).
If you want to reap some productivity improvements, it can't rely on you reading, grokkong and accepting the LLM code line by line. You just kind of need to suck it in.
So the best you can do is to indeed, constrain it heavily, maybe write the interface, then have the LLM write tests, then have it write the code.
I'm not saying I like the approach, but I agree manually verifying LLM code is very frustrating and unproductive.
The author of clean code (who makes his living consulting on how to write software) has pivoted to AI in a spectacular tweet about how to set up automated software development just in time to make his living consulting about how to set up automated software development.
"How does Uncle Bob code today? Find out in Clean Vibe, the long-awaited sequel to the legendary Clean Code - coming soon from Prentice Hall!"
This is the guy that thinks optionals are too complicated? https://blog.cleancoder.com/uncle-bob/2017/01/11/TheDarkPath...
Interesting read. To his defense: the argument is not that Optionals are too complicated, but that it’s a wrong path for language design — instead programmers have to test their code properly.
I like optionals, but I see his point too.
Bob Martin has built a career on asking the wrong questions. He is doing the same here as well.
I think Uncle Bob worship by OOP adherents is crazy, but I also think dismissing him this way is also crazy. His work on popularizing methods for writing good OOP is solid.
But at the end of the day, he's a guy with a long career in programming, which makes him significantly better than the median, but it doesn't make him three sigma above.
In this case, he's got a pretty good short- to medium-term argument that if you have a robust testing and verification suite, AI code that passes it all is a terrific outcome.
I'm really not convinced about long-term. Possibly, AI ends up writing even better in the future and we never have to worry about human maintainability.
But it's also possible AI is at/near its limit, and we'll always need human maintainers. In which case, incomprehensible vibe coding might be a problem.
You just have to make maintainability and readability explicit criteria at the strategic level and hold the AI accountable yourself, same as with testing and linting and the tactical level. Fable can generate code that is absolutely readable by a middle school kid, it'll just be less efficient at it than full jargon-laced fanciness, so finding the sweet spot isn't too hard.
Which is a stupid argument that completely misses the point. If you let the compiler prove that nulls (or other invalid states) can't happen (because null isn't a value of that type), then you don't need to test these impossible cases. So it's easier to ensure your tests are solid because there's not a ton of noise checking what happens inside of invalid program paths.
This does not require a new language feature every time there is a bug, as he asserts. It requires language features to let you be descriptive in your type definitions so that invalid program states don't exist by definition. You literally cannot write one down. It's the same idea as saying you can't assign a Monkey to an int64. Scala's ZIO also shows that in fact you can type-infer whether a given path will produce errors or nulls, so you don't need to have perfect knowledge up front or go back and change tons of code if that changes. Errors can automatically propagate, and you need to handle them once, somewhere. Checked exceptions were a fantastic idea; you just need to let the compiler infer them everywhere.
You can both test your code for correctness, while also letting the compiler enforce correctness, and communicate important information to people reading your code.
I keep posting this but it keeps being relevant. I had an agent implement a feature completely backwards. It wrote a whole bunch of tests proving the correctness of the implementation. All the tests passed.
The really interesting thing to me is that formal verification wouldn't have helped there either -- it would have just written a mathematical proof of the correctness of the backwards feature.
A few models ago it was not unusual to find that Claude had written tests whose assertions looked correct, but were so thoroughly mocked out that they ran no real code at all — a Potemkin test.
I haven’t seen it do that in quite a while, but it was an interesting failure mode!
What do you mean by "completely backwards"?
In test-driven development, you're supposed to write out the tests first to specify the desired behavior, then write the code to confirm to that.
I think he means it was very wrong.
Wait, did you let the same agent write the tests that wrote the implementation? Or was it that the plan was the wrong way round, and both the generator of code and the generator of the tests followed that same incorrect plan?
Also, what were your mechanisms for reviewing the plan against the described business outcome? Anything else you could have done there to catch the backwardness?
And finally, did you run any of it across models from different foundation labs? I'll often run important stuff generated by codex across Anthropic, grok, and Gemini with an opus or fable judge...
Ironically from my experience seeing AI code a lot, it probably follows his clean code verbosity beautifully
This is the dude who ushered in the age of nonsensical boilerplate-ridden OOP code where you need to go down a bajillion of abstraction layers to see the actually implementation. Makes sense that he doesn't care about the bajillion lines of code AI produces as long as it looks good (on the surface).
Does he read his tests and constraints? You’d need lots of tests to have that kind of confidence (SQLite has 500+ lines of tests per line of code), at which point it’s not a lot of additional work to just read the code.
Right now there is about a 95% chance that when I manually review an agent's code I find nothing that needs changing.
I find it plausible that an extra agentic review pass and more testing can bring this number up to the point that one never needs to review code again. AI writes pretty good code nowadays.
(You still need to be diligent and decide the architecture during the planning, and read the gotchas and "things to note" that the agent will spit out at the end of implementation if it had to diverge from the plan.)
But does he read all those tests to know if they're right? Or does he just ask the LLM if the test it created were good?
Focusing on the surrounding other than the code itself is not good strategy or behavior.
Giving up reviewing code is not strategy, but because:
Review AI code line by line is like watch movies frame by frame, and is impossible, very difficult, terribly boring, or abandoned sooner or later.
Well, he knows where is toast is buttered
So the next edition of clean code will be called “clean test code”?
From my perspective, the primary end-user selling point of LLM assistants is not having to read or write anymore.
I think a lot of the "AI Addicts" are masking illiteracy, which is why they cling so passionately to the technology.
My current code base has more tests than runtime code. This sounds far more thorough than that. If you limit the possible productivity improvement to a small portion of total effort, it's inherently also going to be a small improvement.
Which, is fine, but not what most people are hoping for with these tools.
So no human in the loop then? Sounds like a recipe for tech debt…
I currently need 4gb of RAM MINIMUM to send an email. AI didn't get us here.
No, but it does learn from where we are. If where we are is debt, debt is where we'll remain.
Sure, but AI is trained on the code that got us here isn't it?
Do you think that we're going to use less RAM with AI produced code?
"Do not use react. Do not use tailwind. We will not deploy on vercel."
and suddenly things work again, it's magic!
React is like 150kb :/
the expensive bugs are usually gaps in the spec itself
Well, Uncle Bob has always been a hack so this isn't too surprising
When my nephew went into computer science, I had to warn him about the bracelet cult people
for anyone else who was confused: https://clean-code-developer.de/die-initiative/armbaender/
maybe the definition of 'clean code' in the agentic era should be changed to 'the code that complies to the specs, the design constraints and the exhaustive scenario test suite';
this is definitely a better outcome since all the human effort now shifts to spec, design, test scenarios tailored to the domain - as it should be.
having seen my share of human slop through decades (mine included), finally it's a relief to not have to deal with devs that don't have high standards, and the endless arguments/politics that ensue.
with llm it's just a text file away from compliance.
> I’m significantly older than you. I started coding in the late 60s.
Any opinion that starts with such a blatant appeal to authority can safely be ignored.
Appeals to authority aren't necessarily bad. I would hold an opinion on the internals of postgres from someone who worked heavily on postgres internals for forty years quite highly.
Robert Martin started coding in the late 60s and then became an author and a software design consultant - his statement defines the start date of his relevant experience but doesn't speak to the end date of that experience or the density of his experience. He has a wealth of experience but not in a field that's relevant to his comment.
I think an appeal to authority is a good basis for extending a bit of extra trust to statements - but you should always verify things yourself.
Don't fall for the the fallacy fallacy.
It was a pertinent response to the end of the tweet he was replying to.
> Started programming in 1983. Old?
I took that sentence as asking if he was old because he couldn’t trust AI and uncle bob brought up that he was much older and trusted the AI output because he trusted his constraints and test harnesses
[flagged]
[flagged]
Robert Martin, the author of Clean Code, is a caustic dick defending a book full of spurious commandments. I don't put any stock in his takes.
>My current strategy is to not read any of the code written by my agents. That’s the only way I can take advantage of their productivity.
When you start getting good results from agents you soon realize you are the bottleneck.
Automating the verification of the code is the way to go, otherwise it's just not worth it. It takes longer to read and understand code than to write code, so why bother with agents if you are going to manually review it all anyway?
One thing I miss after ditching Copilot (it got too expensive) was how I could trivially ask for features to be written by one model and verified by another. Have Opus write it and GPT or Gemini verify it.
I figured they were entirely separate models and therefore unlikely to hallucinate in the same way, so it gave me a quick sense of confidence.
Currently I use claude code (different models but all variants of the same) so I have them do planning, review of the plan, implementation, review of the implementation, and unit tests. It's fine, but copilot felt easier.
At the moment I do it manually, but I've experimented with having claude code explicitly call out to codex headlessly for adversarial review, and it works great, I just prefer being able to interact with codex during the review.
If you want even greater fun, launch claude and codex in the same working tree and make them fight it out in real time.