The Hard Part Was Never the Code. It Was the Theory.
Senko Rašić is right that “code was never the hard part” insults programmers. Peter Naur explained what it actually gets wrong — in 1985.
TL;DR: Senko Rašić’s post calling “code was never the hard part” an insult to programmers hit the front page and drew a 400-plus-comment argument, and both sides of that argument were right, which is the tell that everyone was measuring the wrong thing. Peter Naur settled it in 1985: programming is building a theory of how some part of the world is handled by a program, and the source code is a lossy artifact of that theory, not the theory itself. That reframe explains why the phrase feels obviously true to some engineers and obviously insulting to others — they hold different amounts of theory. It also explains the thing 2026 actually broke. An agent produces text without theory. Naur’s word for a program whose theory is gone is dead, and the new failure mode is a program that was born that way.
A phrase that started a fight
You have heard some version of it, probably this month. LLMs may be good at coding, but code was never the hard part. Sometimes it’s dressed up — the hard part is figuring out what to build, the hard part is the requirements, the hard part is the people.
Senko Rašić pushed back on it hard, and his framing is the reason the post traveled: he read the phrase as a status claim rather than a technical one. “I believe that’s a gross insult to all programmers everywhere.” His arguments are the obvious ones and they land: if coding is easy, why were programmers in high demand at high salaries for years? Why is so much software so buggy? Why do Clean Code and SICP exist? And the turn back on the other camp — if deciding what to build is the genuinely hard part, why do so many product managers seem clueless? He refuses the craft-versus-customer binary entirely: ¿Por qué no los dos? He ends where I’d end. Don’t outsource your understanding, judgment, empathy, and taste to a model.
Then the Hacker News thread did something more interesting than agree or disagree. It split, cleanly, and both halves were credible.
One camp said the phrase is being read too literally. Nobody means coding is easy — they mean coding was never the bottleneck. The thing that killed their last three projects was misalignment, a stakeholder who couldn’t prioritize, a requirement that changed after the build. Enterprise work in particular is mostly validation, SQL, and scheduling; the difficulty is pinning down rigid requirements with the edge cases considered, not expressing them.
The other camp brought receipts in the opposite direction. Products die of bad code all the time. Memory leaks that make a paid tool unusable. Performance collapse that leaks users quietly for a year. Feature velocity that decays until nothing ships. That camp read “code was never the hard part” as a claim written by people who have never shipped anything where the code could actually lose.
Both descriptions are accurate. Not “both have a point” in the diplomatic sense — both are literally, verifiably true about the work the person describing them does. So the disagreement isn’t about the profession. It’s about something the phrase can’t see.
Naur’s answer, from 1985
Peter Naur — the N in BNF — wrote a paper called “Programming as Theory Building” in Microprocessing and Microprogramming in 1985. Alistair Cockburn thought enough of it to reprint the whole thing as an appendix in Agile Software Development. It is nine pages, it is forty-one years old, and it is a better description of what just happened to our jobs than anything published this year.
Naur’s claim is that we have the object of the work wrong. We think we produce programs. He argues we produce theories:
programming properly should be regarded as an activity by which the programmers form or achieve a certain kind of insight, a theory, of the matters at hand
He is using “theory” in Gilbert Ryle’s sense — not a formal model, but the thing a person has when they can do something and explain, justify, and answer questions about the doing. You have a theory of your commute. You have a theory of how to arrange furniture in a room. It’s tacit, it’s held by a person, and it is not a document.
And then the line that does the damage:
The theory built by the programmers has primacy over such other products as program texts, user documentation, and additional documentation such as specifications.
Primacy. The code is downstream. Documentation, in Naur’s words, is “an auxiliary, secondary product.” The real artifact is in someone’s head, and everything on disk is a partial, lossy projection of it.
He gives three tests for whether someone actually holds the theory of a program. They’re worth memorizing, because they’re the most useful review checklist I’ve encountered and they predate every process framework you’ve been made to sit through:
- They can explain how the solution relates to the affairs of the world it helps handle — which real-world aspect each part of the program text is matched to, and conversely, where in the text any given real-world activity lives.
- They can explain why each part of the program is what it is — support the code with a justification, which finally rests on their own direct, intuitive judgment about what was relevant here.
- They can respond constructively to a demand for modification — take a change the world now requires and know where it goes and what shape it should take.
Notice what fails all three. A person who can read the code perfectly. A person with complete documentation. A person who wrote it three years ago and has forgotten.
Naur’s two case studies are about you
The paper’s evidence is two field reports, and if you’ve inherited a codebase you already know how they end.
Case 1. Group A builds a compiler for language L. Group B needs a compiler for L + M, a modest extension. Reasonable plan: start from A’s compiler. B gets full documentation, annotated program text, extensive written design discussion, and personal advice from group A. Genuinely good conditions — better than yours.
It doesn’t work the way you’d expect. In several major cases, B proposes changes that make no use of facilities already inherent in the compiler’s structure — facilities that were discussed at length in the documentation B had read. B’s instinct is to bolt on patches, and Naur says those patches would have destroyed the design’s power and simplicity. A’s members spot it instantly and propose a simple fix framed entirely inside the existing structure. Naur:
This is an example of how the full program text and additional documentation is insufficient in conveying to even the highly motivated group B the deeper insight into the design, that theory which is immediately present to the members of group A.
The postscript is worse. Ten years on, after passing through more hands with no contact with group A, the compiler still had its original powerful structure visible — “made entirely ineffective by amorphous additions of many different kinds.”
Case 2. A 200,000-line real-time industrial monitoring system, adapted per installation. The people who diagnose faults on it were involved from design and have worked on it full time for years. They diagnose almost exclusively from their ready knowledge of the system plus the annotated program text — and here’s the part that should stop you — they are unable to conceive of any kind of additional documentation that would be useful to them. Meanwhile the outside groups who have full documentation and full guidance keep filing problems that trace back to inadequate understanding, which the insiders clear up in a conversation.
Not “the docs were bad.” The insiders couldn’t imagine a document that would help, because what they have isn’t the kind of thing a document holds.
This is what the Hacker News thread was actually arguing about
Now the split makes sense, and it isn’t a disagreement about programming. It’s two groups honestly reporting different theory-to-text ratios.
If you build forms over a database — most enterprise work, and I’m not being snide, I’ve done years of it — the theory is genuinely thin. The requirements are most of the theory. Once you know the rules, the mapping to code is close to mechanical, and any competent person can pick it up from the text. For that work, “code was never the hard part” is just true. Getting eleven stakeholders to agree what the rule is was the hard part, and it took four months.
If you build something with real invariants — a scheduler, a distributed state machine, a query planner, anything with a performance budget or a consistency guarantee — the theory is thick, most of it never got written down anywhere, and the code is its only durable trace. Tell that person coding is the easy part and you’ve told them the part of their job that took a decade to develop doesn’t exist. That’s the insult Senko named, and he’s right to name it.
“Code was never the hard part” is not a claim about the profession. It’s an accurate report of how much theory your own work carries.
Which is why the argument won’t resolve. Both sides are describing their jobs correctly and hearing a claim about everyone’s.
The part Senko didn’t take, and 1985 did
Here’s where this stops being a semantics fight and starts costing money.
Senko’s conclusion is don’t outsource your judgment to AI. Correct, and too general to act on. Naur gives you the mechanism, in a sentence he wrote when the state of the art was punched-card-adjacent:
if viewed merely as a change of the program text and of the external behaviour of the execution, a given desired modification may usually be realized in many different ways, all correct. At the same time, if viewed in relation to the theory of the program these ways may look very different, some of them perhaps conforming to that theory or extending it in a natural way, while others will be wholly foreign
Read that again with an agent in mind, because it is the cleanest description of the actual problem I have found anywhere.
A coding agent samples from all correct. That set is large. Every member of it passes your tests, satisfies the ticket, and survives review-for-style. Only a fraction of that set conforms to the theory of your program, and nothing in the model’s inputs distinguishes them, because the distinguishing information was never in the repository. It was in group A’s heads.
This is the same mechanism I’ve written about from the training side — your agent has no reason to write good code because maintainability was never in its reward signal. Naur gets you there from the other direction, and I find his version more useful: it isn’t that the model is careless. It’s that the criterion is genuinely absent from the artifact. He says so explicitly — the judgment “cannot be reduced to any limited set of criteria or rules.” You cannot prompt your way to it, because if it could be written as a rule, it would already be in the docs, and case 1 says the docs weren’t enough.
And Naur names the failure mode this produces. He calls it decay: “modifications made by programmers without a proper grasp of the underlying theory.” That’s group B’s amorphous additions. He wrote programmers, and I’m not going to pretend he was predicting anything — but swap that one word and the sentence describes a merged agent diff on a system nobody currently understands, which is the same failure arriving by a faster route.
Born dead
Naur’s conclusion is the one people quote, and the reason it stings is that he means it literally:
The death of a program happens when the programmer team possessing its theory is dissolved.
A dead program keeps running. It keeps producing useful results, and it keeps making money — death is not an outage. Death becomes visible only when a demand for modification arrives and cannot be answered intelligently. Everyone reading this has worked on a dead program. It’s the one where a two-line change takes three weeks and the estimate is a coin flip.
Revival, Naur says, is not reading the code and not restoring the docs. It’s a new team rebuilding the theory. He doesn’t put a price on that, and I won’t invent one — but nothing in his account suggests it’s cheaper than the first build, and case 1 is a group with documentation, source, and the original authors on call still failing to get there.
Here’s the 2026 version, and it’s new. Naur assumed every program was alive once — a team held the theory, then dispersed. An agent-built system can arrive without ever having been alive. Nobody dissolved. Nobody left. The theory was never constructed, because construction is what happens in a person while they work the problem, and the working was delegated.
That inverts the economics everyone is currently quoting at you. Cheap to produce, expensive from then on, permanently — because you skipped the step that makes future changes cheap, and the bill starts arriving at change #2. It’s why taking over a vibe-coded app feels different from taking over a bad human codebase. A bad human codebase has a theory you can partially recover by archaeology, since every insane codebase was a sane response to constraints you can’t see. The other kind has no theory to recover. There’s nothing behind the text.
What this changes on Monday
I use agents for most of my working day and none of this is an argument against them. It’s an argument about which step you are not allowed to skip.
Review for why, not for style. Style review has lost most of its value in my own work — the output comes back tidier than what I’d have typed by hand, and editing it is cheap either way. Naur’s test 2 is the question that still has teeth: why is this part what it is? Ask it of the diff. If the answer is “the agent chose it” or “it passes,” you’ve reviewed the text and not the change. This is the same bar as verification, one level up: verification asks whether it does what we said; theory asks whether it does it in a way this system can survive.
Use the modification test as your real readiness gate. Naur’s test 3 is a better signal than any interview or checklist. Hand someone a change request against a surface they claim to own, and watch whether they respond constructively or start pattern-matching. That’s the difference between someone who read the code and someone who holds the program.
Keep one accountable human per surface, and mean it. Not a name in a CODEOWNERS file. A person who can pass all three of Naur’s tests on that surface, who is given enough time on it to have built the theory, and who reviews what the agents do there. This is the real constraint on how many agents one person can supervise — not tokens, not review throughput, but how many programs one human can hold a theory of. It’s fewer than you want it to be.
Record constraints, not decisions. Documentation is a secondary product and it will never carry the theory, but it can carry the inputs — which is what group B was missing. “Single service because two people and a March deadline.” “This queue is FIFO because the client’s reconciliation assumes it.” Constraints are the recoverable part.
Stop treating prompt transcripts as documentation. A session log is a secondary product of a secondary product. It records what was asked and what was emitted. It does not record why this shape and not the other four correct ones, because in most cases nobody decided that — the sampler did.
Let juniors build theory on purpose. The pile of unglamorous work was never busywork; it was the mechanism by which theory got built, which is why removing it quietly stops producing senior engineers. Onboarding is the same problem in miniature, which is why onboarding into an agent-heavy codebase needs a different shape than the one you used in 2019.
And the honest limit, because I’d be selling you something otherwise: you cannot fully write a theory down. That’s Naur’s entire point and no practice above defeats it. Every one of these is damage control on an unavoidable loss. The only real defense is continuity of people who have spent real time on the real problem — which makes headcount decisions, retention, and how much you delegate to agents the same decision, whether or not anyone frames it that way in the meeting.
Where I land
Senko is right that the phrase is an insult, and I think he’s right for a reason he doesn’t quite state. It isn’t insulting because code is difficult. It’s insulting because it mistakes the artifact for the work. Typing was never the hard part — no serious person ever claimed it was. Building the theory was, it still is, and it is the one part of this job that has not gotten one bit cheaper since 1985.
What changed is that we can now buy the artifact without doing the work. That’s a real capability and I use it daily. But you’re buying the projection, not the thing it was projected from, and the difference doesn’t show up in the demo. It shows up at change #2, when someone asks why it’s built this way and every person in the room, including the one who shipped it, has to guess.
If you’ve got a system nobody can confidently answer questions about anymore — inherited, agent-built, or just old — that’s a theory problem before it’s a refactoring problem, and the sequencing matters. Happy to talk it through.