Clean Code is Good For AIs, Too
A few weeks into my AI journey, I was struck with one of those ideas that are either profound or profoundly stupid.
I was in the middle of tweaking my Elixir Skill.md file, codifying all the various things I’d learned about writing Elixir code that was easy to work with.
Then I stopped. Why was I optimizing my code for my readability, when the majority of work on that codebase would be done by an AI?
So I asked Claude: “All these features are designed to make the code good for humans. But you see code differently. So what would the style rules be if we were writing the code for you to work with?”
Claude went over the top. It designed a “scientific study” with a bunch of experiments. It would try writing the same code two or more ways, and send each off to an independent agent, each agent given the same task. It then had me grade how well they’d done. (It graded them too, and our scores were normally incredibly similar.)
After two days of this, it decided it had enough data to look for patterns. I got charts, tables, and R² values. Fortunately, I also got a summary. And the results were surprising.
I was expecting Claude to opt for special ways of encoding things; ways of triggering its recollection. I was thinking I might end up with code that looked like minified JavaScript.
Instead, what came back felt like what a teacher would write on the board in the first week of a programming class:
Use long, descriptive variable and function names.
Claude explained that this was way more efficient in both time and tokens that having to deduce the function of a variable or method by tracing the code.Use no more than five local variables (including parameters) in a scope.
This one surprised me. Claude said that as the number of variables went up, the search space increased polynomially. I experimented informally with this, and there’s definitely a nonlinear impact.Keep your functions really small.
Like four lines small. The fewer m=oaths there are through a function, and the less the function does, the easier it is to reason about. It turns, that makes it easier to find some encoding of what it actually does.Keep your modules small.
Claude suggested about 700 lines is a good limit. It also said that the modules should be tightly focussed on doing one thing. Again, this helps it encode what a module does, and allows it to be efficient when deciding what to load into the context.Remove coupling.
Every time there’s coupling between things, Claude had to go down the rabbit hole. Context sizes shot up, response times dropped, and “mistakes got made.”Write good comments.
This one really surprised me. I’ve been advocating a no-comment style for decades now, and here’s a machine daring the challenge me.
But Claude said it likes to drop comments into code for at least two reasons.
First, if each module has a short description at the top, it can read just that to decide if it needs to read the rest. It is also a place to explain any things to look out for, problems it had is the past, and so on.
Second, it said it found it useful to add comments to some functions. Again, these weren’t the “this function does X. Parameter 1 is…” type of comment: that’s what good naming is for. Instead it wants comments that describe the unusual stuff,or that act as mileposts so it can quickly navigate.
Let’s Recap
Use long, descriptive variable and function names.
Use no more than five local variables (including parameters) in a scope.
Keep your functions really small.
Keep your modules small.
Remove coupling.
Write good comments.
How tediously predictable. The same old rules.
But there’s treasure in there.
On the surface, AIs and people have radically different ways of approaching coding. People “think.” Computers “pattern match.” You’d expect that the rules that work for LLMs would be totally different to the ones that work for us. But Claude came up with a set of motherhood-and-apple-pie rules on it’s own, not by reading The Pragmatic Programmer or Clean Code, but by running experiments to see what worked.
The treasure part comes from thinking about why this parallel evolution occurred.
It’s All About Attention
This is a theory. I haven’t tested it. I don’t really know how to test it. But it makes sense to me.
Why do we need coding principles? I think it’s because we do not have the ability to look at a 100,000 line piece of code and understand it all at one time. We cannot imagine all the paths through it, all the functions it performs, all the errors in contains, because our brains just aren’t built to do that. We excel at spotting lions in the undergrowth, but we suck at maintaining the relationships between 100 classes visualized while we track down a bug.
So we came up with principles; guardrails that make the code easier for our brains to deal with.
Give things good names, so we know the intent without wasting brain space tracing through code. Keep a small number of variables in scope, because most people top out at 6-8 things in short term memory. Keep functions small, because then you can grok them and abstract them into a single underlying concept. Keep modules small and homogenous so we know where to find things. Reduce coupling, because we’ll never remember to trace through all the possible paths we risk breaking with a change. And write meaningful comments as mileposts to help us in the future.
And, guess what. It turns out LLMs have pretty much the same restrictions. They can only juggle so many things at a time. They have limited context and limited attention, and when things get too big then tend to wander around and make things up.
Maybe the mechanics of how they work is different to the way our brains work (although I suspect in future there may turn out to be more similarities than differences). But, even if the two approaches are radically different, they are both subject to the same limitations of attention and context, and they both benefit from the same principles.
(Oh, and Claude says spaces, not tabs.)
(No, it didn’t care…)



