41 comments

[ 3.7 ms ] story [ 67.2 ms ] thread
I've seen a lot of people talking about how AI is making codebases worse. I reject that, people are making codebases worse by not being intentional about how their AI writes code.

This is my take on how to not write slop.

Fully agree. AI or not, it's still the human developer's responsibility to make sure the code is correct and integrates well into the codebase. AI just made it easier to be sloppy about it, but that doesn't mean that's the only way to use these tools.
..but unintentional AI (aka Modern Chaos Monkey) is so much more fun!
Because of the way that I use AI, I am constantly looking at the code. I usually leave it alone, if I can; even if I don't really like it.

I will, often go back, after the fact, and ask for refactors and documentation.

It works. Probably a lot slower than using agents, but I test every step, and it is a lot faster than I would do it, unassisted.

Page not rendering well on iPhone Safari.

Good content tho!

(comment deleted)
*adds "be intentional" to the prompt*

Got it, good idea.

My intentionality is that I'll never let it make the changes. I make the changes. I might make changes it suggests, but only upon review and only written with my hands.
unfortunately, unless you are god level good (i would say top 100 developers in the entire world), you will be fired eventually.
Code cannot and should not be self documenting at scale. You cannot document "the why" with code. In my experience, that is only ever used as an excuse not to write actual documentation or use comments thoughtfully in the codebase by lazy developers.
"Every optional field is a question the rest of the codebase has to answer every time it touches that data,"

This is a beautiful articulation of a major pet peeve when using these coding tools. One of my first review steps is just looking for all the extra optional arguments it's added instead of designing something good.

There's nothing specific to AI about this. Humans make the same mistake.

To solve this permanently, use a linter and apply a "ratchet" in CI so that the LLM cannot use ignore comments

Optional field can be addressed with good defaults. Well, that is how I think about it. I.e. if they aren't passed in then they are set to a default value.
Here is an example of what I mean when I say optional stuff is a sign of failure to design.

I inherited some vibe-coded scripts that dealt with AWS services like Bedrock and S3. These scripts needed create various AWS SDK clients. These clients needed to know which account/region to use.

Had this been well designed, there would have some function/module responsible for deciding what account/region to use. This decision point might be complex: it might consider things like environment variables, configuration files, and command line arguments. It might need to impose some precedence among these options. Whatever these details, the decision would be authoritative once made. The rest of the code base should have expected a clear decision and just do what was decided.

Instead, the coding assistant added optional account/region arguments in many submodules. These arguments were nullable. When left unspecified, "convenient" logic did its own environment lookups and similar. The result was many "works on my machine" failures because command-line arguments affected only certain portions of the program, environment variables others, config files still others.

This is grim stuff. It's a ton of code that should not exist, spreading the decision all over the code.

What if it's not _my_ codebase?
What changed for me isn’t that AI writes bad code by default, but that it lowers the friction to adding code faster than the team can properly absorb it. The dangerous part is not obvious bugs, it’s subtle erosion of consistency.
Well said. I have to review PRs of non-software developers nowadays.

The “what is this trying to do?” has never been harder to answer than before. It creates scenarios where 99% is correct, but the most important area is subtly broken. I prefer it to be human, where 60-80% will be correct, and the problematic areas begin to smell more and more gradually.

In my experience LLMs, at times, may hide the truth from you in a haystack made of needles.

The concepts of Semantic Functions and Pragmatic Functions seem to be analogous to a Functional Core and Imperative shell (FCIS):

https://testing.googleblog.com/2025/10/simplify-your-code-fu...

The key insight of FCIS is that complicated logic with large dependencies leads to a large test suite that runs slowly. The solution is to isolate the complicated logic in the functional core. Test that separately from the simpler, more sequential tests of the imperative shell.

I think it's much better put in your link. Op is too vague on what constitutes pragmatic v semantic... when what he should just say is make it pure functional because then you don't have to simulate a database in your test suite.
AI feels less like an autonomous programmer and more like a very capable junior engineer.

The useful part is not just asking it to write code, but giving it context: how the codebase got here, what constraints are intentional, where the sharp edges are, and what direction we want to take.

With that guidance, it can be excellent. Without it, it tends to produce changes that make sense in isolation but not in the system.

The velocity problem is real. AI makes it easy to add things faster than you can understand what you added. The intentionality has to come before you prompt, not after you review.
[dead]
This could have been html instead of whatever awful moving pattern it is.
Made it with Vite+. Highly recommend trying it, rolldown HMR and build times are freakishly fast.