Development velocity is not the true bottleneck
- The real cost is in reading: AI reduces the cost of writing code to zero, but exponentially increases the time developers spend auditing and understanding someone else's code.
- The rise of code churn: According to the GitClear (2024) Report, AI is skyrocketing redundancy and code churn in repositories.
- Establish design guardrails: To survive AI-generated code, we need strict modularity (bounded contexts) and automated architectural tooling to enforce coupling boundaries.
The rise of artificial intelligence assistants has transformed the act of programming. Tools like GitHub Copilot allow us to generate entire classes, complex functions, and unit tests in a matter of seconds. The feeling of speed is intoxicating. Suddenly, developers feel like orchestra conductors, dictating instructions in natural language and watching the software materialize instantly.
However, this speed is largely an illusion. In the physics of software development, writing code is only a tiny fraction of the software lifecycle. The true cumulative cost lies in reading, understanding, debugging, and refactoring code over time. By reducing the cost of code generation to zero without imposing strict architectural governance, we risk flooding our repositories with accidental complexity, turning promising projects into unmaintainable systems.
1. The Paradox of Cheap Writing and Expensive Reading
Software development has always been governed by a fundamental asymmetry: code is read many more times than it is written. When a senior developer tackles a task, they spend 80% of their time navigating the project structure, understanding existing business rules, and tracing data flows. Typing characters into the editor is the easy part.
AI assistants drastically alter this equation. By automating typing, they remove the initial friction, but do nothing to ease the cognitive load of reading. In fact, they make it worse. When we delegate code generation to a probabilistic model, we shift human effort from active creation to passive auditing.
Reviewing and correcting code written by a third party—especially one that can invent subtleties or convincingly hallucinate APIs—is mentally more exhausting than writing it yourself. As demonstrated by the classic NYU study (2021), roughly 40% of code generated by AI assistants contains common security vulnerabilities. Developers cannot simply accept the copilot’s suggestion; they must act as ruthless quality control inspectors. If the team drops its guard, the software degrades silently.
2. The Phenomenon of Code Churn and Repository Decay
The widespread adoption of generative AI is already leaving its mark on global software development metrics. The revealing GitClear (2024) Report analyzed over 150 million lines of code written in recent years and uncovered worrying trends:
- Increased code churn: The percentage of code modified or discarded within days of being written has grown substantially. This suggests a “trial and error” dynamic where unnecessary or buggy code is introduced, only to be hastily corrected later.
- Decline in active refactoring: The volume of refactored code (restructured without changing its external behavior) has dropped drastically compared to newly added code.
- Rise of copied and pasted code: AI tends to suggest redundant solutions instead of abstracting common components, weakening the DRY (Don’t Repeat Yourself) principle.
This data indicates that AI is encouraging short-term development habits. The ease of pressing Tab and accepting a suggestion discourages the mental effort required to refactor and consolidate architecture. Instead of building solid abstractions, we accumulate redundant, hyper-specific solutions that increase repository size and, along with it, maintenance friction.
3. Essential vs. Accidental Complexity in the Age of AI
In his famous essay on software engineering, No Silver Bullet: Essence and Accidents of Software Engineering (1986), Fred Brooks distinguished between two types of complexity:
- Essential complexity: The difficulty inherent in the business problem we are trying to solve (e.g., a country’s tax rules or a flight booking algorithm). It cannot be removed without simplifying the business itself.
- Accidental complexity: The difficulty introduced by tooling, poor infrastructure, or bad software design.
Generative AI is extremely useful for mitigating accidental complexity related to language syntax, boilerplate code, or setting up API calls. However, it does not understand the essential complexity of your business domain or the design limits of your architecture.
When we allow AI to suggest code without explicit governance, we allow it to introduce new accidental complexity. A copilot does not know if a new class violates the single responsibility principle of your hexagonal architecture, or if it is introducing harmful temporal coupling between two modules. Its only goal is to provide a suggestion that looks syntactically valid in the immediate context of the editor’s open buffer.
4. Governance Strategies: How to Survive AI-Assisted Coding
To reap the real benefits of AI without destroying the maintainability of our systems, development teams must establish active design governance. It is not about banning the tools, but about shielding the system from their excesses.
Strict Modularity and Bounded Contexts
The best defense against unchecked complexity is isolating different business areas using Bounded Contexts inspired by Domain-Driven Design (DDD). If your module interfaces are explicit and well-protected, the impact of a bad AI-generated suggestion in a specific module remains contained, preventing it from polluting the rest of the application.
Design-Oriented “Context Engineering”
Before asking a copilot for code, we must ensure the AI understands our system’s constraints. This means properly structuring our prompts and providing precise technical context:
- Documenting the project’s architectural rules (e.g., “we use dependency injection patterns and avoid framework dependencies in the domain”).
- Using tools like ArchUnit.NET in C# or similar libraries in other ecosystems to automate architectural tests that fail if generated code crosses forbidden boundaries.
// Architectural test example using ArchUnit.NET in C#
using ArchUnitNET.Domain;
using ArchUnitNET.Loader;
using ArchUnitNET.Fluent;
using Xunit;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
public class ArchitectureTests
{
private static readonly Architecture Architecture =
new ArchLoader().LoadAssemblies(typeof(ArchitectureTests).Assembly).Build();
[Fact]
public void DomainShouldNotDependOnInfrastructure()
{
IArchRule rule = Types().That().ResideInNamespace("Domain")
.Should().NotDependOnAny(Types().That().ResideInNamespace("Infrastructure"));
rule.Check(Architecture);
}
}
Integration Tests as a Safety Net
Because AI can introduce subtle integration bugs that are hard to catch in isolated unit tests with too many mocks, end-to-end (E2E) and integration tests become vital. They act as a safety net that validates the observable behavior of the software, rather than its implementation details.
5. The Irreplaceable Role of the Software Engineer
The future of software development will not be about writing less code, but about designing better control systems. As AI takes over the mechanical task of typing, the software engineer’s differentiating value shifts toward defining constraints, making difficult architectural tradeoffs, and managing the organization’s cognitive load.
The copilot illusion invites us to believe that programming is solely about producing lines of code at high speed. The reality of engineering is different: excellence is measured by the code we choose not to write, by the simplicity of our interfaces, and by the organizational rigor that allows a team to keep delivering value consistently, years after the first line of code was typed.




