DEV Community

Cover image for Will AI Make Abstractions Obsolete?
Remo H. Jansen
Remo H. Jansen

Posted on Edited on

Will AI Make Abstractions Obsolete?

As AI agents become increasingly capable, I've noticed a growing number of discussions questioning some of the fundamental assumptions we've made about software development.

Do we still need code reviews if AI can write and verify code? Why should we use languages like Python or Java when an AI can generate much more efficient Rust? And if AI becomes good enough, why stop at Rust? Why not have it generate assembly or machine code directly? Do we even need frameworks and libraries anymore?

The underlying assumption seems to be that many of the abstractions we've introduced over the years exist primarily to compensate for human limitations. If AI doesn't have those limitations, perhaps we can remove the abstractions too.

I think this is looking at the problem from the wrong perspective.

My intuition is that the more capable AI becomes, the more important abstractions will become. Not less.

To explain why, I think we need to revisit something fundamental about software engineering: complexity.

Complicated is not the same as complex

We often use the words complicated and complex interchangeably, but I think there's a useful distinction between them.

Something complicated has many parts, but those parts can generally be understood independently. Once you've figured out how the individual pieces work, understanding the whole becomes relatively straightforward.

Something complex is different. It involves many elements and, more importantly, many relationships between those elements. Understanding each individual component doesn't necessarily help you understand the system as a whole. The interactions themselves create additional complexity.

Consider a complicated algorithm. It might have hundreds of steps, but each step is well-defined. Once you understand the algorithm, you can reason about its behaviour.

Now consider a distributed system. Its individual components might be relatively simple, but their interactions introduce timing issues, race conditions, partial failures, consistency problems and emergent behaviour.

The difficulty isn't necessarily understanding any particular component. It's understanding how everything interacts.

As the number of elements and relationships grows, the complexity grows too.

This distinction matters because I believe AI is already superhuman in certain dimensions of complexity management.

An LLM can reason across more files, functions, dependencies and relationships than a human developer can comfortably keep in working memory. It can inspect an entire module, trace dependencies, compare implementations and identify relationships that would take a human considerably longer to discover.

But that doesn't mean AI has unlimited capacity.

Models have finite context windows, finite computational resources and limitations in how effectively they can maintain and reason about large amounts of interconnected information.

AI raises the ceiling of complexity we can work with. It doesn't eliminate the ceiling.

And this brings me to the actual purpose of abstractions.

The purpose of abstractions is managing complexity

In his famous essay No Silver Bullet, Fred Brooks makes a distinction between essential and accidental complexity.

Essential complexity is inherent to the problem we're trying to solve. If you're building a banking system, handling transactions, ensuring consistency, enforcing security and implementing business rules are inherently part of the problem.

Accidental complexity, on the other hand, is introduced by the tools, technologies and implementation details we use to solve it.

For example, when building a web application, the essential complexity might involve:

  • Business rules.
  • User interactions.
  • Data relationships.
  • Security requirements.
  • Consistency guarantees.

The accidental complexity might involve:

  • HTTP request parsing.
  • Connection pooling.
  • Serialization.
  • Memory allocation.
  • Database driver configuration.
  • Low-level concurrency management.

Of course, the distinction isn't always black and white. Some implementation details become essential depending on the requirements. But the distinction is useful.

Over the years, we've introduced layers of abstraction to hide away accidental complexity so we can focus on the problem at hand.

We don't use React because developers are incapable of manipulating the DOM. We use it because manually managing DOM updates is usually an unnecessary distraction from the problem we're trying to solve.

We don't use SQL because developers cannot implement database storage engines. We use it because we want to express what data we need rather than describe every operation required to retrieve it.

We don't use high-level programming languages because developers cannot write assembly. We use them because assembly is usually the wrong level of abstraction for expressing our intentions.

Abstractions aren't just about making programming easier. They're about reducing the amount of irrelevant complexity that a problem solver needs to deal with at once.

And that problem solver doesn't necessarily have to be human.

But AI doesn't think like humans, does it?

This is probably the most interesting part of the discussion.

One argument against my position is that programming languages and frameworks were designed primarily for humans. If AI is doing the programming, why should it care about human-friendly abstractions?

Why not let it work directly with machine code?

I think this argument overlooks something important about how LLMs came to be.

LLMs aren't traditional computers that happen to understand human language. Their capabilities emerged from training on enormous amounts of human-generated content: books, documentation, source code, academic papers, discussions and countless examples of humans explaining and solving problems.

They've learned from the way humans represent knowledge, decompose problems, communicate ideas and reason about relationships.

This doesn't mean LLMs reason exactly like humans. Their architecture is fundamentally different, and they can develop capabilities that go beyond human reasoning.

But their learned understanding of the world, including software, is deeply influenced by how humans have organised and communicated knowledge.

Think about the concepts we use every day in software development:

  • Functions.
  • Types.
  • Modules.
  • Interfaces.
  • Ownership.
  • Components.
  • Dependencies.
  • Composition.

These aren't merely convenient syntactic constructs. They provide conceptual building blocks that help us express problems and solutions.

LLMs have been exposed to enormous amounts of knowledge expressed through these abstractions. They've learned not only their syntax but also the conventions, idioms and relationships surrounding them.

Why would we assume that moving closer to machine code would make them better at solving problems?

Being able to generate assembly doesn't mean assembly is a better representation for reasoning about a business domain, a distributed system or an application architecture.

In fact, higher-level abstractions might be particularly valuable to LLMs precisely because they provide semantic structure that aligns with much of the knowledge they've learned.

I wouldn't claim that AI cannot develop representations that are fundamentally different from human ones. It certainly might.

But we shouldn't confuse an AI's ability to operate at a lower level with a reason to force it to do so.

The lowest-level representation isn't necessarily the best representation for solving a problem.

AI can handle more complexity. Why make it handle unnecessary complexity?

Let's consider a practical example.

Imagine asking an AI agent to build a web application.

If we ask it to implement a feature using an existing framework, it gets to benefit from routing, request lifecycle management, state management, component composition and established conventions.

The agent can focus on implementing the feature.

Now imagine asking it to implement everything from scratch, including HTTP handling, memory management, concurrency primitives and serialization.

Yes, it can do that.

But now it has to reason about significantly more things, maintain more relationships and make more decisions.

Some of those decisions have absolutely nothing to do with the problem we're trying to solve.

Even if AI can handle all those details, why would we want it to?

This is the key point: the value of an abstraction isn't that the underlying complexity is impossible to manage. It's that managing it is unnecessary for the task at hand.

There's another benefit worth mentioning: abstractions constrain the solution space.

A framework provides conventions. A type system rules out certain classes of invalid programs. A programming language provides a vocabulary and a set of compositional rules.

These constraints aren't necessarily limitations. They're often what make it possible to build reliable systems without having to reconsider every possible implementation decision.

Good abstractions allow us to focus on the decisions that actually matter.

And this applies to AI agents just as much as it applies to humans.

But what about Rust?

One of the interesting developments I've noticed is that languages like Rust are becoming more attractive as AI takes on more of the implementation work.

Rust has traditionally had a steeper learning curve than languages with garbage collection. Its ownership model, borrowing rules and lifetime system introduce cognitive overhead that can be challenging for developers.

But what happens when an AI agent can deal with much of that overhead?

Suddenly, using Rust becomes more appealing. The AI can handle the compiler feedback, work through ownership constraints and iterate until the code compiles.

This is a great example of AI changing the economics of software development.

But I think there's a distinction worth making here.

Rust isn't a low-level language simply because it doesn't have a garbage collector. It provides powerful abstractions over memory management, ownership and concurrency.

The ownership system is itself an abstraction. It allows us to express constraints about memory safety in a way that the compiler can enforce.

AI makes it cheaper to work with Rust, but that doesn't mean we should abandon the abstractions Rust provides.

If anything, it demonstrates how AI can make more sophisticated abstractions accessible.

The same principle could apply to other languages, type systems and programming paradigms.

We might increasingly choose technologies that were previously avoided because their cognitive overhead was too high.

But that's very different from saying we no longer need abstractions.

Abstractions aren't free, though

I don't think this means we should introduce more abstraction layers indiscriminately.

Abstractions have costs. They can introduce indirection, hide important implementation details, make debugging harder and impose constraints that don't fit the problem.

A bad abstraction can increase complexity rather than reduce it.

Anthropic makes a similar observation in its article Building Effective Agents. It recommends starting with simple, composable patterns rather than introducing complex agent frameworks unnecessarily. Frameworks can introduce additional layers that obscure what's happening underneath and make debugging harder.

That's a good example of the principle I'm arguing for.

The goal isn't to maximise the number of abstractions. It's to find the right abstractions at the right level.

Sometimes that means removing a layer. Sometimes it means introducing one.

The important question is whether an abstraction reduces the complexity of the problem we're trying to solve.

And as AI becomes more capable, the answer to that question might change. Some abstractions that were useful primarily for humans might become unnecessary, while entirely new abstractions might emerge to help AI agents work with increasingly sophisticated systems.

The abstractions will evolve. The need for them won't disappear.

Code reviews might change, but software still needs to be understood

I think the same reasoning applies to another discussion we're having right now: the future of code reviews.

If AI can write code, generate tests, run them, inspect the implementation and iterate until everything passes, why do we need code reviews?

I suspect the answer is that we'll spend less time reviewing implementation details and more time reviewing the actual design and behaviour of the system.

We'll ask different questions:

  • Are we solving the right problem?
  • Are the architectural decisions sound?
  • Are the system boundaries appropriate?
  • Are the important invariants preserved?
  • Are we introducing unnecessary complexity?
  • Does the implementation satisfy the intended behaviour?

Passing tests isn't necessarily the same as understanding whether a system is correct.

An AI can generate code that satisfies the tests while violating an architectural invariant or introducing a problem that the tests don't cover.

The responsibility of reviewing software might move up a level, just as the responsibility of writing it is moving up a level.

We might spend less time inspecting individual lines and more time evaluating the conceptual structures those lines implement.

Again, abstraction doesn't disappear. The level at which we operate changes.

The future isn't less abstraction. It's more powerful abstraction.

I think there's a misconception that as AI becomes smarter, we'll need fewer abstractions.

I suspect the opposite is true.

As our ability to solve problems increases, we'll start tackling problems that were previously beyond our reach.

And those problems will introduce new layers of complexity.

Imagine a future where AI agents can autonomously build entire distributed systems, coordinate with other agents, manage infrastructure and maintain applications over years.

Will they need fewer abstractions because they're smarter?

Or will they need better abstractions because the systems they're building are increasingly sophisticated?

I suspect the latter.

We might move from managing individual servers to managing distributed systems, from managing individual applications to managing ecosystems of autonomous agents.

The complexity doesn't disappear. It moves.

We'll continue inventing higher-level abstractions to manage it. Some might be designed specifically for AI agents rather than humans. Some might be more declarative, more composable or more mathematically rigorous.

We might even end up with entirely new programming paradigms that we haven't imagined yet.

But the fundamental need for abstraction isn't going away.

We didn't invent high-level programming languages because writing assembly was too difficult. We invented them because we wanted to express our intentions without having to deal with every implementation detail.

That motivation doesn't disappear just because the programmer happens to be an AI.

Intelligence doesn't eliminate the need for abstraction. Abstraction is one of the mechanisms that makes intelligence scalable.

Top comments (1)

Collapse
 
trcollinson profile image
Tim Collinson •

Great piece, Remo. Your point that AI raises the ceiling but doesn’t remove it is the one I keep coming back to.

I’d push it one step further, because you hint at the economics without naming the unit. With LLMs, accidental complexity costs tokens, and you pay for those tokens more than once.

  • Generation: A declarative SQL query is a few dozen tokens. The hand-rolled loop-and-filter version that does the same job can be ten times that. Output tokens are usually the most expensive tokens you buy.
  • Re-reading: In an agent loop, that code gets pulled back into context on every turn that touches it. A 400-line bespoke HTTP handler isn’t a one-time cost. It’s rent.
  • Attention: Every token spent on connection pooling is context window not spent on the business rule. Your essential vs. accidental split maps almost exactly onto a token budget.

The part I find most interesting is that well-known abstractions come prepaid. The model already knows React, SQL, and Express from training, so app.get('/orders', ...) costs almost nothing to explain. A clever in-house framework has to be loaded into context before the model can use it. That gives your “abstractions aren’t free” section a concrete test: a homegrown abstraction pays off only if it saves more tokens than it costs to teach.

Caveat: I haven’t benchmarked this. Prompt caching softens the re-reading cost, and I’d like to see real numbers before claiming a ratio. My bet is that if someone measured token spend per feature, framework vs. from-scratch, the gap would be large.

Has anyone here measured it?