High-level programming languages exist so that an engineer can communicate, not to the computer, but to engineers who follow them. Computers are happy processing machine instructions, or assembly if we want to be generous. Every abstraction above that line — named variables, functions, modules, types, patterns, comments, documentation — exists not because the machine requires it, but because the humans reading and writing the code do. We state this as a readability principle, usually in the context of writing clear names and keeping functions short. This is a point that, in a rush to deliver features, engineers often forget, and non-engineers often don’t understand. But I think the idea reaches further than most people take it, and the failure mode when you miss it has never been more consequential than it is right now, in the age of AI.
What It Actually Means#
“Code is for people” doesn’t just mean variable names should be pronounceable. It means that the primary reader of any piece of code is a human being — the engineer who wrote it six months ago and has since forgotten the reasoning, the new hire trying to understand the codebase, the person doing code review, the engineer who gets paged at 2am and has to navigate an unfamiliar section under pressure. Machines execute the code; people have to understand it, modify it, debug it, and reason about its behavior in situations the original author didn’t anticipate.
Readability is the obvious expression of this. Naming things well, keeping functions small enough to hold in working memory, making the structure of the code reflect the structure of the problem — these are not aesthetics. They are the mechanism by which knowledge transfers from the engineer who wrote the code to every engineer who will ever work in it. When you write obtuse code, you are not just making your own work harder to understand. You are destroying knowledge that you had and that nobody else has yet acquired.
Least surprise is a deeper expression of the same idea. A function named process that also writes to a database, sends a notification, and mutates global state surprises the reader. The surprise is a source of bugs — the reader builds a mental model from the name and the context, and then acts on that model, and when the model is wrong, behavior is wrong. But the cost doesn’t stop at bugs. Every engineer who needs to modify that code has to first excavate its hidden side effects before they can touch it safely; that is time that was not spent building. And for a new team member, a codebase full of surprises is a codebase that cannot be onboarded into — each unfamiliar area requires the same slow, careful excavation, because nothing can be trusted to behave the way it appears. Code that behaves consistently with how it is named and structured allows the reader to build an accurate mental model and act on it with confidence. The difference in team productivity, compounded over months and across contributors, is not marginal.
The same principle applies at the architectural level, and this is where it becomes most consequential for how a team scales. A codebase where every service, every module, every integration follows the same patterns is one where a developer can navigate unfamiliar territory and still reason correctly — because they have learned the pattern once, and it applies everywhere. The cognitive load of onboarding into a new part of the system drops sharply when the idioms are consistent. Conversely, a codebase where each team or module has evolved its own conventions means every unfamiliar area is a fresh learning problem. The developer cannot transfer what they know; they have to start over. At scale, that cost compounds.
The structural dimension extends this further. Early in a project I built, the service separation was logical — documented in the architecture notes, understood by the team, enforced by convention. It didn’t hold. The production load was high enough that cutting across the lines was always the fastest path, and under pressure, that’s what happened. In the redesign, I made the separation physical: each integration route ran in its own container. Violating the boundary now produced a hard failure rather than a soft one. The architecture became legible to anyone who deployed it, because it was enforced by structure rather than recorded in documentation, increasing the cognitive load for anyone trying to modify it. Consistent patterns communicate intent; structural enforcement makes that intent impossible to misread. Together they mean that the people working in the system can build an accurate mental model and trust that it will hold.
AI Changes the Calculus#
I have been using AI coding tools heavily for long enough to have a view on what they do to this problem, and I find that most of the commentary is looking in the wrong direction. The usual concern is hallucination — the AI generating code that is wrong. That is a real issue and a manageable one; wrong code tends to fail in visible ways. The deeper issue is something else.
AI removes the friction from producing code. For an engineer who has not internalized the “code is for people” concept, this is a power tool applied to a pre-existing problem. They could already write code that worked but wasn’t readable; now they can write it at an order of magnitude greater speed and scale. They can generate a thousand-line method in seconds. They can produce code that passes every test, compiles cleanly, ships to production, and that no human — sometimes, even the AI that generated it — can coherently explain. The machine doesn’t care. The next engineer who has to understand it will.
This is not hypothetical. Every generation of tooling that lowers the cost of writing code — IDEs with autocomplete, snippet libraries, frameworks with heavy scaffolding — has made it easier to produce working but incomprehensible code. AI is that pattern applied at a qualitatively different scale. The barrier to generating a large, internally consistent, syntactically correct, semantically opaque codebase has dropped to nearly zero. What has not dropped is the cost that codebase will impose on the people who inherit it.
I have seen this play out directly. Programs built quickly under AI assistance, shipping features at a pace that felt like a win, arriving at the maintenance phase requiring AI assistance just to navigate them — because no human can hold their structure in mind without it. The next step is refactoring: going back through working code to make it comprehensible, which is expensive work that produces no new features and that could have been avoided if the principle had been applied from the start. The velocity was real; so is the bill.
The organizational signal is beginning to register more broadly. A number of companies that reduced their engineering headcount on the assumption that AI could absorb the difference are now re-hiring. The tasks AI handles well — generating new code against a clear specification — turn out to be a narrower slice of the engineering function than the reduction in headcount assumed. Reading an existing codebase, reasoning about its behavior under conditions the original authors didn’t anticipate, deciding what to change and what to leave alone, making changes that hold up over time — these remain human work, and they become harder in proportion to how little the original code was written for people.
The flip side is worth stating clearly, because it is the more interesting half of the picture. AI is exceptionally good at following patterns. Given a well-established set of architectural conventions — consistent naming, consistent structure, consistent idioms across the codebase — an AI coding tool will reproduce those patterns with a fidelity and consistency that is difficult for a team of humans to sustain over time and across contributors. The patterns become guardrails, and the AI stays inside them. An engineer who has done the work of establishing what the codebase’s idioms are, and who knows how to communicate those idioms as context, gets a tool that actively reinforces the architecture rather than eroding it. The investment in coherent patterns pays a compounding return when AI is generating code inside them.
This is why the principle matters more now, not less. The engineer who has internalized that code is for people brings that understanding to every interaction with an AI tool — it shapes what they specify, what patterns they establish as context, and what output they accept. The one who hasn’t brings that gap instead, and the tool amplifies it faithfully.
The Abstraction Layer Doesn’t Disappear#
AI has not changed what code is for. It has changed what it costs to ignore that.
Consider where this trajectory leads. If AI can generate and modify code reliably enough, the argument follows that we could skip the human-readable layer entirely and go straight to bytecode — AI reads it, AI modifies it, humans need never engage with it directly. The problem is that humans still have to specify what they want, audit what was done, understand why something is broken, and decide what to change. That intent has to live somewhere in a form people can reason about. If it isn’t in the code, it has to be in the specification — and any specification language precise enough to drive correct code generation without ambiguity is, by definition, a programming language. A higher-level one, but a language nonetheless, subject to the same principles of readability, consistency, and least surprise. Natural language fails here for the same reason it fails in law: it is too imprecise, and imprecision at the specification level requires a judge to resolve. AI narrows that gap considerably — more than any previous tool has — but for anything complex enough to matter, where ambiguity in the specification produces subtly wrong behavior, the judge is still required. The precision gap has shrunk; it has not closed.