devrob.inMagento · e-commerce · AI
← writing
AI made writing code cheap. It did not make systems easier to reason about.

// Architecture

AI made writing code cheap. It did not make systems easier to reason about.

A question has been going around: if coding has been solved, why does software keep getting worse?

The usual answer is that AI writes sloppy code and we're all drowning in it. I don't think that's right. The generated code I review is mostly fine. It compiles, it has tests, it follows the conventions in the file it's sitting in. The problem is upstream of the code.

Writing code was never the expensive part of a mature e-commerce codebase. Reading one was.

Two costs, and only one of them moved

Every change to a running system has two costs.

The local cost is producing the change: writing the class, the observer, the migration, the test. This is what code generation attacks, and it attacks it well. A task that used to take forty minutes of typing and doc-lookup now takes five.

The global cost is knowing whether the change is safe. What else fires when this event fires? Which of the eleven modules touching this area also modified this behaviour? Does the interceptor I'm adding run before or after the one the payment extension registered? Is there a cache layer that will serve the old value for the next six hours?

Code generation does almost nothing for the second cost. It can read files you point it at. It cannot tell you which files to point it at, because that knowledge isn't in any one file. It's distributed across a plugin registry, a set of XML declarations, a dependency-injection graph, and a database column somebody added in 2019.

When you cut one cost to near-zero and leave the other alone, the ratio between them changes. Changes get made faster than anyone can reason about them. That's the mechanism.

Why extensibility is the thing that hurts

The platforms I work on daily are built for extensibility, and extensibility is precisely the property that makes global reasoning expensive.

Take a hook system, any hook system: WordPress filters, Magento plugins, Rails callbacks, Django signals, Laravel events. The design promise is the same: you can change behaviour without touching the original code. That promise is real, and it's the reason these ecosystems have tens of thousands of extensions.

The cost is that the call site no longer tells you what runs.

php
$price = $this->priceCalculator->calculate($product);

What does that line do? You can't know from the line. In Magento 2 that method may be wrapped by any number of registered plugins across every installed module, each of which can rewrite the arguments, replace the return value, or skip the original entirely. The execution order is determined by a sortOrder attribute in XML files scattered across those modules. Reading PriceCalculator::calculate() tells you what the original author intended. It does not tell you what happens on this installation on Tuesday.

This is not a Magento complaint. It's the general shape. The abstraction that makes a platform extensible is the same abstraction that decouples the code you're reading from the code that runs.

Where AI actually lands in that picture

So picture what an assistant does inside that architecture.

You ask for a change to how a price is calculated. The assistant reads the class, understands the class, and writes a clean, well-tested, conventional modification to the class. It is right about everything it can see.

It is not wrong. It is locally right in a system where local rightness was never the hard part. The eleventh plugin that quietly overrides the return value was never in the context window, because nothing in the file mentions it.

I've watched this play out on a store where a discount stopped applying. The change that broke it was correct code, correctly tested. It just ran at a point in the chain where a third-party module had already replaced the value it was reading. No test caught it, because every test mocked the collaborators, which is what unit tests do. The mock is a statement of what you believe the system does.

That's the loop that makes software worse: cheap local edits, unchanged global comprehension, and a test suite that validates your beliefs rather than the system.

The move that has worked for me

The fix I've landed on is not "use AI less." It's to spend the time the assistant saved on the cost it didn't reduce.

Before accepting any change to shared behaviour, I make the global picture explicit: on paper, in the ticket, wherever. Concretely:

Enumerate the interception points. For the method being changed, list every plugin, observer, event listener, or preference registered against it on this installation. Not the ones you remember. The ones you can produce from the container. If you can't enumerate them, you don't know what your change does.

Verify against the source of truth, not the docs. I got burned badly enough by this to make it a rule: the installed vendor source is authoritative, and the documentation describes some other version. When a claim about framework behaviour matters, I read the vendor directory. Every time I've skipped that step to save ten minutes, it cost me an afternoon.

Check the data layer separately. Mocked tests never validate that a column exists. A query naming a field that lives on a different table passes every unit test in the suite and throws only in production. Read the schema.

Write down the invariant. Before hunting for problems, state what must stay true: "the sum of the line totals equals the order total," "this runs exactly once per order," "a released payment refuses if any refund is open." You can't find a violation of a rule you never named, and a change that preserves an unnamed invariant does so by luck.

None of that is new. What's new is that it used to be a smaller fraction of the work, because writing the code took long enough to force you to think about the system while you did it. That accidental thinking time is gone. It has to be replaced deliberately.

The part I'm less sure about

I don't think this is permanent. An assistant that could walk a dependency-injection graph, resolve the plugin chain, and read the actual schema would attack the global cost directly, and the tooling is clearly heading that way.

But it's not a code-generation problem, and it won't be fixed by a better code generator. It's a system-comprehension problem. Until the tools model the system rather than the file, the split holds: generation is cheap, comprehension is not, and every hour you save on the first should be treated as an hour you now owe the second.

Software isn't getting worse because machines write bad code. It's getting worse because we removed the friction from the half of the job that was never the hard half, and mistook that for solving it.


Source / further reading: https://ptrchm.com/posts/nothing-works-and-everyone-is-euphoric/