A machine arm rapidly builds the upper floors of a software city while an engineer adds foundations, guardrails, inspection points, and recovery routes

AI Engineering · 2026

AI Coding Makes DFX
More Important, Not Less

When implementation gets cheaper, engineering constraints become more valuable

15 min read Published June 18, 2026 Updated August 9, 2026

Abstract

Over the past few months, I have been using AI to write more and more product code. My feelings are mixed. AI is fast, practical, and sometimes surprisingly good at implementing a feature. But a feature that works is not automatically a system that is well designed.

The problem is rarely that AI cannot write code. It is that we usually give it functional requirements and leave the engineering constraints implicit. DFX, or Design for X, brings reliability, maintainability, observability, testability, security, operability, and cost into the definition of done. As code generation becomes cheaper, these constraints become more important, not less.

AI coding should not be about producing more code faster. It should be about producing better code under clearer constraints.

Part I · The feature works. What about the system?

01The feature is done, but the system is getting harder to touch

Over the past few months, I have been using AI to write more and more code. My feeling is a bit mixed. On the surface, AI coding is already very impressive. Ask it to build a page, write an API, add a scheduled job, connect to a database, or fix a small bug, and it can usually do the job well. In many cases, the result is not merely usable. It is fast, practical, and sometimes surprisingly good.

But after using it on real product code for a while, I started to notice another problem: AI is very good at implementing features, but it does not automatically produce a well-designed system.

At the beginning, everything looks fine. The feature works. The API returns the right data. The UI is connected. The task runs. The logs show no obvious errors. Then another requirement arrives, followed by another and another. After several rounds, the code slowly turns into something familiar: business logic scattered across layers, unclear module boundaries, duplicated checks, hidden state transitions, inconsistent exception handling, and configuration logic mixed with execution logic.

In other words, the feature is done, but the system is becoming harder and harder to touch.

This is not simply an “AI writes bad code” problem. Human engineers can produce exactly the same result. The difference is that AI can produce it much faster. It accelerates good decisions, but it also accelerates missing boundaries, vague requirements, and expedient patches.

The 2025 DORA report describes AI’s primary organizational role as an amplifier: it magnifies existing strengths and weaknesses rather than replacing the system around the work. [1] That finding is broader than code design, but it matches what I have seen in a codebase. If the engineering discipline is strong, AI helps it travel faster. If the design is already drifting, AI gives the drift more throughput.

The real issue is that most of the time, we give AI only functional requirements. We explain what the system should do, but not how it should behave when things go wrong, how it should be maintained, how it should be observed, how it should be extended, or how it should protect people from costly mistakes.

That is why I think DFX becomes even more important in the AI coding era.

02Functional requirements are not enough

In software design, we usually talk about two broad kinds of requirements.

Functional requirements describe what the system should do: user login, order creation, task scheduling, report generation, payment processing, notification delivery, or trade execution. They are easy to see from the outside. A button exists or it does not. An API works or it does not. A workflow completes or it fails.

But a real system is not judged only by whether one feature can run once under ideal conditions. It also needs to be reliable, maintainable, observable, testable, secure, scalable, and operable. These qualities are often called non-functional requirements, although the name makes them sound secondary. In practice, they can shape the architecture more deeply than the visible feature itself.

Fred Brooks made a useful distinction in No Silver Bullet. Software work contains accidental difficulty in representing a solution, but its essential difficulty lies in specifying, designing, and testing a complex conceptual structure. [2] The paper predates AI coding by decades, so applying it here is an inference. AI can remove a remarkable amount of representational friction. It does not remove the need to decide what the system means, where its boundaries are, or what must remain true after failure.

Take a simple task execution system. If we only care about the feature, the implementation is straightforward:

  1. Read a task from the database.
  2. Execute it.
  3. Update its status.
  4. Return the result.

AI can write that code very quickly. Reliability makes the design more interesting.

What happens if the process crashes halfway through? What if two workers pick up the same task? What if an external API times out after the operation has partially succeeded? What if the business action succeeds but the database update fails? Should the task be retried? Is the operation idempotent? Can the user safely click the button again? Which state must be persisted, and which state can be recalculated?

These are not small implementation details. In production systems, they are often exactly where the real failures happen.

A useful test: functional acceptance proves that the happy path can complete. Engineering acceptance defines what remains true when that path is interrupted, repeated, delayed, changed, or operated incorrectly.

Part II · What DFX changes in the design conversation

03What DFX means in practice

DFX means Design for X. The X can represent many different quality goals:

Design for Reliability and Availability: expect failures, contain them, and recover without losing critical promises.
Design for Maintainability and Extensibility: keep boundaries and state transitions clear enough for the next change.
Design for Testability: make important behavior controllable and observable under success and failure.
Design for Observability: expose signals that explain user impact, system state, and recovery progress.
Design for Security: constrain authority, validate inputs, and make dangerous actions explicit.
Design for Operations: support deployment, rollback, diagnosis, intervention, and audit as normal workflows.
Design for Scalability and Cost: state load assumptions and decide what the system may spend to keep its promises.

The classification below comes from a broader product-design context. It groups DFX into product delivery, product evolution, and product operation, then includes concerns ranging from procurement and deployment to reliability, serviceability, security, compatibility, and reuse. [3] Software teams may use different labels, but the central idea holds: the product must be designed for a life beyond the moment a feature is completed.

A DFX taxonomy grouping quality goals under product delivery, product evolution, and product operation

Figure 1 · DFX spans delivery, evolution, and operation. A feature is only one moment in that lifecycle. The diagram helps move quality concerns into the design conversation before implementation begins.

These topics do not sound as exciting as shipping a new feature, but they decide whether the system can survive after the feature is shipped. A feature can be implemented in one afternoon. A bad architecture can punish a team for the next two years.

This is especially true when working with AI. Code generation is cheap, so accidental complexity is cheap too. You can keep asking it to “just add this condition,” “just support this new case,” “just fix this one bug,” or “just reuse the existing logic.” Each instruction looks reasonable in isolation while the overall design gets steadily worse.

Eventually, nobody knows where the real business boundary is. Nobody knows which state is authoritative. Nobody knows whether a retry is safe, whether a deletion is reversible, or whether a failure is expected, ignored, or dangerous.

That is the moment when code stops being an asset and starts becoming a liability.

04Why AI tends to create spaghetti code

I do not think AI creates spaghetti code because it does not understand programming. In many cases, it understands syntax, frameworks, common patterns, and even architectural concepts quite well. The problem is that its default objective is usually too narrow.

If you ask AI to “implement this feature,” it will optimize for completing the feature. It chooses the most direct path that makes the current requirement work. For a small script or a disposable prototype, that may be exactly right. In a product expected to evolve, the shortest path to today’s behavior is not always the shortest path to a maintainable system.

The deterioration is rarely dramatic:

The first change
puts business logic in the controller because that is convenient.
The second change
adds several conditions to the same service because introducing a boundary feels slower.
The third change
squeezes a new state into an old field because changing the model seems expensive.
The fourth change
catches and ignores an exception because the request was simply to “make it work.”

None of these decisions looks disastrous by itself. Together, they create a system that is difficult to reason about.

A clean mechanism receives one reasonable patch after another until it still runs but is buried under tangled wires and duplicated controls

Figure 2 · “Just one more change” is how accidental complexity compounds. The green light is still on: the feature works. The cost has moved into the next change, the next incident, and the engineer who must explain which wire is authoritative.

Martin Fowler’s “Design Stamina Hypothesis” describes the same curve. Neglecting design may produce features faster at first, but the degrading codebase makes each later change slower; sound design helps a project “go faster for longer.” Fowler is careful to call this a hypothesis rather than a proven law, and the point at which design pays off depends on context. [4] AI does not invalidate that curve. It makes us reach the consequential part of it sooner because more changes can arrive in less time.

This does not mean asking AI to build a grand architecture before it writes one line of code. AI can over-engineer too: too many layers, too many helper classes, too much configuration, or an elegant abstraction that solves a problem the product does not have. Complexity is not free, whether it comes from patches or patterns.

The better question is not “Can AI write code?” Of course it can. The better question is: can we give AI the right engineering constraints before it starts writing?

Part III · Design for failure and for people

05Reliability: stop designing only for the happy path

Reliability is one of the most useful DFX perspectives to bring into AI coding. A reliable system is not a system that never fails. That is unrealistic. It is a system that expects failures, limits their blast radius, makes them visible, and gives the team a way to recover.

The principle is simple: assume failures will happen. Processes crash. Networks become unstable. Disks fill up. Third-party APIs time out. Users click the wrong button. Developers deploy the wrong configuration. A system that works only when everything goes well is not reliable. It is lucky.

Before asking AI to write the code, I now prefer to ask questions like these:

DFX questions before implementation

  1. Where can this workflow fail?
  2. What state will the system be in after each failure?
  3. Which operations must be idempotent?
  4. Which failures should be retried, and which layer owns the retry?
  5. Which failures should trigger alerts or require human intervention?
  6. What must be logged and measured so the incident can be understood later?
  7. What must be persisted so the system can recover after a crash?
  8. What evidence will prove that recovery is complete?

After AI answers those questions, the implementation usually looks different. State transitions become more explicit. Retry ownership becomes clearer. Important failures are no longer silently hidden. Recovery becomes part of the design instead of an afterthought.

The task runner’s timeout problem has a close analogue in AWS. Malcolm Featonby describes an EC2 request whose response is lost after the service may already have created the resource. Blindly retrying could create a second instance. AWS addresses this by making intent explicit with a caller-provided request identifier, allowing retries to be recognized and handled idempotently. [5] The important lesson is not “always add an idempotency key.” It is that retry behavior belongs in the API contract, not in a random catch block added after an incident.

Reliability also does not mean protecting every path equally. Google SRE frames reliability as risk management: align the service’s reliability target with the risk the business is willing to bear, rather than maximizing uptime at any cost. [6] That is a useful correction to DFX checklists. A delayed analytics report, a duplicate payment, and a lost audit record do not deserve identical mechanisms or budgets.

Another principle is to keep the system simple. A simple design that solves the real problem is often better than a clever design that creates five new ones. At the same time, simplicity should not hide a single point of failure. If a critical workflow depends on one service, one job, one configuration, one external system, or one manual step, that dependency should be explicit. AI will not always point it out unless the design review asks for it.

06Human mistakes are also part of system design

Many production incidents are not caused by machines failing on their own. They happen when people use a system in ways the design did not anticipate or protect against.

Someone deletes the wrong data. Someone publishes to the wrong environment. Someone changes a configuration without understanding its impact. Someone clicks a dangerous button too quickly. Someone starts an operation twice because there is no progress feedback.

It is easy to blame the user, but good system design should reduce the probability and cost of these mistakes. Don Norman makes this point directly in “Human Error? No, Bad Design”: blaming the final human action often prevents teams from finding the equipment or procedure that made the error likely. [7]

For software, that principle has concrete consequences:

These are not decorations around the feature. They are part of reliability and operability. Yet many AI coding prompts ask only how the user completes the desired action. They rarely ask how the user might perform the wrong action, lose context, repeat an operation, or need to reverse it.

Part IV · A better contract with AI

07A better way to use AI for coding

For any feature larger than a small isolated task, I no longer think the best first step is asking AI to write code. A better first step is asking it to analyze the requirement through a DFX lens.

For example:

Before writing code, analyze this requirement from the perspective of DFX.
Cover reliability, maintainability, observability, testability, security,
and operational safety.

Identify the main failure scenarios, state boundaries, idempotency
requirements, retry behavior, recovery strategy, logging and metrics,
configuration validation, human-error risks, and the parts of the design
most likely to change in the future.

Then propose the module structure, implementation plan, and verification
evidence. List unresolved business decisions explicitly instead of
inventing assumptions.

This sounds slower, but in practice it saves time. Once AI understands the quality goals, it stops behaving like a simple code generator and starts acting more like an engineering assistant. The code is usually better structured, and the trade-offs become easier to review.

The prompt is not magic. You still need to challenge its assumptions, review the design, understand the system, and decide which risks are worth paying for. The useful change is that quality goals are no longer hidden in the reviewer’s head. They become part of the assignment.

For an implementation plan to be useful, I now expect it to produce evidence in six areas:

A practical DFX review for AI-generated changes
Lens Questions the plan must answer Evidence before release
Reliability Where can it fail, repeat, or partially succeed? Failure tests, idempotency semantics, recovery steps
Maintainability Which boundary owns the rule and likely change? Module map, state model, explicit contracts
Observability How will we know the user outcome and system state? Structured logs, metrics, traces, actionable alerts
Testability Can important states and dependencies be controlled? Deterministic tests for success, failure, and retry
Security and operations Who may act, how is impact limited, how is it reversed? Permission checks, validation, dry-run, rollback, audit
Scalability and cost What load and budget assumptions does the design make? Limits, capacity tests, degradation behavior, cost signals

AI coding should not be about producing more code faster. It should be about producing better code under clearer constraints.

08Architecture matters more when code becomes cheaper

Some people believe AI will make architecture less important because code is becoming easier to generate. I think the opposite is happening.

When writing code becomes cheaper, deciding what should be written becomes more valuable. When implementation becomes faster, design mistakes become easier to amplify. When almost anyone can generate a working feature, the meaningful difference is whether the system can still be understood, changed, operated, and trusted after many such features have been added.

AI can write code, but it needs a clear target.

AI can refactor code, but it needs to know what quality goal the refactoring serves.

AI can fix bugs, but it needs to understand which behavior is correct.

AI can generate abstractions, but it does not always know whether an abstraction is worth its cost.

That is why DFX is not an old architecture topic. It may become one of the most practical ways to use AI coding well.

The future of AI coding should not stop at this question:

How do I implement this feature?

It should continue with another:

Under what quality constraints should this feature be implemented?

If we skip that question, AI may help us move faster in the short term while creating a system that becomes harder to change, harder to operate, and harder to trust. The code may still run, the green light may still be on, and the engineering debt may already be compounding.

References and further reading

  1. DORA. “State of AI-assisted Software Development 2025.” Google Cloud, 2025.
  2. Brooks, Frederick P., Jr. “No Silver Bullet: Essence and Accidents of Software Engineering.” University of North Carolina at Chapel Hill, 1986.
  3. Original DFX taxonomy image.” Image supplied with the June 18 article.
  4. Fowler, Martin. “Design Stamina Hypothesis.” 2007.
  5. Featonby, Malcolm. “Making Retries Safe with Idempotent APIs.” Amazon Builders’ Library.
  6. Alvidrez, Marc. “Embracing Risk.” Site Reliability Engineering, Google, 2016.
  7. Norman, Don. “Human Error? No, Bad Design.” 2014.