Clean Code is No Longer Enough: If an AI Can’t Read It, It’s Legacy Code
For decades we tuned our codebases for two readers: the compiler that runs the code and the developer who maintains it. Now a large language model (LLM) reads it too. Since 2026 that third audience is permanent. Code is now the place where what a human wants meets what an AI writes. Some code works fine but stays opaque to an AI. I call that silicon debt. AI agents can’t refactor, test or extend such code safely, and they end up breaking the logic in ways that nobody expects.
Clean Code is no longer enough. If an AI can’t map the dependency graph of a class, or can’t predict what a property change will break, then that code is legacy already. It needs explicit types, declarative patterns and flat structures. These fit the mathematical limits of a transformer’s context window and attention. I’ll go through the principles, the tools in PHP 8.4 and Symfony 7/8, and the risk of code that grows past what a human can hold in their head.
Foundational Principles of AI-Friendly Structure
Working with AI means dropping magic and dynamic ambiguity. A model reasons by predicting the next token, so when you hide logic behind runtime tricks like magic methods or dynamic typing, the model has to guess. Guessing gives noisy output and hallucinations. So use the most explicit tools the language offers.
Strict Typing and the Death of Ambiguity
Strict typing is the best guardrail for an LLM. Add type hints to properties, arguments and return values, and the model gets a firm contract. When an AI reads a file, it builds a picture of the data flow. Missing types, or mixed, widen the search space for the next valid token exponentially, and the AI is more likely to write code with valid syntax that does not fit the rest of the system.
PHP 8.4 helps here. It moves logic out of the hidden runtime and into the visible declaration. Property hooks and asymmetric visibility show the engine, and the AI, what a property allows and how it changes, right at its definition. The AI has a shorter context distance to travel to learn how the data is handled.
| Feature | Legacy Pattern (AI-Opaque) | Modern Pattern (AI-Friendly) | AI Benefit |
|---|---|---|---|
| State Access | __get and __set magic methods | PHP 8.4 Property Hooks | Eliminates multi-step reasoning to resolve property logic. |
| Visibility | Public properties or boilerplate getters | Asymmetric Visibility (public private(set)) | Defines mutation boundaries clearly for the model. |
| Instantiation | (new Class())->method() | new Class()->method() (no parentheses) | Reduces token noise and simplifies the AST. |
| Validation | Manual checks in setter methods | Hooks with explicit type-narrowing | Localizes validation logic for the attention mechanism. |
The Power of Localized Logic via Property Hooks
I think property hooks are the biggest quality of life gain for an AI reader. In older PHP a simple validated property needed a private variable and two public methods. That often added up to 70% more boilerplate, and it is a lot of text for one field. For an AI this extra code is noise. Less text means less to read, for you and for the model. Every extra token of this code takes room in the context window and thins out the model’s attention.
Hooks put the logic in one place. An AI that reads a property definition sees how the value is validated and changed, with no hunt in the class for a setProjectName method or for any other method that touches it. That is what I mean by context coherence. When logic sits next to the declaration, self-attention gives that link more weight. The result is fewer errors when the AI writes code.
Small Atomic Units and the Single Responsibility Principle
Gemini 3 Pro and Llama 4 Scout advertise context windows of 10 million tokens. The effective context window (MECW) is a lot smaller. Research says accuracy drops by over 30% when the part that matters sits in the middle of a large file. People call this effect context rot.
For an architect, the Single Responsibility Principle (SRP) is now a technical need for AI accuracy. God classes with thousands of lines are beyond what most LLMs can reason about reliably. Small units mean the AI reads only a few tokens to understand one task. That lowers the intrinsic load on the model. It then has more memory left for hard problems.
| Model | Advertised Window | Effective Window (Complex Tasks) | Efficiency % |
|---|---|---|---|
| Gemini 3 Pro | 10M tokens | ~9.2M tokens | 92% |
| Llama 4 Scout | 10M tokens | ~9.7M tokens | 97% |
| GPT-4.1 | 1M tokens | ~980K tokens | 98% |
| Claude 4 Sonnet | 200K tokens | ~178K tokens | 89% |
| DeepSeek V3 | 128K tokens | ~105K tokens | 82% |
Reducing Hallucinations via Explicit Context
The biggest threat to our code now is the hallucination: code that looks reasonable but is wrong. It happens when the AI lacks the context to make a deterministic choice, and ambiguous data structures and hidden dependencies usually cause that. I call the result hallucination debt.
Schema over Speculation: DTOs and Value Objects
DTOs and value objects are essential here. They describe the shape of data in a form a machine can read. Ask an AI to refactor an endpoint and it looks for the data contract first, so if the endpoint takes a generic array, the AI must guess every key and every type. Guessing is the root of most AI-generated bugs.
Symfony’s #[MapRequestPayload] with a strictly typed DTO hands the AI a schema. That schema is the source of truth. The symfony/object-mapper component goes further and hydrates immutable DTOs by constructor property promotion. The AI knows exactly which data to expect and how it’s validated. So it does not need to vibe code its own reading of the data structure.
Documentation as Metadata
Old style documentation explains what the code does, and an LLM can usually infer that. Today the docs must explain why: the intent and the trade-offs you can’t see in the syntax. In PHP that means using PHPDoc and attributes as metadata that tools can read, beyond plain comments.
The PHP 8.4 #[Deprecated] attribute is a good example. It tells the engine and the LLM which methods to avoid and what to use instead. That steers the AI to modern code while it refactors. Documenting side effects in property hooks works the same way. The AI then sees the ripple effects of a plain assignment.
Removing Ambiguity in Edge Cases
AI models are probabilistic. They pick the likely next token from their training. If the code does not handle an edge case explicitly, the AI will probably write a generic path that ignores your project’s safety rules. So favor exhaustive logic.
The match expression beats if-else chains for an AI. It forces a decision for every possible input, and I like that a lot. Pair a backed enum with match, and the AI spots a missing case at once. This declarative style shrinks the entropy gap, meaning the distance between accuracy and creativity. The AI stays on the correct logical path.
Code Structures AI “Likes”
The math behind transformers, self-attention in particular, favors some structures. Humans can untangle deep inheritance or tangled dependencies in the end. For an AI model they raise the perplexity, which is a measure of how confused it is.
Composition over Inheritance
Deep inheritance confuses an AI a lot. With a class five levels deep, the AI must hold the whole parent chain in memory to know which properties and methods exist. It then often invents methods that don’t exist, or forgets protected properties in the base class.
Composition is flat, and that is the main reason it works so well. You build objects from small, independent parts that implement specific interfaces, so you get a has-a relationship. An AI parses that more easily, as the constructor injects the dependencies explicitly. One file tells it which behaviors are available. That led to 17% higher accuracy in unit test generation, which is a real gain for little work.
Pure Functions and Predictability
A pure function returns a result that depends only on its input, and it has no side effects. Pure functions are the gold standard for code an AI can read. They have no state, so the AI can reason about each one on its own. That helps a lot when it writes code and tests.
In Symfony this means moving business logic out of god services. Put it into small, stateless domain experts. When a function is pure, the AI can write unit tests that are 100% accurate, as nothing external or global needs mocking.
Interface-Driven Development
An interface is the ultimate API contract for an AI. Give it an interface and it sees the surface area of a service with no need to read the code behind it. That helps in large codebases, where the full code of a service may not fit the context window.
Interfaces combined with the Symfony AI agent component allow tool-based reasoning. The AI treats a service as a tool with a defined input and output schema. It can then run big workflows and not get lost in the details of the code behind them.
| Structure | AI Sentiment | Reason |
|---|---|---|
| Composition | High | Flat structure; explicit dependencies; low memory overhead. |
| Pure Functions | High | Deterministic; easy to test; no context leaks. |
| Interfaces | High | Provides a concise “contract” or summary of intent. |
| Deep Inheritance | Low | High cognitive load; requires unrolling the tree; prone to hallucinations. |
| Magic Methods | Low | Ambiguous; requires multi-step resolution; breaks static analysis. |
Mastering the Prompt: Accuracy vs. Noise
The engineer’s role has moved from coder to architect and supervisor. The prompt is now the main way to state architectural intent. Vibe coding, which means vague natural language instructions, drives technical debt. Real work needs a context-rich approach, with the facts in the prompt.
The Failure of Vague Prompts
A prompt like “Add a feature to save user profiles” is a disaster for real code. The AI will likely:
- Guess the database schema.
- Use deprecated PHP functions.
- Ignore the security voters or rate limiters of your project.
- Write code that looks like it works but does not fit the existing design.
This mismatch creates silent debt. The code looks right and may even pass the tests. But it brings inconsistencies, and they cause a collapse of competence when a human has to maintain it later.
The Context-Rich Specification
Treat the AI like a junior developer who needs an exact specification. A good prompt includes:
- Environment Definitions: name PHP 8.4, Symfony 7.4 and strict types.
- Data Schemas: paste the DTO or entity structure straight into the prompt.
- Constraints: tell it to handle
UniqueConstraintViolationExceptionand return aResultobject. Say it must use theProfileRepositoryand not theEntityManagerdirectly. - Output Expectations: ask for a class that implements
ProfileSaverInterface.
This is spec-driven development (SDD), and it turns the prompt into an enforceable contract. Assumptions come out before the first line of code exists. That prevents drift.ach turns the prompt into an enforceable contract. It prevents “Drift” by making assumptions explicit before the first line of code is generated.
The VibeContract Pattern
Recent research suggests a pipeline of three stages for safe code generation, called VibeContract.
- Decompose: Break the high-level prompt into small, atomic tasks.
- Validate: Write machine-readable contracts (interfaces or schemas) for each task, and check them.
- Generate: Steer the code and the tests against those checked contracts.
With this method the architect’s intent limits the AI’s creativity. That lowers the risk of broken logic.gy ensures that the AI’s “creativity” is constrained by the architect’s “intent,” minimizing the risk of logical catastrophes.
The Risk Factor: The Danger of Blind Trust
The most dangerous effect of AI is the lull of correctness. AI code has perfect syntax and uses common code shapes, so devs often skip the deep review of the logic a solid system needs. Their understanding drops sharply. That is epistemic debt.
Cognitive Debt and the Collapse of Competence
Cognitive debt grows when a system gets more complex than a developer can follow. Some devs outsource logic to AI instead of offloading boilerplate. They skip the friction that builds a mental model of the system, and that friction is the whole point.
Recent studies show that devs who use AI can lose 17% of their conceptual understanding and debugging ability, even when they finish tasks faster, and they do. That creates fragile experts. They write huge amounts of code but can’t fix it when the AI makes a subtle error in logic. To pay this debt, teams must put shared understanding before velocity.
Security and Abstraction Leaks
AI models learn from historical data, and that data holds millions of insecure coding patterns. Unless you say otherwise, an AI may suggest outdated libraries or patterns with known holes, like SQL injection or weak password hashing.
In a Symfony project, security belongs in the design. That involves:
- Input Hardening: Use the DTO-first pattern to prevent mass assignment.
- Authorization Voters: Use Symfony voters, not ad hoc
ifchecks, to manage tricky access logic. - Rate Limiting: Apply multi-horizon compound rate limiting at the infrastructure level.
An AI can’t know these rules for your project, and it will not guess them right. Enforce them in the prompt and in peer review.
The Rubber Duck Fallacy in the AI Era
The rubber duck method matters more than ever. Before you merge AI code, you must be able to explain the logic back to yourself. If you can’t, you’ve lost cognitive ownership of the code. That ownership is the only defense against technical decay. Without it, a system turns into a black box that nobody understands.
| Risk Type | Description | Mitigation Strategy |
|---|---|---|
| Epistemic Debt | Loss of cognitive understanding of the system. | Mandatory code reviews and knowledge-sharing sessions. |
| Lull of Correctness | Blindly trusting code that looks “fine”. | Treat AI code with higher inspection than human code. |
| Abstraction Leak | AI ignoring global constraints or side effects. | Use strict architectural boundaries (e.g., deptrac). |
| Security Debt | Introduction of insecure/deprecated patterns. | Use “Secure by Design” framework features. |
Preventing Code Degradation
AI tools raise the volume of code by an estimated 3-4x. So human review is the bottleneck. To stop the design from eroding, teams need a four-layer prevention architecture.
Layer 1: Specification (SDD)
Define intent before you generate any code. Living specs, which you write in structured natural language and keep in version control, bring assumptions into the open. That blocks drift at the source.
Layer 2: Contract Enforcement
Schemas and formal contracts must be machine-enforceable. Tools like Pydantic, Zod or Symfony’s JSON Schema validation make sure that the AI’s output follows your data integrity rules at runtime.
Layer 3: Traceability
Link intent (the spec) and implementation (the code) in both directions. When a requirement changes, you should be able to find every affected module. That prevents orphaned logic, meaning code that exists but no longer serves its original purpose.
Layer 4: Validation
CI/CD pipelines must check more than compilation. They must confirm the AI honors the intent of the code, with exhaustive unit and integration tests. The Symfony MockAgent lets teams test AI integrations with no calls to real APIs, so the test suite stays reliable.
The Role of the Software Architect
An architect used to be a designer. Now the job is supervising silicon, and the value comes less from writing code than from three things:
- Maintaining the Source of Truth: Keep the theory of the software in the heads of the human team.
- Enforcing Consistency: Prevent the fragmented codebase that appears when several AI tools use different styles.
- Cognitive Offloading vs. Outsourcing: Teach the team to use AI for boilerplate (offloading) and keep hard logic (intrinsic load) a human-first job.
Summary: The Symbiotic Developer
Writing code that an AI can read is where our work is heading. AI agents can generate code in seconds, so the only code that survives is code built to be understood, verified and extended.
This does not make our code dumber. We just state our intent more clearly. With PHP 8.4 and Symfony 8, architects can build apps that suit both people and machines. The takeaways for any engineering team:
- Explicitness is Safety: Use strict typing, property hooks and backed enums to give the AI clear guardrails.
- Flat is Better than Deep: Favor composition and pure functions over deep inheritance and magic methods.
- Intent is the Source of Truth: Use spec-driven development so the AI follows the architect’s plan and not its own probabilistic guesses.
- Cognitive Ownership is Non-Negotiable: Never merge code you don’t own cognitively. Cognitive debt is the most expensive debt a company can carry.
AI does not replace the symbiotic developer. It makes them stronger. If our code is readable to both carbon and silicon, our software stays an asset for the next decade.
Technical Appendix: Memory and Performance Benchmarks
Code that an AI can read matters due to the physical and mathematical limits of modern hardware. The memory for an LLM’s working memory, the KV cache, grows with the length of the sequence. More tokens, more RAM.
We can estimate the memory complexity $M$ of a transformer model as:
Here L is the token length. For a 128,000 token codebase, the memory requirements are:
| Context Length | Approximate RAM Required | Use Case |
|---|---|---|
| 8K tokens | 4-8 GB | Medium conversations; short articles. |
| 32K tokens | 16-24 GB | Small to medium code projects. |
| 128K tokens | 64-96 GB | Entire technical documentation; large codebases. |
| 1M tokens | 200+ GB | Complete codebases with dependencies. |
Use property hooks to cut a class from 58 lines to 18 lines, and you save more than vertical space. You also cut the memory load on the AI agent. So it can pay more attention to high-level logic and less to parsing the extra code. This is why code that an AI can read is a performance optimization for the development process itself.
Check out llmfit, a terminal tool that matches LLM models to your specific hardware, so you don’t have to guess when you run AI locally. It scans your RAM and GPU and gives each model a best fit score across speed and quality. You only download models that will run well.