In the previous two chapters, we learned to use "documentation" to set global laws for AI, and "negative space design" to carve out the boundaries of specific tasks. We now possess powerful "soft constraint" capabilities. However, when the project scale swells and the codebase becomes a behemoth with hundreds of files and tens of thousands of lines of code, you will find that even with perfect documentation, AI will still show signs of "confusion," "forgetfulness," and even "mental breakdown" when facing this "monster."
It might, in a seemingly simple modification, reference a completely unrelated module; or when refactoring a function, forget that it was called from some obscure corner five files away. The root of the problem is not that our instructions are unclear, but that we have stuffed far more information into AI's brain at once than its "cognitive bandwidth" can handle.
In this chapter, we will learn how to apply the most classic and powerful idea in software engineering -- modularization and decoupling -- to solve this problem. But this is not a retread of old ground. We will re-examine and apply these principles from a brand-new perspective: "AI-friendly architecture."
Our goal is to break down that vast, daunting codebase into a series of independent "small problems" that AI can easily understand and handle, through careful physical code-structure design. We need to decouple not only "code," but also "AI's attention."
5.1 Why Does AI "Go Crazy" in Large Codebases?
To understand how to cure AI's "madness," we must first diagnose its cause. The various "idiotic" behaviors AI exhibits when facing large codebases are not because it is truly "stupid," but stem from fundamental differences between its underlying working principles and the human mind.
Cause One: The "Flatness Curse" of Context
When a human engineer reads a large project, their brain automatically constructs a hierarchical, weighted mental model.
- We quickly identify which are "core modules" (like domain models, main services) and which are "edge modules" (like utility functions, UI components).
- We know that modifying core modules requires extreme caution, while modifying an edge UI component is relatively safe.
- We subconsciously ignore the code unrelated to the current task, tightly focusing our attention on the small relevant portion.
AI does not have this ability. To it, all the code context you provide is a flat, undifferentiated sequence of tokens. A configuration item in config.js carries the same importance, in its "eyes," as the core business logic in UserService.js. It lacks an understanding of the code's "macro structure" and "semantic importance."
This "flatness curse" leads to:
- Attention dilution: When there is too much contextual information, AI's attention is diluted by a large number of irrelevant details. It is like asking a person to listen to a hundred people talking at once -- they end up hearing no one clearly.
- False correlation: In the flat token ocean, AI might wrongly associate two completely unrelated modules due to some superficial, lexical similarity, leading to absurd modifications.
[Real-World Nightmare] (an illustrative case built from common incident patterns)
You ask AI to modify a color variable in a CSS style file. Because the context includes the entire project's files, AI, during its analysis, notices that the backend database configuration file db.config.js also contains a string named 'primary-color' (possibly in a comment or test data). As a result, it not only modifies the CSS file but also "helpfully" changes the string in the database configuration file, causing the entire backend service to fail to connect to the database. This is a classic case of "false correlation."
Cause Two: The "Cognitive Black Hole" of Implicit Dependencies
A poorly designed large project is often full of various "implicit dependencies."
- One module modifies the state of another module through a global variable.
- A function depends on a certain environment variable having a specific value at a specific moment.
- Two modules communicate through a shared, undeclared event bus.
Human developers might remember these "minefields" through "project experience" and "word of mouth." But AI knows nothing about them. These implicit dependencies are "cognitive black holes" for AI. It simply cannot see any connection between these two modules in the literal code.
When AI modifies one of these modules, it cannot predict that this change, like a stone thrown into water, will affect another distant module through invisible ripples, eventually causing the system to crash in some unexpected place.
[Real-World Nightmare]
In your frontend project, there is an authStore.js responsible for user authentication. After a user logs in, it mounts a global currentUser object on the window object. A dozen components in the project implicitly depend on the existence of window.currentUser. Now, you ask AI to refactor authStore.js to use the more modern Context API. AI completes the task brilliantly, but it does not know of the existence of this "black magic" called window.currentUser, and so naturally deletes that line of code. As a result, all those dozen components crash after the user logs in because they cannot read currentUser.
Cause Three: The "Physical Ceiling" of Token Limits
This is the most direct, most physical limitation. All large models have a token cap for their context window. Even with models like Claude 3 that have 200K or even 1M super-long contexts, this cap still exists.
When your project's total code volume exceeds this cap, you cannot feed all the information to AI at once. You have to manually select the "relevant" files. But this process of "selection" itself is a huge mental burden and is highly prone to error. You might very well miss a key dependent file, thereby misleading AI into making the wrong decision.
More importantly, even if you do not hit the token cap, the longer the context, the more AI's "reasoning cost" and "error probability" rise markedly. In a super-long conversation stuffed with 200K tokens, AI's "attention" will seriously degrade. It might very well forget an important constraint you set at the beginning of the conversation. In the research literature this is known as the "needle in a haystack" problem -- models use information in the middle of the context least effectively (see Stanford's Lost in the Middle study, arxiv.org/abs/2307.03172), and longer contexts do not automatically mean better performance.
Conclusion: The fundamental way to combat AI's "madness" is not to train a smarter AI, nor to hope for infinitely long context windows. It is to start from our code architecture itself, transforming that huge, flat, implicitly-dependency-filled "cognitive swamp" into an orderly world composed of many small, independent, clearly-interface-defined "cognitive building blocks."
This is the new mission of modular decoupling in the AI era.
5.2 Separating the Main Flow from Supporting Capabilities
When performing modular decoupling, the most common mistake is to divide by "technical type" or "functional page." For example, putting all API requests in one folder and all UI components in another. This way of dividing has some effect, but it does not touch the core of the problem.
A more profound decoupling model that better matches AI's mental model is what I call "capability-driven decoupling," or the "main flow-capability" pattern.
The core idea of this pattern is to break a complex business function into an extremely concise, stable "main flow," and a series of pluggable, independent "supporting capabilities."
- Main flow: It is responsible for only one thing -- orchestrating the supporting capabilities in the highest-level, most business-language-like way to complete a full business closed loop. The main flow itself contains no specific implementation details. It should be extremely stable and rarely changed.
- Supporting capabilities: Each "capability" is an independent module that encapsulates a specific technical implementation, such as "sending a request to the API," "storing data locally," "parsing a PDF file," "displaying a notification popup," and so on. These capability modules are replaceable, independently testable, and independently modifiable by AI.
[A Vivid Metaphor]
Imagine you are directing a movie shoot.
- Main flow (the director's work): Your script says: "Scene 1: The protagonist appears with a grave expression. Scene 2: An explosion occurs. Scene 3: The protagonist discovers a clue in the ruins." This script is your main flow. It only cares about "what" happens, not "how" it happens.
- Supporting capabilities (the various specialist teams):
- Acting team (Capability A): Responsible for realizing "the protagonist appears with a grave expression."
- Special effects team (Capability B): Responsible for realizing "the explosion."
- Props team (Capability C): Responsible for realizing "the clue in the ruins."
As the director, you only need to call on these three teams. If you are not satisfied with the explosion effect, you do not need to modify the script. You just call the special effects team over and say to them: "Redo this explosion. I want a more stunning effect." The special effects team's work will not affect the acting or props teams at all.
AI programming should be the same. We, as the "director" (the decision maker), should let AI deal separately with each "specialist team" (capability module), rather than letting it face a chaotic film set.
Practical Exercise: Refactoring a "User Registration" Feature
Before Refactoring: A Bloated "Monster" Function (AI's Nightmare)
// A monolithic, AI-unfriendly function
async function handleUserRegistration(formData) {
// 1. Validation
if (!formData.email || !formData.password) {
// Directly manipulating UI state
showError("Email and password are required.");
return;
}
if (formData.password !== formData.confirmPassword) {
showError("Passwords do not match.");
return;
}
// 2. API Call (tightly coupled)
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: formData.email, password: formData.password })
});
if (!response.ok) {
const errorData = await response.json();
showError(errorData.message);
return;
}
const userData = await response.json();
// 3. Local Storage (tightly coupled)
localStorage.setItem('authToken', userData.token);
localStorage.setItem('user', JSON.stringify(userData.user));
// 4. Analytics (tightly coupled)
analytics.track('User Registered', { userId: userData.user.id });
// 5. Navigation (tightly coupled)
router.push('/dashboard');
} catch (error) {
showError("An unexpected error occurred.");
// 6. Logging (tightly coupled)
logErrorToServer(error);
}
}
This function is a classic "cognitive swamp." It tightly couples at least six different "capabilities" together: validation, API, local storage, analytics, navigation, and logging. If you ask AI to modify any part (e.g., "replace localStorage with sessionStorage"), AI's "flat" vision needs to understand all six parts simultaneously, making it highly error-prone.
After Refactoring: The "Main Flow-Capability" Pattern (AI's Heaven)
Step One: Extract the "Capability" Modules
We create a series of independent capability modules, each with a single responsibility.
// capabilities/validator.js
export function validateRegistrationForm(formData) {
if (!formData.email || !formData.password) return { isValid: false, message: "..." };
// ...
return { isValid: true };
}
// capabilities/authApi.js
export async function registerUser(email, password) {
const response = await fetch('/api/register', { ... });
// ...
return await response.json();
}
// capabilities/session.js
export function saveSession(token, user) {
localStorage.setItem('authToken', token);
localStorage.setItem('user', JSON.stringify(user));
}
// capabilities/analytics.js
export function trackRegistration(userId) {
analytics.track('User Registered', { userId });
}
// capabilities/navigation.js
export function redirectToDashboard() {
router.push('/dashboard');
}
// ... etc.
Notice that each capability module is pure, independent, and easy to test. Asking AI to modify session.js requires only giving it this one file and its test file, without providing any context about other business logic.
Step Two: Rewrite the "Main Flow"
Now, using the "director's" perspective, we rewrite the handleUserRegistration function.
// main-flows/userRegistration.js
import { validateRegistrationForm } from '../capabilities/validator';
import { registerUser } from '../capabilities/authApi';
import { saveSession } from '../capabilities/session';
import { trackRegistration } from '../capabilities/analytics';
import { redirectToDashboard } from '../capabilities/navigation';
import { notifyError } from '../capabilities/notifier';
import { logError } from '../capabilities/logger';
// A clean, AI-friendly main flow
export async function handleUserRegistration(formData) {
const validation = validateRegistrationForm(formData);
if (!validation.isValid) {
return notifyError(validation.message);
}
try {
const { token, user } = await registerUser(formData.email, formData.password);
saveSession(token, user);
trackRegistration(user.id);
redirectToDashboard();
} catch (error) {
notifyError("Registration failed. Please try again.");
logError(error);
}
}
Do you see the beauty of this "main flow"?
- Extremely concise: It contains no implementation details, only calls to capabilities. The line count is greatly reduced.
- Business language: It reads like a business requirements document, not technical code. Both AI and humans can easily grasp its intent.
- Highly stable: As long as the "user registration" business process does not change, this file rarely needs modification. If we need to change the API address, we modify
authApi.js; if we need to change the navigation destination, we modifynavigation.js. The main flow file itself stays as closed to modification as possible.
How Does AI Work Under This Architecture?
Now, when you want AI to modify a feature, your instructions become incredibly precise and safe.
- Old instruction (dangerous): "In the user registration feature, replace
localStoragewithsessionStorage." (AI needs to carefully find those two lines of code in that bloated 200-line function while praying it does not affect any other logic.) - New instruction (safe): "Here is the
capabilities/session.jsfile. Please replace all instances oflocalStoragewithsessionStoragein it." (AI faces a small file with only a few lines of code; the task is clear, and the chance of error is much lower.)
Through the "main flow-capability" pattern of decoupling, we create a "minimum cognitive unit" for AI. We only let it handle one "capability" module at a time, which allows us to minimize the amount of context code we provide to it, thereby fundamentally avoiding every cause of AI "going crazy" in large codebases.
5.3 An AI-Friendly Project Structure with High Cohesion and Low Coupling
The "main flow-capability" pattern is a logical decoupling idea. To make it physically real, we need to design an AI-friendly project directory structure that matches it.
The core goal of an "AI-friendly" directory structure is to concentrate "concerns" physically and make "dependencies" physically visible. In other words, it embodies the classic design principle: high cohesion, low coupling.
Three Principles of an AI-Friendly Directory Structure
Principle One: Organize by "Business Domain" Rather Than "Technical Type"
Traditional project structures tend to organize like this:
/
├── components/ (All UI components)
├── services/ (All API services)
├── stores/ (All state management)
└── utils/ (All utility functions)
The biggest problem with this structure is "low cohesion." When you need to develop a "User Management" feature, you have to jump back and forth among the components, services, and stores folders. If you want AI to help you develop it, you have to throw all the relevant files from these three folders at it, creating a huge contextual burden.
An AI-friendly structure should be organized by "business domain" or "functional feature":
/
├── features/
│ ├── UserManagement/
│ │ ├── components/ # UI components belonging only to user management
│ │ ├── hooks/ # Business logic belonging only to user management
│ │ ├── userApi.js # API client belonging only to user management
│ │ ├── userStore.js # State belonging only to user management
│ │ └── index.js # Exports the feature's public interface
│ ├── OrderManagement/
│ │ ├── ...
│ └── ...
└── shared/
├── components/ # Common components shareable across all features (Button, Input...)
├── hooks/ # Common hooks shareable across all features (useAuth, useApi...)
└── ...
Under this structure, when you ask AI to develop the "User Management" feature, its "cognitive boundary" is physically limited to the features/UserManagement/ folder. You only need to give it the context of this folder, and it gets all the information needed to complete the task, without being distracted by any code in OrderManagement. This is high cohesion.
Meanwhile, UserManagement and OrderManagement have no direct dependency between them. They can only communicate through modules shared in the shared folder. This is low coupling.
Principle Two: Explicitly Declare "Public Interfaces"
Each business domain module (e.g., UserManagement) should have an index.js file as its only "exit." This file explicitly exports the parts that the module wishes to be used by the outside world (other modules or the main application entry).
// features/UserManagement/index.js
// Only export high-level components and hooks, not internal implementation details
export { UserListPage } from './pages/UserListPage';
export { useUserStore } from './userStore';
Other files inside the module, like components/UserTable.js, should not be directly referenced from outside.
The benefits of this approach are:
- Stable interface: It defines a clear, stable "public contract" for the module. As long as the exports in
index.jsdo not change, the module's internal implementation can be freely refactored without worrying about breaking external dependencies. - Reduced AI cognitive load: When AI needs to use the
UserManagementfeature, you do not need to show it the module's complete source code. You simply tell it: "You can importUserListPageanduseUserStorefromfeatures/UserManagement." This is like giving AI a simple, easy-to-read "API document" rather than a thick "complete source code collection."
Principle Three: Eradicate "Magic Dependencies" and "Side Effects"
What AI fears most are those invisible, implicit dependencies. An AI-friendly project must strive to make all dependencies explicit.
- No global variables: Any shared state between modules must be passed through an explicit state manager (like Redux, Zustand) or the Context API.
- Dependency injection: If a module depends on another module, the dependency should be "injected" through constructor or function parameters, rather than being directly
imported inside the module. This makes the dependency relationship immediately clear and extremely easy to mock in tests. - Pure functions first: Write "pure functions" whenever possible -- functions with no side effects that always return the same output for the same input. Pure functions are the easiest code units for AI to understand and reason about, because they are completely independent and self-contained.
An AI-friendly project structure is not just about making things clearer for humans. It is also an active "scaffolding" tailored specifically to AI's "cognitive shortcomings." Through physical isolation, it forcibly reduces the information complexity AI must handle at any given moment, thereby channeling AI's powerful computing capacity toward where we need it, safely and for value creation.
[Refactoring Steps] Transform Your Existing Project into an AI-Friendly Structure in 30 Minutes
The theory sounds great, but when faced with an existing, messy project, where do you start? Do not worry. Refactoring does not require throwing everything away. Follow these steps and you can significantly improve the "AI-friendliness" of your existing project within half an hour.
Prerequisite: Your project already uses some form of modular system (e.g., ES Modules, CommonJS).
Phase 1: Identify and Group (10 minutes)
In this phase, we only do "archaeology" and "planning" -- without touching a single line of code.
- Print the directory tree: Print the complete file structure of your project's
srcdirectory to a text file. - "Color" the business domains: Open this text file and begin identifying business domains using different colors or markers.
- Which files relate to "user"? (
UserList.jsx,api/user.js,stores/user.js...) Mark them yellow. - Which files relate to "order"? (
OrderTable.jsx,pages/OrderDetail.jsx,services/order.js...) Mark them blue. - Which files are "common," used by all domains? (
components/Button.jsx,utils/formatDate.js...) Mark them green.
- Plan the new structure: At the bottom of the text file, start planning the new directory structure based on your "coloring" results.
// New Structure Plan
/src
/features
/User/ (all yellow files go here)
/Order/ (all blue files go here)
/shared (all green files go here)
/lib (external library configs, e.g., axios instance)
/pages (if you use a file-based router like Next.js)
/styles (global styles)
App.jsx
main.jsx
Phase 2: Physical Migration (15 minutes)
Now, we start moving. This process is mechanical but very important.
- Create new directories: Create new folders such as
features/User,features/Order, andsharedin your project according to the structure you just planned. - Move files: Based on your "coloring" results, batch-move old files into the new folders. At this point do not modify any code content -- only move files.
components/UserList.jsx→features/User/components/UserList.jsxservices/order.js→features/Order/orderApi.js(you can rename it along the way)components/Button.jsx→shared/components/Button.jsx
- Fix import paths: After moving files, your project will definitely report errors because it cannot find the modules. This is the most critical step. Start your application, open the browser's developer tools, or run your build command. It will tell you which files have wrong import paths.
- Let AI help you! This is a pattern-based task that AI can handle perfectly. You can copy all the code from a file and say to AI: "This file has been moved from
AtoB. Please check and fix the relative paths of all itsimportstatements." AI will handle this very efficiently. - Or, use the "Find and Replace" feature of an IDE like VSCode to batch-fix import paths.
Phase 3: Establish "Public Interfaces" (5 minutes)
Finally, build clear "city walls" for our new feature modules.
- Create
index.jsfiles: Create anindex.jsfile in eachfeaturesdirectory (e.g.,features/User/). - Export the public parts: Check the files in this module and ask yourself: which parts need to be used outside the module (e.g., by
App.jsxor anotherfeature)? These are usually page-level components, state management stores, or high-level hooks. Export them inindex.js.
// features/User/index.js
export { UserPage } from './components/UserPage';
export { useUser } from './hooks/useUser';
- Refactor external imports: Now, go to the places that use the
Usermodule's functionality (e.g., your main routing file) and change the original deep, messy imports to imports fromindex.js.
- Old (coupled):
import { UserPage } from './features/User/components/UserPage';
import { useUser } from './features/User/hooks/useUser';
- New (decoupled):
import { UserPage, useUser } from './features/User';
Congratulations! After these 30 minutes (a rough estimate for a mid-sized project; scale up or down with project size and chaos), your project may not have changed in functionality, but it has made a qualitative leap in "AI-friendliness." You now have:
- Physically isolated cognitive units: You can safely let AI focus on the
features/Userdirectory without worrying that it will mess up the order logic. - Clear dependency relationships: Dependencies between modules are now channeled through clear
index.jsinterfaces, greatly reducing the possibility of AI creating "cognitive black holes."
This half-hour investment will save you incalculable time and mental energy over the hundreds of hours of AI collaboration ahead. You have created a clean, orderly workstation that your "super intern" can understand and work in efficiently. Now, you two can truly begin creating value together.