{0,0}

AI in software development: Who is the code that we no longer write for?

The impact of artificial intelligence on software development and how it affects good practices and design patterns created initially by and for humans.

For years, many software development best practices were justified by a fairly human idea: code had to be understandable to us.

We separated responsibilities because we could not hold the entire system in our heads at once. We created abstractions so we would not repeat ourselves. We encapsulated infrastructure details so we could focus on business logic. We designed boundaries between layers so one person could read, change, and maintain a part of the system without having to understand all of it.

But in recent debates with friends and colleagues in the industry, a thought came up: the arrival of artificial intelligence as a development tool is starting to unsettle that explanation.

If AI can read hundreds of files in seconds, spot repeated usage, change multiple places at once, write tests, review code, explain errors, propose refactors, and eventually perhaps operate part of the full development cycle, then an uncomfortable question appears:

Do abstractions, patterns, and best practices still make sense if the main consumer of the code is no longer only a human?

Personally, I believe the answer is yes. But not for the same reasons as before.

AI lowers the cost of writing code, but not the cost of designing systems

One idea is that certain classic principles, such as DRY (Don’t Repeat Yourself), are starting to lose value. The argument usually goes something like this:

“Before, we did not want duplicated code because we would have to change it manually in many places. Now AI can find every occurrence, change them, and adjust the tests. So duplicating code does not matter as much anymore.”

We are aware that AI does reduce the mechanical cost of editing code. Changing five similar functions, updating ten files, generating a migration, or adapting a repeated signature across several services is much cheaper than before.

But that view confuses the cost of editing with the cost of design.

DRY was never only “do not write the same thing twice.” In its more important form, DRY means that a rule, a decision, or a business concept should have one authoritative representation within the system.

It is not the same to duplicate a simple condition like:

if (amount <= 0) {
  throw new InvalidAmountError();
}

as it is to duplicate a business rule like:

const shouldCompleteTransaction =
  blockchainStatus === 'COMPLETED' &&
  kytStatus === 'PASSED' &&
  approvalStatus === 'APPROVED';

The first duplication may be tolerable. The second represents domain knowledge. If that rule appears in a webhook, in a cron job, in a backoffice action, and in a public API, the problem is not only that four places must be edited when it changes. The problem is that the system no longer has a single owner for the idea of “completed transaction.”

AI can help find every copy. But it cannot always guarantee that those copies mean exactly the same thing, belong to the same context, or should evolve together.

So in a world with AI, the question should not be “is there repeated code?” The question should be: “Are we duplicating mechanics or are we duplicating meaning?”. Mechanical duplication matters less than before. Conceptual duplication remains dangerous.

Weak abstractions do become obsolete

AI exposes something that was already true before it arrived: many abstractions exist without a strong reason.

Layers that only forward calls. Interfaces with a single implementation and no intent to replace it. Repositories that wrap trivial CRUD without protecting any real boundary. Services, Managers, Handlers, Processors, and UseCases that add no meaning, only distance.

That kind of abstraction was often justified with phrases like “this keeps things tidier” or “this is how we follow Clean Architecture.” But if the abstraction does not protect a rule, define a boundary, enforce a dependency, encapsulate a policy, or reduce a real risk, then it is probably decoration.

And AI makes decoration even harder to defend.

If a tool can generate, navigate, and modify repetitive code with ease, then abstractions whose only purpose was to make human navigation more comfortable lose force. Not all of them disappear, but they do become more suspicious.

In that sense, AI does not kill good practices. It kills bad justifications. It is not enough to say “this is abstracted.” The important question is: “What decision does this abstraction protect?”

If the answer is “none”, perhaps it should not exist. Personally, I already agreed with this idea before AI arrived. Artificial intelligence only reinforced my opinion.

Strong boundaries are not visual aids: they are system constraints

The most interesting argument I heard against abstractions goes something like this:

“The goal of separating database, business logic, transport, and infrastructure was to help humans focus on one thing at a time. But if AI can understand the whole system, those boundaries are no longer necessary.”

That criticism makes sense if you think of boundaries only as a comprehension tool. But good boundaries do not exist only so code is easier to read. They exist to limit what the system can do.

A boundary can express rules like:

  • “This part of the system can talk to Postgres.”
  • “This part can interpret external payloads.”
  • “This part is the only one authorized to mutate balances.”
  • “This part contains the state machine for a transaction.”
  • “This part emits events only after persisting changes correctly.”

That is not simply organization. That is design.

There is a huge difference between a system where AI can detect a rule violation and a system where the architecture makes that violation difficult or impossible.

For example, we might have balance logic scattered throughout the code:

await db.accountBalances.update({
  accountId,
  instrumentId,
  balance: currentBalance + amount,
});

A sufficiently powerful AI could find all those places, review them, and detect inconsistencies. But the system would still allow any part to modify balances directly.

Instead, if there is a clear boundary:

interface LedgerService {
  applyMovement(command: ApplyMovementCommand): Promise<ApplyMovementResult>;
}

then the architecture expresses a constraint: “Balances are only modified by applying movements.”

That boundary matters even if AI writes the code. It matters because it reduces the space of invalid states. It matters because it turns a convention into structure.

The same applies to a state machine:

const nextState = transactionStateMachine.apply(currentState, event);

The importance is not that it is easier to read. The importance is that every transition goes through one canonical point. The system no longer depends on each handler remembering to implement the correct rule.

AI can write the code, but the system still needs invariants

The idea that “AI can handle everything” often assumes that the main problem of software is modifying text. But software is not only text. It is a set of decisions about rules, dependencies, side effects, possible failures, ownership, and evolution.

AI can generate an adapter. But someone, or something, has to decide where external provider semantics end and internal domain begins.

AI can write a repository. But the system has to decide whether business logic may know about SQL, locks, indexes, transactions, and persistence details.

AI can write tests. But someone has to decide which properties of the system must remain true and what should be mocked.

AI can update every use of a function, but that does not replace the need to define the single place where a business rule lives.

This becomes especially important in systems where errors are not cheap: finance, ledgers, crypto transactions, distributed state, idempotency, concurrency, security, external integrations, and asynchronous processes.

In those contexts, good abstractions are not decoration. They are containment barriers.

They do not exist so the code looks cleaner. They exist so the system has fewer ways to break.

Code is no longer only for humans, but it should not be only for AI either

For a long time we said that “code is read more than it is written.” That phrase is probably still true, but it may need an update: code is now also read, interpreted, and modified by models.

That changes some priorities. Perhaps we stop valuing abstractions that only helped navigate files. Perhaps we accept more local duplication when it is clearer than a premature abstraction. Perhaps we write less boilerplate by hand. Perhaps design patterns matter less as recipes and more as a language of intent.

But AI being a new consumer of code does not mean code should become an amorphous mass that only AI can operate.

Even if in the future AI reviews, debugs, deploys, and responds to incidents, the system still benefits from explicit constraints. AI can reason about a coupled system, but that does not make coupling good. AI can follow trails in duplicated code, but that does not create a single source of truth. AI can detect a dangerous dependency, but that does not replace an architecture that forbids it.

Intelligence, human or artificial, does not remove the need for an explicit, clear structure.

In fact, the faster code can be generated, the more important it becomes to have clear limits on where each decision may live.

The new criterion: less decoration, more intent

The arrival of AI should not lead us to abandon design patterns, principles, and abstractions. It should force us to justify them better.

Before, we could accept an abstraction because it “helps organize things.” Now that explanation is weak.

An abstraction should be defensible with something more concrete:

  • “This boundary prevents domain logic from depending on the database engine.”
  • “This interface allows changing providers without polluting the main flow.”
  • “This state machine centralizes valid transitions.”
  • “This service is the only place where balances are mutated.”
  • “This mapper keeps raw external payloads out of the domain.”
  • “This outbox ensures events are published after the commit.”

That kind of limit remains valuable in a world with AI. Perhaps more valuable, not less.

On the other hand, if the justifications are some of the following:

  • “Because it looks more enterprise.”
  • “Because we always use repositories.”
  • “Because Clean Architecture says there should be an interface here.”
  • “Because maybe someday there will be another implementation.”
  • “Because I do not want to repeat three lines.”

then AI will probably push us to remove it.

And that is a good thing.

In short

AI changes deeply how we develop software. It lowers the cost of writing code, changing it, exploring it, and generating alternatives. It also makes some mechanical skills that once took up a large part of daily work less valuable.

But it does not make design obsolete.

What it does make obsolete is performative design: patterns used as decoration, abstractions without force, layers that protect nothing, DRY applied to superficial similarities, and architectures copied without understanding what problem they solve.

The code we no longer write by hand still needs to express intent.

It needs to say which rules matter. Which dependencies are allowed. Which states are valid. Which parts of the system may produce effects. Which concepts belong to the domain and which are external details. If this is not explicit, AI may generate code that does not follow the system’s rules; AI slop in code is real, can affect security and scalability, and creates a snowball effect because it feeds back into the models that keep working on the slop and generating even more slop.

Perhaps in the future AI will write most of the code. Perhaps it will also review, debug, deploy, and operate entire systems.

But even in that scenario, the central question does not go away. It is not “can AI understand this code?”

The question is: “Does this system make explicit the rules we cannot afford to break?”

That is where good abstractions, design patterns, and principles still make sense. Not because humans are incapable of understanding everything. But because correct systems should not depend on someone, human or AI, remembering and/or reviewing everything every time.