Write code like a human will maintain it(unstack.io) |
Write code like a human will maintain it(unstack.io) |
I’m pretty sure many people who use AI to write emails or blog posts add "make it sound like a human wrote it" to their prompts. We all know what the result usually looks like.
If AI is writing my code, I'd rather have it focus purely on correctness and efficiency than on making the code easy to read.
heck! I might even ask it to imitate Arthur Whitney’s style.
/s
One pattern I've really been enjoying lately has to do with a language called haxe. It's designed to be compiled into other languages (java, python, others). There's this extension called reflaxe which lets you make mini compilers for compiling haxe into pretty much anything.
So if I have anything with duplicate structure... like maybe I want the CLI subcommands to resemble the http API.
$ foo bar --baz 6 # produces similar output as HTTP GET /foo/bar?baz=6
...and `docs/generated/foo.md#bar` should have documents both the CLI and the http usage. Then I have the LLM maintain a haxe source of truth and then have it compile that source of truth to the other stuff.Previously I was using OpenAPI spec's and generators for this, but they wound up feeling like a black box that I ended up debugging all the time. The reflaxe setup is much more generic. So you end up with this mountain of generated code, but unlike LLM-generated code it's obviously generated (in .../generated/thingy.py or whatever) and a comment at the top of the file says where to look to learn how it's generated). I'm generating docs this way, accessor functions for database tables and SQL for making those tables, matching clients and servers in different languages... it replaces a lot of purpose built tools with just one, and LLMs are pretty good at it.
So even though the repo is large, the parts of it that are authoritative remain small. I find LLM's manage this boundary much better because it's so crisp an in their face. But they have to be told to do this, otherwise they'll just create context-size problems that they'll later struggle with.
Of course you could do this with yaml files or some such, but unlike yaml, haxe has a type system, you can write tests in it, so the agent can notice fundamental flaws before the generation happens... with yaml those flaws would be propagated into the generated code before they'd get noticed.
Otherwise what I have found is that the LLM will add a new if statement which will handle the newly discovered issue and you start stacking them ifs. As the article mentions LLM's unlike humans aren't lazy, they will copy, paste add patches for every issue, why bother think and understand root cause :d.
So as part of our review we have a rule against that as well.
The benefit is that a human who is a junior might need at least a few weeks to months of guidance to have a good taste of when to duplicate and when to DRY. An LLM likely already has good judgment, and your prompt merely needs to be activate this judgment.
bottom up AI use seems a godsend compared to the corporate AI rat race.
i setup some slop reporting systems and ensured my boss knows theyre great starting points but serious use requires real time investment.
https://arxiv.org/abs/2605.20049 https://arxiv.org/abs/2605.13280
> we are not going back to hand-writing these functions
do you really think there isn't a good chunk, if not the majority outside some bubbles, of developers that still hand code? Crazy to hear, I bet you're not a programmer
Of course, this doesn't solve the overall issue that agents don't write code like you and still requires a lot of human attention in planning and code review out to clean up leftover issues, and e.g. challenge bad assumptions about architecture and real-world context. A human is still very much needed to cull the slop (or, more gratuitously: align the agent). But IME it does help avoid a lot of pitfalls and makes the code high quality a lot more quickly.
Funny enough, discussed this yesterday
Stop Optimizing Code for Humans https://youtube.com/live/eLn4-XA-KdQ?feature=share
— John F. Woods (1991)
My first real use with them as that was during an interview with Jetbrains’ assistant enabled and I repeatedly went “oh! Huh!”, so … in that form, it seems very good but that’s first impressions and everything feels like magic at first
This is kinda the "this could have been an email" of AI complaints.
As an AI acolyte, I strongly believe that AIs should be being trained for this sort of mode by default, but they tend to be optimized (or at least tested) to solve the single shot benchmarks rather than multi-step stuff that you really need in a long term maintainable project.
There is a https://www.scbench.ai/ SlopCodeBench that checks this and a variety of others. These don't seem to often come up when frontier labs are advertising their new models.
We therefore want to encapsulate the logic of all users being "qualified". I think the best way to realize this is to try to say in as few words as possible what we want. We want the user.toBe.qualified, but we can write that even more efficiently, user.isQualified(), so we can ASK at the top of each API `if !user.isQualified()` and then fail loudly and quickly if so, and then go do something afterwards otherwise. We presumably expect multiple users that will have a property of being qualified, so let's use a class:
```js
class User { constructor(data) { this.isAuthenticated = data.isAuthenticated; this.hasActiveLicense = data.hasActiveLicense; \\ many more details... }
// Now encapsulate the boolean property of being qualified, for all Users, in one place
isQualified() { return (this.isAuthorized && this.hasActiveLicense && this.hasPaidInFull && this.hasAdminPermissions); } }
// Now, we can use our isQualified() property at each of our API guard clause by checking if the user is NOT qualified:
if (!user.isQualified()) { return res.status(403).json({ "error": "Unauthorized access requested."}); }
// Critically, the API guard clause remains identical even if we change the criteria for validation and what it means to be "qualified"
```
There are of course many other ways to solve this problem beyond this approach. For instance, you could export a function called isUserQualified(user) (or more likely userId) then call it somewhat similarly:
```js
if (!isUserQualified(user)) { return res.status(403).send("Error: Unauthorized access requested"); }
```
The other approach I like is to use a factory pattern to build a userSession with function arrow notation. That's really helpful when you are getting raw data back from a database call, say as a JSON object. That would look like this:
```js
const createUserSession = (userData) => { return { ...userData, // here we use the ...notation for convenience but don't trust this as secure) isQualified() { return ( this.isAuthorized && !this.isSuspended ); } }; };
// Then you instantiate a user session from the request like this:
const user = createUserSession(res.session.user); if (!user.isQualified()) { return res.status(403).send("Error: Unauthorized"); }
```
There are actually loads other approaches too, like using class inheritance (define a parent User class and child QualifiedUser class, for instance), or types. I would say that readability here is less a concern than the issue that you have multiple APIs and if you refactor or change the logic for qualified users, now you have to refactor your most sensitive attack surface at multiple points. It's just safer to have that rewrite only happening once, wherever you put it, in my view.
Maybe someone has the perfect claude.md that solves this problem but I have not seen it.
Instead modularize the knowledge with skills and specialized MD files. Agent should lazy load what is needed to do focused work.
Skills have usage description metadata, but with free files you can simply instruct agent with CLAUDE.md to load them, e.g.: "Before you attempt to change any frontend code first load and follow `docs/{JS|HTML|CSS}_coding_rules.md`".
The general intent is fine, and I'm certainly not abandoning everything we've learned as an industry - but LLMs are not people, people are not LLMs, and there's no reason to believe that the coding practices we've found to be good for humans will be good for LLMs.
Each time we apply a practice like this, we should ask ourselves what problem it was intended to solve, whether that problem still exists for LLMs, and if the existing practice is the best fit for LLMs.
You still have to look at the diffs because it won’t have external knowledge to the codebase or make the best decisions.
It does find bugs and keep things simple.
Taken from: https://github.com/zakirullin/cognitive-load/blob/main/READM...
Getting away from stuff like this is exactly why I want to use AI. When I say "implement this for idle but active users," I _want_it to define isUserActiveIdle() and stuff these 4 conditionals in it. Having to check the generated code for stuff like this undoes, like .... all the benefit of using AI.
AI makes all these little decisions for us. I can about some of these decisions. I just want to notice when it's doing this without having to make my eyes bleed reading 10k lines of generated code a day.
Collect all findings and classify into Blocker/Maybe/Nits.
Very simple, but effective: I like it very much!
Where it really, really struggles for me is in existing complex infra codebases.
Write code like a coworker you really like will maintain it.
There's a huge cost to Clean-Code-style DRY'ing of your codebase which is that you wind up creating all kinds of little functions that all add cognitive overhead to reading your codebase, and that premature DRY'ing can lead to picking the wrong abstractions.
If you can tolerate a bunch of copypasta, you can sit back after you've written 5,000 or 10,000 lines of code and can look at the actual result, instead of speculating, and make better-informed decisions about how to clean the codebase up. If you're making those decisions the first time you copy a bit of code around, you can wind up making a worse mess, since you often don't know where you're going.
(Also performance and security issues, addressing which doesn't make the code more readable, but does make the program itself more robust.)
That being said... I have found them weirdly unsuitable for some tasks though, e.g. asking frontier LLMs to assist me with game development, I found that they were not able to add simple features to a simple Pong clone (nor even port a working Pong implementation) without constantly breaking things. (Yes, Pong, from 1972!)
So, I want to say YMMV, but just the past 2 weeks, my own mileage has varied very much! They seem to be extremely domain specific.
"Half the time, it works every time!"
(Because it's true.)
disagree, then you end up with something this this
function checkAll(target, conditions) { return Object.entries(conditions).every(([path, expected]) => { const value = path.split('.').reduce((o, k) => o?.[k], target); return typeof expected === 'function' ? expected(value, target) : value === expected; }); }
and const ok = checkAll({ user, account }, { 'user.isActive': true, 'user.isSuspended': false, 'account.status': 'open', 'user': u => u.hasPermission('read'), // predicate for the trickier bit });
how is that better?
code review has always been a liability fig leaf. it is much harder to understand a system from reading the code than writing the code. if AI can write code 100X faster than humans it is simply impossible for humans to do real code review. effectively it is just pretending to do code review, and then running unaudited code without the proper systemic security guardrails in place.
At a minimum, there should be precommit checks and CI workflows that cause PRs to fail if the documentation is not up-to-date and synced with the other docs.
Then regular codebase analysis for improvement. This is where you find the bug sources, make new modules for consolidation, and get those +5000/-4000 PRs that people stuck in the world of manual code review hate.
Mine starts with “Enter plan mode. Examine the differences on this branch vs. main. Consider: ...” and proceeds to a bullet list of things.
Any time I notice something in code review and have to get the agent to fix it.. I throw it on the list!
My list is like 200 items now. Know what? Agents don’t care that they just got a wall of generic feedback, they happily look into all the bullet points.
I added “ensure the new things aren’t duplicating code that already exists elsewhere” and it gave me such a surprise - it really truly started planning cleanups!
We are just scratching the surface. We have to give tools to our tools so they can use them to be better tools for us.
I actually have a pretty simple set of instructions right now and Claude still regularly messes them up. Like, my first instructions are:
- Never commit to git without permission.
- Never sign commit messages
You know what it constantly does? Commits without permissions and signs commit messages! And then I ask it, and it's like "oh, you're right, I have that instruction and I ignored it". If you were working with a junior developer, you'd expect at some point they get it, but with Claude even if I tell it to stuff the instructions into it's memory, it will do it, and STILL mess it up.Then it gets even more fun, because if I politely correct it in the session, it will understand, but then there's a decent chance it will be completely unable to commit anything in the session going forward. Phd level intelligence!
Anyway, my point is, if you're asking it to review a list of 200 items I guarantee it's quietly messing up on a lot of that list.
Negative prompting is very unreliable. Giving exact instructions on how you want commits made will give you better results.
No signing is easy - make signing interactive and requiring a password.
Anyway, telling agent to NOT do something is always worse than telling agent to do something.
That's why you should say something like "Use constants instead of magic strings/numbers" rather than "Do not use magic strings/numbers".
Also whatever review its doing against the list of checks - must be a fresh context.
- Always sign commit messages
In which case, Claude is being given conflicting instructions, which is a different class of problem than too many instructions.As some other comments have said, there are various other options like hooks which are run by the harness and can be used to always enforce certain things (running a formatter for example).
But for your examples, settings are the correct solution. Set an ask permission on git commit and it will always prompt you before committing. For commit messages, just set attribution commit to an empty string and it will stop adding its own attribution. https://code.claude.com/docs/en/settings#attribution-setting...
Two tiny changes; problem solved permanently.
Every harness I've tried has obeyed that.
Why? It has no idea why it does what it does…
You know what solves this? A deny rule. That’s right, you can solve it committing without permissions by.. not giving it permissions.
> it's quietly messing up on a lot of that list.
If only there was some way to know. But alas, code review won’t be invented until 50 years ago.
Non-deterministic behavior via code.
This is a gripe I've had with AI tools for a while now. Though it's gotten somewhat better in time, but we don't really know what to expect from the tool in terms of quality. Ex. I'd expect a human engineer to probably not use a brand new assertion library for a new test when there are 200 tests using an existing one. But Claude has done this to me multiple times. So I have to add yet another item to the list, like you have, and tell it to look for testing conventions before writing. But, there is plenty we don't have to tell it, like what a function is or a test should probably cover the change in the diff. But we don't really have a list of what things are on each side so we're just left to sort of hunt and peck to build a viable solution.
Which is fine. One should write quality docs and all that. But it seems exactly like training my offshore contract replacement instead of building tools for my own use. Except that income doesn't even go to a human just a black box datacenter company and shareholders.
/init will make a project-wide one, or you can instruct it to "Create CLAUDE.md in any sub-directory that is sufficiently complex" then modify from there.
You don’t need a SaaS when you can use a text file instead.
Just be incrementally aware of and closing gaps in the list of things you care about, and make it part of your day!
I am curious what does it contain, for me a lot of times its a back and forth with agent until it "looks good to my eyes and taste", but haven't written any such list yet, because it is context dependant, in some projects I forgive minor issues, or allow magical numbers, but in other projects I force agent to use constants with meaningful names `SECONDS_IN_A_DAY = 24 * 60 * 60`
https://en.wikipedia.org/wiki/Code_smell
*edit: that wikipedia page ^ itself is a pretty answer to your request for a list of things to avoid when writing maintainable code.
For a Rust project, I created macros that output compiler errors when documentation and tests are not in a shape I want them to be, like missing function invocations or assertions, which forces the agent to address them, where otherwise they would've just worked around them by adding stupid trivial assertions like `assert_eq!(true, true)`.
That still isn't fool-proof either, but it helps minimise those instances. I'm bullish on the idea of integrating formal methods and model-checking with AI. I think that combo feels like a promising avenue for constraining the stochastic side of AI-generated code with something closer to deterministic verification. Provided you can write correct specs of course!
https://github.com/cadamsdotcom/CodeLeash/blob/main/.claude/...
* https://github.com/alibaba/open-code-review
** https://layandreas.github.io/personal-blog/posts/beyond-vide...
- You should use skills instead of commands (commands still work, but they've been rolled into skills) https://code.claude.com/docs/en/skills
- Hardcoding git diff against main isn't always ideal (depending on your git workflow). If you branch off of branches, the agent will figure it out, but it often has to go through a few hops to determine what to actually diff. You're better to ask it to diff 'the commits on this branch' or similar.
- You might not necessarily want to enter plan mode for a review. I don't personally as I don't see any benefit to it writing a markdown plan file for a review.
- Skills over commands: agreed - but they are more complex than a single file. I’m suggesting a command because it’s a great way to start and build up that checklist. Over time, migrate it to a skill. But one thing I’d say is keep it in your repo. That way, PRs can include new bullet points with the code that gave rise to them. Opinions will differ on this: it muddies the PR a little in the present. But OTOH future folks can git blame to see where that bullet came from.
- Hardcoding against main: yes, this is a very good catch - a better prompt is to tell the agent to compare to the branch’s merge base. Again a detail left off for simplicity to encourage folks to start somewhere. Your suggestion is a quick win fast follow for correctness.
(side note - 90% of the time Opus 4.8 guesses the intent and decides to use the merge base on its own! Opus 4.6 didn’t do that. These things are smart if you set them up for success!)
- Plan mode for review I would say personal choice. It is crucial in my experience because some of my list makes it overzealous in planning cleanups and the like. YMMV. Yes plan mode is optional and I sometimes have to reread and discuss a review plan after a context switch in my day - but at least it didn’t spray bad cleanups all over a decent PR.
Good way to double your token use though, if you’re concerned about that.
Yes, yes, there has been a library of information on HN by now about how to use agents effectively. (And I'm grateful for that, because I can keep current and in the loop without feeling enslaved to the new style of development.)
None of that is a reason not to do what the title of TFA says. If your review process is doing the right thing, you should observe that it results in your agent moving the code in the "human-maintainable" direction. If you, for whatever reason, actually directly make commits yourself any more (read this ironically; I genuinely can't understand why anyone would want to give up on that, no matter how good the generated code gets, because "the LLM could do better" is not the point), then of course you should write it to be human-maintainable.
The reason humans find "human-maintainable" code to be maintainable is because maintainability is one of the precious few worthwhile at-least-vaguely-objective metrics of code quality we have.
Every time I see someone try to make a point about the fact that some code actually is just better than other code, only to be met with more of this sort of advice, I start to wonder whether I was alone in ever actually enjoying programming.
I still do code review - how else would I know what to codify! But since starting this about a year ago, I don’t review menial dumb AI mistakes anymore.
yes, but where?
WTF - I need to implement a review command to guide to do its job properly.
Can you imagine any other industry charging people money for a product like this ?
When you are charging people money - scratching the surface cannot be an excuse.
Its first attempt WILL be crap. You can refine its output in the loop as a human, or let it go and then do a quality pass post-hoc.
The beauty of the latter is you can codify and automate a subset of the work, and the agent can loop until those bars are cleared then you get a higher quality work product to look at.
Will try your approach to distill the code to bullet points.
Do human developers check for 200 items when they do code review? How long would that take? It's quite clear that AI code review could be better even with some error.
What's easy for human to review is also easy for AI (small code base, small PRs). What's hard for AI is also hard for human, if not more. But the time cost is so different that it's almost a nobrainer to choose AI to code and review, especially considering most software out there is not so critical :)
AI is a different intelligence - a 200 point list shoved in its face all at once doesn’t overwhelm it. That’s only 5-10k tokens!
Bear in mind though, because I added all the items on the list I’ve seen the reasons they’re there & act as a backstop against it missing things. There’s always got to be a quality bar, it’s part of being a professional and signing your name on your work.
We (humans) don't do 200 point inspection, but we also don't review every code change in a spherical vacuum without prior knowledge of the project. However, we do check a lot of things.
> What's hard for AI is also hard for human, if not more.
That's just plain wrong. AI straight up suffers with counting things, and I'm not just talking about counting 'r' in strawberry. Some things are easier for AI some are easier for human.
A model might be able to follow 200 different instructions at the same time, while another model will choke on it
I actually have no data on how other models do - feeling very spoiled by Claude to be honest.
Would love to hear how it goes if you try it with other models!
Failing that they default to the most common style they were trained on. Which, at this point, is mostly code they wrote...
Basically I got sick of telling the agent to put dynamic imports at the top of the file so I made them into an error that blocks commits.
Now I don’t even need that in the review.md because it can’t make it though to /review.
To me building on multiple scalable systems this has been the most dangerous part of LLMs. On a good codebase it will work good, but it will maek it worse, so you keep using it, till it doesnt work and then you have to pay the bill and fix for what you didn’t build before.
If you put an agent on a fresh codebase 2 things are often given:
-> You have a mental model of the code -> The code is somewhet concise
After multiple iterations both is lost and LLM performance degrades. To solve this you can regular refactor, but it’s not a nice experienc. So my best solution is:
I use LLMs for exploration and for review, but I write the code myself. I find it hard to believe why so many engineers try to avoid it. It’s not consuming much of my time. And it’s actually the most enjoyable part.
Sometimes I race AI i give it a prompt /bug to fix and at the sametime im greping/symboling through the codebase and tryto fix it myself. AI isn’t always faster.
I know. It’s an unbelievable concept in this AI era. Write code? Isn’t that what dinosaurs did?
If you expect that a human will need to read and maintain that code you might as well write it for them. You’ll get annoyed by having to read overly-verbose copy-pasted code. So will they. So write the code yourself and bringo: you’ll fix things yourself and write things in a way that makes sense for other humans to maintain.
Or you can come up with a convoluted web of markdown files to try and coax your agents and loops to understand what future human maintainers will expect the code to look like.
I’m not sure what path will be easier in the long run. Anyone inherit a loop-based agent-driven code base yet and have to try to understand it?
Difference between something like Opus 4.8 and even 4.5 is massive, not even talking about difference between Opus 4.8 and Sonnet or Composer.
What i see online is that a lot of people are using cheaper models, stuff like sonnet or cursor composer or even latest cursor grok. But they aren't the same thing as using the best models. The difference in quality and correctness is huge.
The reason being that most software developers aren’t very good at specifying systems precisely enough that they can confidently determine whether their program does what they think it does.
We don’t need to specify systems to the rigour of mathematical proofs every time, of course. The point is that spoken language isn’t precise enough to measure correctness against.
When you suggest that the difference in correctness is huge, I can’t really understand what you mean. Correct with respect to how you think good software should be written? Correct with respect to informal specifications written in prose? How?
I know that the frontier models have large contexts and can compress a lot more tokens from their training sets. But how does that make it able to read your mind and make formal what is ambiguous or informal?
It work wonders for me.
To be honest I'm not sure how true this is. I think it's more that there does seem to be quite a baked-in bias to repeat basic structures and not reuse (much less come up with) abstractions. So where that is the existing pattern it looks like it's keeping with that, when in reality it would often do that either way.
There have been many cases where I've started a piece of work by laying down very rigid abstractions and a few examples of using them, and I explicitly prompt to not only exclusively use the specific abstraction API but also copy the way I've used it. And the (frontier) LLM does neither, it just steams ahead re-implementing things from scratch from bottom up basic structures, partially and often totally ignoring the abstractions.
I don't know exactly why this should be the case but my naive suspicion is that there's just an awful lot of this type of stuff in the masses of training code and the weights just somehow 'know better' how to get results this way, rather than using your more novel abstractions/patterns.
Yup, this is the sad state of affairs that we’re currently in, and the only way to avoid this is to specifically instruct the model on how exactly to implement things.
From my point of view, I think this is fine, to be able to use these LLMs as fast implementation engines. The challenge is to make it surface these types of implementation decisions before it goes off doing it the wrong way.
"Add comments to your code under the assumption that the next person to maintain it is a homicidal maniac who knows where you live"
Or is it more about the review process and a context reset?
You can even put them on the build pipeline, even. No tokens spent at all!
When you start a project not everything is DRY, and you don't start pulling out shared helpers until they're called for. If you're a slacker and expect the agent to magically fix everything with one-off prompting and only moving forward, it's not going to work. Agents prefer to carry existing patterns forward, just like many humans.
The shared helper example from the article requires intent to refactor. If you ask the agent look for smells and refactor, it will happily assist you with that. If you ask it to add a feature, which happens to add duplication, you get a feature with duplication because you didn't specify anything else.
Better yet, add tooling like static code analyzers (shout out to Credo) to your development pipeline and catch stuff like this regardless of LLM. Verify against a mechanical baseline, with LLM judgement layered on top as-needed.
"The most frustrating part: I thought I was outsourcing maintenance to the LLM, but the slippery slope I found myself on was actually training it to have ever-worsening habits."
That's on you. The LLM is a tool and you're not using it well. Build a good foundation for the agents to anchor on, and you'll get good results moving forward.
Another thing is competitive edge, if you use claude and your competitors use claude then nothing really gives you an edge. AI is a commodity, not competitive edge.
The competitive pressure should drive human work because it's unique.
In that case, humans with superior skills who can write code become the advantage.
That is important for companies that compete with each other.
The standards most "good developer" humans demand were learned from many decades of painful experience about what happens when you do it the other way. These are not only compromises for human convenience, they often are things that we have learned will come back to bite you later even though they just add more work today for no gain.
I started using AI with the best intentions. Checking everything before committing. Improving output by hand if it didn't quite follow the existing code style guidelines or variables were not named as well as they should be. Or if it did something sloppy or hacky.
Now, AI GOES BURRRRRRRRRRRR! If the tests pass it's good to ship. AI can deal with the problems it may create. No problems so far.
* define the software layers, their function, and the max depth allowed
* establish a corp code formatter for each language, along with a process to PR it
* establish a business vocabulary and what the terms mean
* establish a data dictionary, make it part of the database schema/table/col comments
Are far more successful with LLMs. You _should_ have been doing this years ago, but with LLMs its a super power.
1. Created a "coding" skill with every practice I posted on my blog website, as well as a bunch I had in the queue to blog about but never got a chance, summarized into "do this" kind of language. This is more or less good for any PL, but a bit Ruby-slanted.
2. Created a "rails" skill because that's my framework, where similarly I explained my approach to architecting Rails apps.
3. Created a "writing" skill where I literally fed it my entire blog, and tried to get it to write more like me (mixed success, weaker models did better for some reason, but I haven't tried the GPT-5.6 series yet).
4. Next, I really wanted it to format code exactly like I would, even things like "let's make this `if` into a ternary, let's split these assignment groups with a line break, let's vertically align here, but not there", but with GPT-5.5 (my primary driver up until yesterday) there's almost no way to make a skill of reasonable size that will be consistently applied. So instead I instructed the agent to write me a Rubocop cop for every single situation I ever encounter where I would've formatted code slightly differently. This was quite powerful, because I usually thought of linters as enforcers of objective consistency decisions in the codebase, but this was me going full format nazi on the agent. And the nice part is that these cops can contain some non-autocorrectable feedback, which AI will follow.
5. I'm working on a review loop where the most easily missed parts above get double checked. This is the first thing I'm doing with pi subagents. (I feel like I'm getting better results if I don't use subagents for code exploration, other tool calls). The idea here is that I want reviews to be in the implementation loop. I always read/review code in the end, but so want it to have gone through the review loop before it gets to me. Since implementation is already context-heavy, I want to be able to orchestrate this loop without adding to the implementation context.
6. I'm also adjusting all of the above for GPT-5.6, because it requires less guidance, so I'm carefully trimming the verbiage to save tokens.
So far the results have been surprisingly good. I want to experiment with GLM-5.2 running under these constraints.
One invariant in all of this: I read the code. My end product is not working software, it's good code (which also incidentally produces working software).
You'd be surprised how readable this makes the code when XXX is about the size of your vertical screen and Y is relatively small.
I’ll build whole features and then break them apart into several MRs that chain off each other.
Everyone who has seen this style has been really grateful for it and finds my code much more readable. I encourage and embody the pattern everywhere I go.
Exception: For mass find/replace or auto-linter changes, that’s all one MR, and is usually pair programmed so the other person can confirm that’s all I did, still easing cognitive load.
Kotlin, Python and Typescript, to name a few. A lot of functions you write are helper functions, wrapper functions, system functions, etc and all 3 languages support making things modules of interconnected concepts, extension functions, etc.
You can make code very readable this way - arguably more readable.
Have you ever had so many tests for a single class that you’ve broken the test class into a package and have a whole file / test class for each big method? Same idea! :)
Edit: this small things drive me crazy when I’m coding with LLM… which is basically all the time these days, but I also read all the code as well.
Easier said than done to be honest, especially if there are many people (and their agents) pushing code. It’s hard to keep up these days.
I think I would prefer code that is clear, understandable and simple even if it doesn’t compile and needs some straightforward polishing.
For personal projects, I can trust that I myself will be maintaining things so I still write things like it matters, but I do not extend the trust to others.
You can have all the prompts you want on top of this, but if you don't have this automated stuff running behind the scenes, you aren't serious about these issues.
Looking through some of these comments here, I see lots of people rewriting concrete rules in markdown willing to spend tokens on the hope AI won't miss it where an actual program won't.
Gradually over its recent booming years, software work went from one of several practical engineering refuges for curious tinkers and puzzle-addicts to a career path for financially ambitious bright people akin to finance, law, or medicine.
Many people carrying a "software engineer" title now never really enjoyed that part of the work at all but were suitably clever and responsible to accomplish what modest ends they were tasked to by their very generous employers. Mostly (but not entirely), those people are the ones most eager to have AI agents shield them from rigorous design and puzzle work and enable them to leverage their innate cleverness more lazily. They never really internalized the coding and engineering principles of the industry and so can't foresee what might be down the road for them with this technique, especially when they're surrounded by peers with the same mindset.
> AI isn’t always faster.
It is when coding was an extremely frustrating and high friction experience for you in the first place, as is the case for many who work among us now.
They're the one copy-pasting straight from StackOverflow, and if it does not work, will copy-paste something over it. When something works, they will copy-paste it all over the codebase regardless of context.
For those people, LLM tooling are the Second Coming. Because it helps them eliminate all the telltale signs of bad works they've been doing. For them, "Use the AI" is the new mantra because they can't imagine not needing to use it.
It’s not enough to read code. You don’t internalize it. You need to experience it painfully :)
Also agents frequently defensively wrap old code instead of questioning whether legacy paths should exist. You get this nested doll layering effect of extremely large amounts of defensive code.
+1 - this is also my experience. I also "race" the AI on some tasks, especially when it's simple and the AI is taking forever to return a result - so it even has a head start, and I often complete it faster or around the same time.
For some things maybe it is faster, but it isn't really returning a better result. It often turns into spaghetti, doing things I didn't ask it to do.
It is always faster if you don't care about quality and need to churn out code as fast as possible to close tickets.
At my workplace, there is more work to be done than there is engineers, and approximately 2 engineers per service. I can spin off multiple Claude Code instances on unrelated work, steering them occasionally, and then finally reviewing the output. After I have reviewed it, I post it for team review.
You're absolutely right that my depth of familiarity is lesser with this code, but we are absolutely shipping more as a result of increased parallelization.
The bottleneck now is typically reviews - both pre-push and team reviews.
Opus 4.8 has been the turning point though. I am not winning many races compared to Opus 4.8, especially if it is something more complex.
I think if everyone was using Opus 4.8 (or better) for every single task/question/etc, you would have much different overall sentiment. I don't think you would have many AI disbelievers left.
Really? I always thought that was the best part of programming. And now that I can direct an LLM to identify a specific pattern and rework it in a certain way, or to extract a function for a specific purpose and then use it where possible (with my review, of course), so much the better.
I agree with you about the joy of writing things directly, overall. But being able to get a few hundred lines of new approximately-what-I-wanted-to-type code (which I generally can read and fix much faster than I would have written it from scratch) definitely improves the experience, when my brain is racing ahead of my fingers. Certainly it gets me more motivated to actually start on a new feature. Similarly for all the not-exactly-exact find-and-replace tasks.
(I'm not a slow typist, but I slow myself down when I write the code, by thinking too much about details that won't be important until after the tests run.)
I’ve recently been given the task to work on a fully vibe-coded app. I reduced the code from 140k to 28k lines. This was luckily pre-mvp stage so no users. But if people get stuck at this stage, imagine what’s happening with real workload.
I’ve always been a solo dev, but I’m wondering how in big companies there is even so much to you can actually touch that you need an LLM to do that. Like I can’t delete refactor 120k lines in an established codebase. And one task to fix, takes review time and product time. Like are there now engineers that commit 1k+ lines a day of LLM produced output ?
Bugfixing often is just a bunch of lines, or a replacement of a service often isolated maye 150-200 lines, but I just might be just not experienced enough on these kind of codebases. I can’t grasp the notion of the LLM-multiplex and I don’t know where it’s going.
I have to say I have seen some good solo-dev projects popping up recently (games) that actually got profitable, but usually they were experienced in their discipline before.
One thing I usually keep having to point out directly is to remove all “progress tracking” code comments and make sure all comments are appropriate for long term maintenance in the code base. Claude tends to leave comments like “button click causes save now, no longer uses onBlur” when the code really never used onBlur, that was just a thing Claude wanted to do earlier in the same task/branch and I redirected it at some point.
I haven't used them so far but maybe these would work better than basic instructions for such cases.
The models will interpret this willynilly; but nonetheless, it's often a better than doing nothing.
The reason prompting it to review its own work for loose ends, record any new undocumented or noteworthy behavior, suggest changes to tests/processes to make it go more smoothly the next time, etc is that it’s prescriptive and process-oriented (and thus easily verifiable/done in-context) rather than descriptive and outcome oriented (which to do properly could require way more context than the model has, because it doesn’t know what it doesn’t know about your particular work, only what it’s seen so far).
Even promoting it to do these after-the-fact vs as an upfront requirement can have a big impact IMO. If you make “maintainability” part of the task before it’s seen the real work it will focus on general “best practices” crap rather than the real work, so either way if this is something you care about it doing you have to give it guidance for how you want it done.
If you were to review the logs of a model after the fact, you’d also not really save on input tokens unless you compressed the context or sharded it out, which can easily miss the small details that constitute the difference between “what actually happened” vs “how the LLM models this general class of problems” unless the first pass involves the entire context anyway. That said I do think there’s a lot of value in building some kind of pipeline for validating and aggregating these “learnings” across sessions.
I am following similar steps from this article https://www.lucasfcosta.com/blog/backpressure-is-all-you-nee...
I recently reacted angrily in a PR review comment after encountering one for the umpteenth time... that caught me off guard. I didn't know I was capable of that.
> Only write comments to explain the why when it is not obvious from the code (rationale, gotchas, constraints). Do not comment on the what — well-named code already says it. Do not comment on how a framework works.
It still keeps adding these bad comments. When I then ask it to review the comments based on my preferences it then deletes most of them or improves them.
Today I asked Claude why it disrespects my preference and it said that the surrounding code was like that and it followed that style. It suggested I add this line to my global CLAUDE.md file:
> The comment rule above beats the style of the surrounding code: neighboring files with what-style comments are not license to write more of them, and comments carried along when porting or copying code must be re-judged against the rule, not kept for consistency.
Let's see if that improves things.
I have a lot of CLAUDE.md rules to restrict this stuff, but realize the “encapsulation” language is something I’m missing.
It's as though the machine can't separate the chat and planning docs from the code itself and so they meld into each other. As though it can't fully grasp that the code will outlive the current session by years.
Anyway I find a checklist approach works well to sort this out. I don't consider looking at machine generated code until after a checklist covering all this sort of stuff has been applied. My checklist approach currently has about 50 items which I have the machine apply by splitting it up across about 20 subagents. Pretty silly but it seriously improves the first-pass quality vs only a few subagents. I find this checklist can effectively eliminate words like "genuine" and "landed" from the code too. Eliminates vague and made up terminology. Makes it less nauseating
Normally when I can't get claude to follow a prompt I try a lint hook, but it's tough to lint something that subjective.
so that can be useful information in some situations.
On the other hand, what a horrible out of date mess of comment that can turn out to be a little bit later. Taken as gospel by the next entity (human or llm) to massage that function.
Years ago I would often write comments first. I.e. start with describing the overall goals. Then break it down into routines and order of operations, all still in plain english. Once I was happy with that, I'd break up the comments with blocks of code. I guess this is sort of like "literate programming" though I was doing it long before I ever heard that term and I still have never read much about it. It's almost more like I was prompting myself towards the end goal. The downside of this approach is that the comments do end up more or less just explaining in english what the code is doing, so maybe aren't quite as useful to future maintainers.
The big problem is folks misunderstood it as documentation (arguably plain.tex should have also been the sourcecode for _The TeXbook_ and that it wasn't is a big part of this) --- it could be, but usually that's better as a separate text/chapter....
I've been trying to collect books on Literate Program/notable Literate Programs published as books:
https://www.goodreads.com/review/list/21394355-william-adams...
and I will note that my own programming took a quantum leap forward when I purchased and read:
https://www.goodreads.com/book/show/39996759-a-philosophy-of...
and applied its principles one chapter at a time to a project which I was able (w/ a bit of help) to get into Literate Programming form:
https://github.com/WillAdams/gcodepreview/blob/main/literati...
Use short names where they're contextually clear. Use long names where they're contextually weird/non-belonging. Use comments to explain the "whys" of your code.
To make up some hypothetical numbers in order to illustrate with math: if you ship bugfixes 10x faster but then have 11x more bugs you need to fix, that's not a net improvement. Even if it's only 5x more bugs, maybe you could reduce that to 2x if you changed how you worked to only be 8x as fast in a way that produced higher quality code. Similarly, maybe you could cut the time it needed to produce a new feature by 50% if your code were higher quality by moving 20% slower.
My point in all of this isn't that you literally need to work the same way you did before you had these tools, but that framing it as either "move fast and ignore the code" and "use the same exact heuristics you would in the pre-LLM days for what code is acceptable" is a false dichotomy. If you aren't thinking about how effectively you're using these tools and whether there are changes you could make to move even faster because "AI go brrr", I think you've lost the plot in the same way you probably think that other people in this thread have.
Manual edits literally aren’t possible. You can’t grok the code growth and the new patterns fast enough to be productive.
This does work. I’ve seen it in real products. Nobody has a real mental model of the code flows. But with enough money in Claude credits it doesn’t matter.
The spend to support this development model is something like $50/day/developer.
This work great until you reach a certain size, then good (or even "not bad") code is required otherwise the model spins its wheel trying to ensure the change is correct.
The way I've measured how good/bad the code is (for AI) is to have one "baseline fixed change" that I measure how long time it takes to implement. Always in the beginning (less than 10K LOC, as just some measurement), this baseline change will take 2-3 minutes. As you add more code, the same change starts to take 5-6 minutes, and once you hit 1 million LOC, it can take as long as 10 minutes, even though the change is the same.
It's when this baseline task starts to take longer time, that you need to update the design/architecture/layout/whatever, to better fit the task/domain, and to actually make it easy to maintain and still possible to add changes without spending 10 minutes. So its at this point you refactor, and once done, the baseline task will again be easy for the model to do.
So yeah, if all you do is smaller projects, then "shipping 10x as many features" is easy and doable, for the lifetime of the projects. But once the projects start to accumulate technical debt, the model will have a harder time making sure the changes are correct, and suddenly "shipping 2x as many features" is maybe doable, but you could still have had 10x if you just spend slightly more time on the actual design and architecture of the program.
The solution, as you say, is probably to break it down into isolated sub-components that are only aware of each other's APIs and nothing more.
I understand you're excited about the tool, but for the sake of earnest discussion here, maybe commenters like yourself can tone the hype down to plausibility?
Claims like this are just nonsense. It's not how product development works.
How do you even have so many bugs left to fix if the tool is so fast and productive? Surely, you didn't have a backlog of tens of thousands of bugs that you're still chewing through? And of course, the volume of new bugs much be minimal since the AI-composed additions introduce "no problems so far". If it works like you say, which we'll accept in good faith per HN guidelines, you must have exhausted your backlog long ago.
And if you've indeed exhausted your bug backlog long ago (incredible!), you're left to talking about shipping "10x as many features". Yet no product has a limitless capacity for features. Nobody would want to use software so bloated and churning that was gaining features at such a pace. And who is designing and specifying them so quickly anyway? If it works like you say, which we again accept in good faith, you must have stalled out on your feature list long ago.
If the AI indeed allows you to "[ship] 10x as many features and bugfixes", and we take what you say in good faith, then one of the following seems to be implied:
* you've fixed all your bugs and blew through your mature feature designs already, leaving your AI agents sitting idle for all but a few hours a week, while you're bottlenecked on feature design and your software product is bloated beyond imagination
* your coding productivity before AI was absolutely glacial by industry standards such that "10x" productivity for you is actually much closer to "0.5-2x" for others
Any insight into which of those it might be?
Anthropic themselves have admitted you don’t need much to poison LLMs¹. I can’t wait for us to discover the backdoors that are being introduced. I hope it happens soon so people get to their senses. Bah, what am I saying, when (not if) that happens, the response will just be to throw more LLMs at it.
> That's why you should say something like "Use constants instead of magic strings/numbers" rather than "Do not use magic strings/numbers".
Pro tip: This is good guidance for communicating with humans as well. Don't just block people but give them a path forward. (But don't force them down that path, because people really hate feeling like their agency is being impinged.)
What ever happened to communicating through code?
Agents can follow examples and infer patterns, and they can read commit history and diffs. Real-world commit logs for human-only projects are dominated by short commits (well, at least the ones where the humans are skilled, appreciate version control, care about the project, etc.) with thoughtful commit messages.
Maybe you can get an agent to identify and summarize such threads.
LLMs are a tool like any other - and like any tool there will be people who are better at utilising the tool to achieve an outcome than other users of the tool.
Those differences typically come down to a combination of experience, in-depth understanding of your subject/objective and an awareness of the strengths and weaknesses of the tool.
TBH the rise of LLMs reminds me a lot of the era when google search emerged. People's ability to utilise a search engine to achieve their aims was massively varied.
* Some people were absolutely useless - they struggled to use a search engine to find anything beyond the most basic of things.
* Others were ok - they'd be able to dig a little deeper, they knew a few of the tricks of the searching trade and they had a bit of an understanding of where to look/how to look for something obscure.
* Some were good - they knew all the key search constraints, they had a good understanding around how data is structured and what keywords and approaches to use when to get to the thing they need
* and some had 'google fu' - those people seemed to have a god-like ability to navigate the internet to find the most obscure things in a matter of seconds.
LLMs are different to search engines but their rise shares many similarities to them - they are changing the rules of the game in a monumental way; they have fundamentally reduced the barriers to entry in many fields.
The skills that made someone valuable in a pre LLM world do still matter, but IMO we're currently starting to go through a major change where the people who learn how to best utilise LLMs within their field will find themselves leapfrogging people with stronger skills who obstinately seek to avoid using the new tools becoming available to them.
People with more capital will always have an advantage. This is a large part why the american market fails to produce convincing competition.
If most people by your account are subpar programmers before AI, why do you believe they'll suddenly be better with AI?
Also, these comments always come off more than a bit anti-social. It's like hating your coworkers correlates strongly with AI-adoption.
The truth is that no one in management cares about your perfect code, they only care about the projects you deliver, the features you push out and the meeting KPIS.. Your customers don't care about your perfect abstractions either, they just want the button to work when it's clicked.
You can always argue about maintenance, but who is going to listen? By the time it's an issue, the business is throwing money at the problem to make it go away. Heck, even major security issues are a nothing burger.. I've lost count on the number of breaches big companies have had in the last decade..
Such is the plight of our industry.
I don't think most developers care about perfect code (if such thing exists). What they care about is maintainability, so that the project is delivered on time, features keep getting pushed and KPIS can be actually met. And that the customers have a working button.
Instead we got projects that keeps being dragged around (until it's put out of its misery), half baked features, and KPIs that are twisted until they're barely looks like what was stated at the beginning of the quarter. And the customer is complaining about another button breaking.
What I do is never ask for permission, because that's an engineering concern, not a product/management one. I just tidy the code I work on, one step at a time.
Agreed on the plan mode. I think it's personal choice and also situation-dependent. When I've already reviewed everything the agent has written and made changes along the way, I'm pretty confident a review is only going to throw up minor tweaks. When reviewing someone else's code or if you've got a load of changes you haven't reviewed yourself yet, plan mode is probably useful. I always think it's worth highlighting that plan mode isn't just 'don't make changes', it's a fundamentally different mode with a different prompt and objectives.
if what you're suggesting is true, then more important instructions like "Don't delete the production database" are a problem. I shouldn't need to consider how to phrase "Don't delete the production database" in a positive manner. Isn't the point of an AI agent that it understands my intent and I don't need to hold its hand? I'm not saying your suggestion is wrong, I just think that suggests a limit to what these tools should be used for if that's the case.
My guess though is that it probably ignores some of the positive instructions too, and the hardcore AI users mostly don't notice because they probably aren't reviewing the work.
These are still stochastic machines, guardrails must be inserted at the system level.
They are getting better every day about managing their own guardrails, so we will get there eventually.
There's a really major problem with the concept of human-in-the-loop though, which is just that humans are not built for that that kind of work. It's like all those tests against the TSA where they manage to sneak something through that should have been caught. But the problem is, a TSA agent sees probably like 1000 things they can ignore to the 1 thing they need to look at, and it's easy to just start rubber stamping things.
Don't think of a pink elephant in a tutu.
What did you just think about? =)
This has been true since the very first GPT release, any words you say to the model will nudge it towards that word - it doesn't matter if you have a negative in front of the word.
Form the guidance using positive words like "always use the development database at..." instead of "don't access the production database" ... Now it "knows" that the prod database exists and "thinks" about it when processing.
EDIT: reading the WardsWiki reference from that Wikipedia page, there's also the point made by early users of the term that smells are something you have to check out, but don't always mean something needs fixing - e.g. a bad smell may be a gas leak, or it may just be a rubbish bin.
If that's true, we're left with a question equivalent to "does everyone see the same red?". As far as I know, the pure version of that question cannot be answered because subjective experiences of sensation cannot be transferred. And at that point, I'd say the manner in which they're experienced differently is equivalent.
Looking into it, it seems like smell has the most potential for genetic variance: https://pmc.ncbi.nlm.nih.gov/articles/PMC3990440/
That said, I can’t find any direct research comparing perceived senses. So they (and now I) might have oversimplified.
Thanks for the link, looks interesting!
Often small technical changes like "making a service 5% faster" are worth millions for large companies. That's all implementation.
I could ban it from using git, but having access to git logs helps it do work so I don't want to go that far. I could probably ban the subcommand, but as a convenience I do like to be able to ask it to make a commit. (I'm not super attached to this, I frequently commit myself just to avoid these issues). It does tend to write good commit messages, so sometimes I ask it to generate the commit message and then just do the operation myself, which kind of sucks in the sense of feeling like reverse-centaur (Cory Doctorow term). (The reason I do that is sometimes it gets confused where if I greenlit one commit, it assumes all future commits are greenlit)
I guess the other option would be to not use "auto" mode, but god there's just no way I want to sit around and it "yes" 50x a session. I'd rather just sandbox it and nuke it if it does something too stupid.
This way the agent can read git logs (this is very useful) but cannot commit directly (I don't see any value in this).
The automatic coauthor line is also just a pet peeve. It’s a tool, not a person. I don’t say that a commit was coauthored by vim - why should Claude get special treatment?
Bash(“git * commit *”)
And for bonus points you can have the agent write you a Pre Bash hook. Tell it to make the hook look in the command for anything that’s a git commit. The vaguer your instructions here the better. Let it pull some crazy regex out of its hat. You’ll be impressed.
I want to be clear that I'm not saying this to discredit the people saying they can't get claude to stop making its own commits, I'm just adding data to the "wow this is inconsistent" collection.
That said, I've had lots of success using AI to learn, refactor and clean up codebases.
I notice another trend were a lot of AI naysayers haven't really spent a ton of time getting intimately familiar with AI.
I don't think that's really true, it's more your feels than anything. I've noticed this thing where everyone that's using AI think nobody else is using AI, or at least not the cool way they are doing it. It's typically nonsense.
I've been using AI for quite a while, personally and professionally, and I can already see the clusterfuck it's causing. I still don't let it work on my important personal projects, but for simple tools and websites, sure. At work we're told to use AI exclusively to code, and it's already causing problems - the AI just endlessly applies bandaid after bandaid and it's turned the code to spaghetti. Nobody even reads the code, the people above just tell us to approve it and ship it. I'm looking for a new job because I already know this isn't going to produce the long-term success they are expecting. All it is doing is making it more expensive to write software, without the knowledge of how the code works, and if you can't already understand how that looks in the future, well... I don't know what to tell you.
I've also seen a lot of comments that restate what the code already says and that's just noise, more work to keep in sync, an additional thing that can fail, and more cognitive load because you have to read twice the same thing (best case, if code and comment are still in sync). That's the result you risk when you think you must comment your code.
I appreciate the occasional comment that explains why something seems overly tricky or weird or not immediately intuitive. Once, I had left such a comment that saved myself years later from making a mistake. Of course, this should be kept at a minimal level. It leads to me liking clear code with few comments the most. (Some guidelines, even if it's not perfect, to limit complexity and spaghetti code help a lot).
Function, class, module documentation is also useful so you don't have to read the whole thing and you know what it's intended to provide (which is slightly different than simply what it provides, and this differences is important).
Clear code takes precedence over commented code if either of them could be used to solve the problem of communicating what's going on; comments are still useful in the cases where clear code isn't always enough. Of course, being able to discern whether there's a way to make the code cleaner to avoid needing a comment is an art rather than a science, and it's a skill that I think few people excel at (and judging by how so much LLM-generated code is littered with inane comments, one that's also pretty rare in agents)
Maybe these would work better for such cases.
re: automatic coauthoring -- you can turn that off, but I prefer to turn that on. Commit ownership is something that I want to make clear was not just a human, but a human + AI. Agentic coding does get special treatment from me, because the volition to make changes doesn't come from me. I want that to be clear in the history. The only time I do my own commits (without the coauthor line) is when I make some change myself, without a coding agent.
If you want LLMs to be your advantage you can train your own, that's completely valid.
Let's say you want to have a company that runs inference 5% faster, if everyone can do it your business model is worthless.