Coding Is Dead, Long Live Programming

Summary

In this post, we argue that the role of a coder now belongs to a coding agent, but that the distinct role of a programmer defines the software engineer's role in interacting with the agent to produce computer programs. At the heart of this are, I think, answers to questions we have about software maintenance and the education of junior engineers.

Coding is Dead

Commentators in the tech industry have recently declared that “coding is dead.” The triggers for this statement are:

Of course, all statements like “coding is dead” are hyperbolic, but it would be foolish to refute that in 2026 and beyond: agents will write the majority of code.

If coding is dead or will be dead by the early 2030s, what will software engineers do?

Programming is not Coding

The terms programming and coding can seem interchangeable, but historically, they are not the same. They refer to different activities, but for many years we have performed them together, making the distinction unclear.

Following Yourdon and Constantine in Structured Design, we assert that software engineering has the following roles:

In the latter two roles, the original distinction between programmer and coder stems from an era when code was often punched out on cards rather than encoded in a high-level language. A programmer handed the coder a coding specification that defined the program to be written:

“Each component subprogram is coded using the coding specifications. Ideally, this phase would be a simple mechanical translation; actually, detailed coding uncovers inconsistencies that require revisions in the coding specifications (and occasionally in the operational specifications).” Bennington, Herbert; Production of Large Computer Programs.

Once, in larger organizations, different people performed the roles of programmer and coder. The programmer specified what code was needed and handed the resulting coding specification to the coder, who then authored the code that was to be loaded into the computer.

With the introduction of high-level languages, it became easier for developers to perform both the programming and coding roles simultaneously. The compiler did much of the work that the coder had done before, so the program specification could be completed simultaneously with coding. Later, the RAD/agile movements leveraged the ease with which programs could be specified as code was written, speeding up software development by collapsing many steps into a single process and shifting quality left via Test-Driven Development.

It is because of high-level languages and RAD/agile practices that many people use “programming” and “coding” interchangeably. But they are not the same.

Coding or Programming: What is the Difference?

You can reason about this difference easily. Once you learn multiple programming languages, you come to appreciate that all of them have variations of the same concepts: control structures, variables, etc., that only differ by their syntax. By contrast, data structures and algorithms don't depend on specific language syntax. Object-Oriented models of your domain can be developed using CRC cards or UML, without writing code. We think of these concepts as existing outside the syntax and the code, at the level of the program. You can reason about a program in a conversation, or on a whiteboard, without needing to use a specific syntax.

When writing code, you probably used to look up what you needed in documentation or examples and translate it to your domain. These statements were code and reusable as such, once fitted to the logic of your program.

We often overestimate the importance of coding in this merger of roles. It is likely that you spend more of your time as a programmer debating which frameworks and libraries you want to use, and what classes and interfaces form part of your model, than you do over whether you should use a for or while loop.

Knowing what syntax to use in a particular language is the role of a Coder; knowing what the code should do is the role of the Programmer.

Alex Booker has a good description of the difference between coding and programming in his video Coding or Programming: What Is the Difference?

XP: Elaboration, CRC Cards, Programming & Coding

Armed with this understanding, it may be helpful to identify when a developer is in a programmer or coder role. Consider the roles we took in an XP-like software development life cycle (SDLC).

The developer roles are: analyst, designer, programmer, coder.

Notice how the coder role is a relatively small part once we break out programmer from it. Because any team member may switch in and out of roles, we overlook how often we are in the programmer role rather than the coder role (or the analyst or designerrole). This tallies with the observation that coding is a small part of what software engineers do and aligns with the reason: many activities are, in fact, programming rather than coding. Your perception of how little of your job is coding is not incorrect, but you perhaps don't appreciate that analysis, design, and programming are engineering activities.

The Middle Loop

In their book Vibe Coding, Steven Yegge and Gene Kim discuss the Middle Loop, the construction of the context the agent uses when carrying out tasks. We often call this specification-driven development, but it bears less resemblance to older document-heavy approaches to software development, and more to the activities of the classic XP loop, with agents in the coding role.

Whilst this is an evolving practice, we can think about a common loop for working with agents like this:

The Middle Loop

The Inner Loop

The Inner Loop The Middle Loop

The developer roles are: analyst, designer, programmer. The agent roles: researcher, coder

The loop is fundamentally the same as XP, but the developer no longer performs the coder role and may be assisted by a researcher role:

The researcher role helps us utilize existing knowledge and patterns more efficiently because the programmer can delegate research that they used to do to the agent.

We didn't previously break out this role; it was part of being a programmer, and it is what we were doing when we visited Stack Overflow, documentation, or pulled candidate objects from the requirements based on their responsibilities.

We might still need to do some of this by reading, but the agent can help with heavy lifting, so we add a new role for that. However, decision-making, based on discernment and taste, lies with the programmer role.

The key, though, is that much is familiar from our first loop. Even with an agent's help, many roles still fall to the human-in-the-loop.

High-Level Direction Judgement and Taste

“It’s not perfect, it needs high-level direction, judgement, taste, oversight, iteration and hints and ideas. It works a lot better in some scenarios than others (e.g. especially for tasks that are well-specified and where you can verify/test functionality). The key is to build intuition to decompose the task just right to hand off the parts that work and help out around the edges.” Andrej Karpathy, x.com

Judgement, taste, etc., are the programmer's responsibilities. The agent is good at coding, but it needs help deciding what to write. That is why we still need the programmer role and don't simply delegate that to the agent. Maybe one day it will do that role too, but that day is not today.

Watch the Loop

Why suggest that the programmer/coder split is like pair/mob programming, where one partner watches the other? Because the agent needs to be observed.

“It's important to watch the loop as that is where your personal development and learning will come from. When you see a failure domain, put on your engineering hat and resolve the problem so it never happens again.

In practice this means doing the loop manually via prompting or via automation with a pause that involves having to press CTRL+C to progress onto the next task. This is still ralphing as ralph is about getting the most out how the underlying models work through context engineering and that pattern is GENERIC and can be used for ALL TASKS.” Geoffrey Huntley, Everything is a Ralph Loop

Now, some argue that it is inefficient. Do you need to review the agent's output? It's just coding. Just use a linter and check that it passes the tests.

Watching the loop confirms the theory behind what you are doing. What is the theory? That's the bit you must come away from the development loop with. It is how you will be able to reason about and maintain the software.

Programming as Theory Building

Peter Naur's essay, “Programming as Theory Building” states that the most important part of programming is theory building: “ On the Theory Building View of programming the theory built by the programmers has primacy over such other products as program texts, user documentation, and additional documentation such as specifications.” The theory, the knowledge built up by the programmer in crafting the program, is important for three reasons:

  1. The programmer can explain how the program maps to the problem domain: “the programmer must be able to explain, for each part of the program text and for each of its overall structural characteristics, what aspect or activity of the world is matched by it.”
  2. The programmer can explain the decisions that led to the choices expressed in the program text and documentation.
  3. The programmer is able to understand how to modify the program to support change: “Designing how a modification is best incorporated into an established program depends on the perception of the similarity of the new demand with the operational facilities already built into the program.”

It is the latter point that is key. In large codebases, our ability to maintain a program depends on our possessing the theory of the program. The theory lets you modify the program successfully because you understand how the program's theory should be adjusted to meet the new requirement.

“The point is that the kind of similarity that has to be recognized is accessible to the human beings who possess the theory of the program, although entirely outside the reach of what can be determined by rules, since even the criteria on which to judge it cannot be formulated.”

The risk is that modifications made to a program, without understanding the theory, lead to decay. Most of us have experienced decay when modifications accrete as new additions that don't fit the theory of the remainder of the program. Eventually, the program becomes incoherent, and we are forced to rewrite.

“The death of a program happens when the programmer team possessing its theory is dissolved. A dead program may continue to be used for execution in a computer and to produce useful results. The actual state of death becomes visible when demands for modifications of the program cannot be intelligently answered. Revival of a program is the rebuilding of its theory by a new programmer team.”

Theory is the key to maintenance. If you don't have the theory, then you will struggle to maintain the software, because you will struggle with the programmer role.

“A very important consequence of the Theory Building View is that program revival, that is, reestablishing the theory of a program merely from the documentation, is strictly impossible.”

Code Reviews and Theory Building

In teams that don't use pair programming, a common compensation is to do code reviews. The purpose of both pair programming and code review extends beyond quality review; it is about sharing the theory of the program.

Often, teams struggle with reviews as flashpoints or have arguments in pairing because they fail to do enough theory-building when they pull the story from the backlog, a process we call elaboration or alignment. A simple way for many teams to improve code reviews is to avoid doing design there but to shift it left into elaboration when the story is pulled from the backlog. This shifts the theory building to an earlier step. The review then just confirms that the code matches the theory. Conflict arises when the theory is unclear during pairing or review.

Pair programming and code reviews spread the theory through the team. Junior developers often don't have the same facility with theory-building that senior developers do. Pairing, or code reviews, allow developers to exchange the theory through conversation and reach alignment.

These exchanges work best when they don't focus on the code but on the theory of the program.

The danger is that a new developer on the team, who has yet to build the theory for the program, in conversation with other developers, correctly specifies the program without the theory. Without the theory, their programming specification is likely to be wrong, and the resulting code will be wrong.

InnerSource, OpenSource, and Theory Building

OpenSource, or InnerSource projects, encounter this need to help contributors acquire theory all the time. Often, Open-source projects identify tasks that require less theory to help new contributors get started. Once contributors establish a track record of contributions, maintainers begin to invest the time in helping them understand the theory.

LLMs and Theory Building

The LLM cannot be used in the analyst, designer, or programmer roles, only as a researcher to support, because it cannot capture the theory of the program as expressed by Naur.

As such, an agent struggles to know the theory of the program and fulfill the programmer role. This is why the design is a conversation with the agent. This is why the loop must be observed. Acting as the programmer, you are building the theory of the program.

This, then, is the danger of handing the programmer role to the agent, rather than using it as a researcher. You no longer have the theory of the program, which will lead to the software decaying under modification.

This is why, when working with an agent, it is important to observe the loop, because we need to build the theory of the program. We may choose to work either as a pair programmer, reviewing the test and implementation as they are developed, or as a code reviewer, looking at the PR from the agent. Again, both will be easier if we shift left and put more effort into the design of our program, building a clear theory.

Agents' speed at writing code does mean that the old conflict, in which design insights that update our theory are much more easily applied, persists. With hand-written code, we may resist the amount of work a change to the theory represents for an insight. But with an agent, we just ask the agent to update the design, change the tasks, and add tasks to modify existing code.

Of course, updating the design for a new theory necessitates having tests that confirm the behavior remains the same when we change the theory. This is why we practice TDD. (This is also why we do TDD against behaviors and not details – because we want to be able to revise our theory, not make it rigid).

A Programming Episode with Brighter

As I am writing this, I am working with an agent to pick up a backlog item for Brighter. Configuration for Brighter can be hard. Even for simple CQRS, you need to ensure that pipelines are consistently sync or async, handlers are registered, and when you use messaging middleware, message mappers and transformers, subscriptions and publications, reactor and proactor pipelines, the whole thing has plenty of opportunities to trip users (including agents) up. The “pit of success” for Brighter is a source of a lot of friction and causes developers to reject us in favor of other frameworks.

What we need is static analysis, diagnostics, and dynamic analysis (Roslyn) to help. It looks like a useful backlog item to burn some tokens on.

Working with an agent to fulfill this, once we outline the requirements, requires us to pass our ADR steps.

We use our workflow spec:design to kick this off, and the agent has a pretty good attempt at the design of a tool that would meet the requirements.

But Brighter is a mature codebase, so it gets it wrong. It can't hold all of our code in its context. Its approach isn't dumb; it just misses a lot. It fails to understand the levels at which configuration can occur: local, with producers to the external bus, and with configuration to an external bus.

It doesn't yet have a theory for how to do this.

I give feedback on it. I explain the theory of the levels of configuration. I tell it about IBrighterBuilder and how that can help.

It refines its approach. It's better, but while it recognizes that we need a model of our pipelines, it fails to associate that with using our IAmAPipelineBuilder<T> to create pipelines; instead, it describes (a dry run is what I tell it) and not to build.

I give it more feedback. I explain the theory of using IAmAPipelineBuilder<T> to populate a model that describes a pipeline we can use to validate and report.

We re-run, and now it proposes extending PipelineBuilder with describe methods to build a model. This looks better.

I notice how it is setting up its rules. We have an implementation of the Specification pattern that is already checked in for some future functionality. It is in an assembly that depends on this one, but we can move it and its tests. I discuss it with the agent as a better way to implement this.

This is iterative, a conversation with the agent as we work out the theory of how this fits together. I already hold much of the theory of how Brighter works. I can extend that theory to what might work to implement this feature. The agent can pursue this faster than I can, reading existing code, figuring out what it would take to make it real.

This is programming. We are building the theory of how this works.

Does Stack Matter?

One simplistic idea is that the stack you use does not matter. Now that you can use an agent to write the code, what does it matter what stack you write it in? The problem comes down to whether you can build the theory of the program if you don't understand the stack. In some situations, you may be able to understand the theory without knowing the language, but in other situations, the nuances of how we choose to use language features to represent program theory may be important. Typically, as complexity grows, so does the likelihood that the stack – not just the language but the frameworks and libraries we use – is important to the theory, and as such, we have to understand it.

In cases where we need to understand the stack to build the theory, this would create unmaintainable code in which the programmer does not understand the stack they are working with.

Can LLMs Build Theory?

Agents are continuously new developers who struggle to maintain the theory, even if it is presented to them in conversation. So agents, like new developers, don't have the theory and can be coders but not programmers without supervision.

When using an agent, developers experienced with the codebase may be slower, precisely because they need to explain the theory to the agent. For short tasks, where explaining the theory might take longer than just doing the work, manual efforts may be faster due to this cost.

As codebases expand, we would seem to have increasing issues in codebases where the programmer role has been delegated to the agent, which has no theory, and thus treats every new requirement as novel. This accretes multiple theories into the codebase, which eventually becomes incoherent for agents or people.

New Developers, LLMs, and Theory

This also explains why senior developers are more productive with coding agents:

Will Agents Develop Theory?

Memory is the key problem for agents with theory, both forgetting the theory behind changes and the amount of context required to keep those decisions in mind when modifying existing code. It's possible that developments in LLMs over time will break down this problem, allowing agents to build and retain theory. For now, the programmer role does seem beyond agents. They can help as a researcher, but don't have the theory a seasoned developer has with a codebase.

For now, then, the programmer role requires a person.

A Programming Episode with Brighter

Reviewing the ADR, the agent wrote, I realize we now have two approaches to validation. One uses our Specification<T> pattern, but the other creates a bespoke validation infrastructure. This seems ugly. If we are moving to the Specification<T> pattern, why do we still have a bespoke class to check our pipeline?

After reviewing the ADR, I realize the issue is reporting failures. Our Specification<T> pattern will give you pass/fail, but won't tell you which rule failed and why. It also explains an earlier issue, where the review noted that we were throwing an error from a Specification<T> when we reached a failure case that should not occur. We need to implement the Visitor pattern in our specification to collate all the errors.

I explain this change to the agent, noting that using the Visitor pattern will allow us to accumulate error messages in the graph, which can be retrieved if the combined specification evaluates to false. I tell the agent to ask me questions so that we can have a dialogue about this design.

The agent tries to overcomplicate this at first. When it answers, it can't figure out how to make this work. At a guess, the model doesn't recognize this approach. It can't initially see how to translate the multiple rule failures from its bespoke code, so it comes up with a ForEachSpecification<T>. I have to walk the agent back to basics on this approach, explaining that each Specification<T> is a rule; a rule may result in multiple breaches, to be recorded in an IEnumerable<ValidationResult>. I then explain that if there are any results, the predicate fails. The visitor pattern is then used to retrieve the results. I suspect that I assumed the agent understood this pattern combination, but it does not.

The agent now understands and presents a summary. The summary matches my expectation, so I ask it to go ahead and change the ADR.

The code looks simpler. I spot some failures to extract methods, leaving a complex implementation. Although this is just an ADR, I will have the agent change it now.

Overall, this conversational process is faster than writing this out by hand. We are getting closer iteratively.

But I also appreciate that I had simply accepted this, without properly reviewing, I would have had something less maintainable, because there were no clear patterns flowing through the code, but different ideas lashed together. I have worked with code bases where new insights had not led to refactoring, resulting in a codebase that felt like a lesson in decision archaeology. That was what would have happened here, without effective review. Because I took on the agent's roles as a researcher and a programmer, we iterate toward a cleaner design.

I feel comfortable spending more time here than I did before. The rush to get to writing code is gone. The agent can handle that quickly. Instead, I iterate on the design, safe in the understanding that the agent can deliver it quickly.

Return of the Programmer

Armed with the understanding that we have merged two roles in our thinking for years, how does this help us think about the future for software engineers?

In the era of agents writing code, this distinction between programming and coding becomes important. With coding agents, the agent is fulfilling the role of the coder at the levels of accuracy that mean it is rapidly taking over that task. But human software engineers continue to own the task of programming. The two have been merged in recent years, but we need to recognize their differences so we can make better decisions about who owns each task.

Copyright © Ian Cooper 2024+ All Rights Reserved.