微信扫码
添加专属顾问
我要投稿
AI时代的软件设计需要重新思考:层层封装对AI不友好,上下文切割才是关键。 核心内容: 1. 传统分层架构在AI时代的局限性 2. AI处理代码的独特特点与需求 3. 上下文切割设计原则的实际应用案例
(以下内容95%为AI生成,故事也是)
上个月,一个朋友找我诉苦。他们团队花了大力气搭建了一套"完美"的分层架构——Repository层封装数据访问,Service层处理业务逻辑,再往上还有各种Manager、Handler,每一层都有精心设计的抽象接口。结果呢?让AI帮忙改个功能,愣是要把七八个文件的内容都贴进去,AI才能勉强理解这个功能在干啥。更要命的是,AI经常在错误的层次调用错误的方法,搞出一堆违反架构原则的代码。
"我们这架构设计得不好吗?"朋友问我。
我说:"你们的架构对人类很友好,但对AI来说是个灾难。"
这话怎么讲?这让我想起了自己最近踩过的几个坑。每个坑背后,都指向同一个问题:我们习以为常的软件设计原则,在AI时代可能需要重新思考了。
让我通过三个故事,分享一下我对AI时代软件设计的新理解。
先说说我朋友公司那个"完美"架构为什么会让AI抓狂。
这得从数据访问层的演进历史说起。最开始,我们直接写SQL:
SELECT * FROM users WHERE age > 18 AND city = 'Beijing'
后来觉得SQL太底层,搞出了HQL:
from User u where u.age > 18 and u.city = 'Beijing'
再后来,觉得字符串拼接容易出错,又封装了各种finder方法:
userRepository.findByAgeGreaterThanAndCity(18, "Beijing")
最后,为了更灵活的查询,又引入了Specification模式:
Specification<User> spec = Specification .where(UserSpecs.ageGreaterThan(18)) .and(UserSpecs.cityEquals("Beijing"));userRepository.findAll(spec);
看起来越来越"高级"了对吧?每一层都在试图简化上一层的复杂度。这种设计背后有个核心假设:上层的使用者能力不如下层的API设计者,所以需要通过封装来降低使用难度。
在人类主导开发的时代,这个假设是合理的。一个刚入行的程序员可能不太会写复杂的SQL,但调用个findByName
方法还是没问题的。团队里的架构师负责设计底层,初级开发者只需要会用就行。
但这套逻辑在AI时代彻底失效了。
AI跟人不一样,它的"能力"是均匀分布的。它写SQL的能力和调用高层API的能力没有本质差别。更关键的是,每增加一层封装,就增加了一层它需要理解的概念。
来看个实际的例子。假设我要实现一个功能:查找所有VIP用户中最近30天有购买行为的北京用户。
如果用原始SQL,我只需要给AI看这个:
SELECT u.* FROM users uJOIN orders o ON u.id = o.user_idWHERE u.level = 'VIP' AND u.city = 'Beijing' AND o.created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)GROUP BY u.id
AI一看就懂,直接就能帮你改。比如要加个条件"订单金额大于1000",它立刻就知道在WHERE里加AND o.amount > 1000
。
但如果用层层封装的架构呢?你得给AI解释:
UserRepository
是什么OrderService
是什么Specification
怎么构建最后你发现,光是让AI理解你的架构,就得贴进去一堆"框架知识"。而且更糟糕的是,AI经常会犯这样的错误:
// AI的错误代码
public class VIPUserService {
private UserRepository userRepository;
private OrderRepository orderRepository; // 不应该直接依赖Repository层!
public List<User> findActiveVIPUsers() {
// 违反了分层原则,Service不应该直接操作另一个领域的Repository
List<Order> recentOrders = orderRepository.findByCreatedAtAfter(date);
// ...
}
}
为啥会这样?因为AI看到了所有这些层,但它不理解为什么要分层,也不理解层与层之间的边界在哪。对它来说,这些都只是可以调用的代码而已。
有意思的是,这种层层封装的重量级框架,即便在人类主导的时代也没有取得压倒性胜利。
看看Java生态:
为啥?因为每一层抽象都是有成本的。这个成本包括:
而在AI时代,这个成本被放大了。因为AI就像一个永远的新人,每次对话你都要重新"教育"它。你的抽象层次越多,教育成本越高。
那么,AI时代我们该怎么设计软件呢?核心思路是:与其通过分层来降低局部复杂度,不如通过合理的边界切割来保证上下文的完整性。
什么意思?我们来看个对比。
Controller层 ↓Service层 ↓Repository层 ↓数据库
每一层都试图对上层隐藏复杂度,结果是理解一个完整的功能需要跨越多层。
用户管理领域 ←→ 订单处理领域 ←→ 支付领域 垂直切片 垂直切片 垂直切片
每个领域内部包含完整的功能实现,领域之间通过清晰的接口交互。
具体怎么做?不要这样组织代码:
src/ controllers/ services/ repositories/ models/
而要这样:
src/ user-management/ order-processing/ payment/ inventory/
到了代码里,也不要过分介意分层,必要的话,这样也没什么不行:
// order-processing/OrderService.js
class OrderService {
async createOrder(params) {
// 直接在这里写SQL,不要怕"不够高级"
const user = await this.db.query(`
SELECT id, level, balance FROM users WHERE id = ?
`, [params.userId]);
if (user.level !== 'VIP' && params.amount > 10000) {
throw new Error('非VIP用户单笔订单不能超过10000');
}
// 直接调用支付服务,不要经过七八个中间层
const payment = await this.paymentAPI.charge({
userId: params.userId,
amount: params.amount,
method: params.paymentMethod
});
// 直接插入订单,简单明了
const order = await this.db.query(`
INSERT INTO orders (user_id, amount, payment_id, status)
VALUES (?, ?, ?, 'paid')
`, [params.userId, params.amount, payment.id]);
return { orderId: order.insertId, paymentId: payment.id };
}
}
看到了吗?这个代码:
(这里只是拿常用分层举个例子,实际上,仅仅Java这种常用的分层,对AI来说还是比较熟悉的,但是你在常用分层上搞了自己的分层之后,才会开始碰到问题。所以当我们引入我们自己的设计的时候就要考虑了,怎么把相关上下文都放在一起,这样对于RAG工具也方便,自己构建上下文也简单)
第二个故事是我自己踩的坑。
我在做一个类似Deep Research的ChatBot系统时,需要处理两种"线程"(注意,这里的线程不是并发编程里的thread,而是像论坛里一个帖子和它的回复那样的thread,最早ChatGPT也管一个对话列表叫一个Thread):
最开始,我想:"都是对话线程嘛,肯定有共性,复用一下能省不少代码。"于是设计成这样:
// 初始设计:追求复用
class Thread {
messages: Message[];
stateHandler: StateHandler; // 主线程和子线程都用这个
async processNextMessage() {
return this.stateHandler.handle(this.messages);
}
}
class StateHandler {
handle(messages) {
// 根据当前状态决定如何处理
}
}
看起来很优雅对吧?主线程用StateHandler管理"澄清→生成"的状态转换,子线程用它管理ReAct的对话流程。一石二鸟!
结果呢?AI总是出错:
为啥会这样?因为主线程和子线程虽然都有"状态",但它们的本质完全不同:
强行复用,就像让一个交警既指挥马路上的车,又指挥流水线上的机器人。看起来都是"指挥",实际上完全是两码事。
后来我改成了概念隔离:
// 改进设计:概念隔离
class MainThread {
messages: Message[];
stateHandler: StateHandler; // 主线程专属,处理用户交互状态
phase: string; // 当前对话阶段
}
class SubThread {
messages: Message[];
messageGenerator: MessageGenerator; // 子线程专属,生成内部对话
interactionUnit: InteractionUnit; // 控制对话对的生成
}
// 明确不同的职责
class StateHandler {
// 只负责主线程的状态管理
async handleUserMessage(userMsg, currentPhase) {
switch(currentPhase) {
case 'clarification':
return this.clarifyIntent(userMsg);
case 'research':
return this.startResearch(userMsg);
}
}
}
class MessageGenerator {
// 只负责子线程的消息生成
async generateNextMessage(context) {
// 不需要考虑用户行为,只管生成下一步
return this.createToolCallMessage(context);
}
}
注意,我故意不让StateHandler和MessageGenerator有任何继承或接口关系。它们就是两个完全独立的概念。
神奇的事情发生了:改完之后,AI的正确率立刻飙升。它再也不会搞混这两种场景了,因为:
这个教训让我深刻理解了:在AI时代,清晰的概念边界比代码复用更重要。
第三个故事是关于接口设计的。
在编写Agent时,我经常需要用提示词生成对话内容。最开始,我严格遵循"最小知识原则":
// 初始设计:最小知识
async function generateUserResponse(recentMessageHistory, currentPhase, taskDescription) {
// 只传入绝对必要的参数
const prompt = buildPrompt(recentMessageHistory, currentPhase, taskDescription);
return await llm.generate(prompt);
}
// 使用时
const response = await generateUserResponse(
thread.messages.slice(-5), // 只传最近5条消息
thread.settings.phase, // 只传当前阶段
thread.settings.task // 只传任务描述
);
这看起来很"正确"——函数职责单一,参数精确,没有多余的依赖。
但问题来了:需求总是在变。今天产品说"生成回复时要考虑用户的偏好设置",明天又说"要根据对话的总时长调整语气"。每次需求变更,我都要:
更要命的是,当我想让AI帮我改时,光是解释清楚"这个参数是从哪来的"、"为什么要传这个不传那个",花的时间比我自己改还长。有次我跟AI解释了半天,它还是理解错了,最后我一怒之下自己改完了。
但是,人啊,一旦习惯了天上飞,就无法忍受在地上跑。我不甘心回到手写代码的时代,于是开始反思:为什么我们需要最小知识原则?
传统的理由有两个:
但对AI来说,第二点几乎不是问题。你在提示词里说"只读取,不要修改",AI比人类同事还听话。
那就只剩下耦合的问题了。
可是仔细想想,过度的参数限制本身也是一种耦合——你把本来内聚的数据强行拆开,反而增加了调用者的负担。
所以我现在的做法是:
// 新设计:完整上下文
async function generateUserResponse(context) {
// 传入完整的上下文对象
const { thread, settings, user } = context;
// 函数内部决定用什么,而不是调用者决定传什么
const prompt = buildPrompt({
messages: thread.messages,
phase: settings.phase,
task: settings.task,
userPreferences: user.preferences, // 新需求?没问题,数据已经在这了
conversationDuration: thread.duration, // 又来新需求?数据也在
// ... 未来可能用到的任何数据
});
return await llm.generate(prompt);
}
// 使用时超级简单
const response = await generateUserResponse({ thread, settings, user });
这样设计的好处:
你可能会说:"这不是违反了软件设计的基本原则吗?"
是的,但请记住:这些原则是在人工编码时代总结出来的。当AI可以在几秒钟内重写整个函数时,"解耦"的价值就没那么大了。相反,让AI理解你的意图变得更重要。
而且说实话,当我们发现真的有耦合问题时,让AI重写也就是一句话的事。与其提前做过度设计,不如出现问题时快速重构。这就是AI时代的新玩法。
经历了这三个故事,我终于明白了:这三个看似不同的问题,其实都是关于上下文切割和管理的问题,只是发生在不同的层面:
而AI最需要的恰恰是完整、清晰、独立的上下文。
基于这些经验,我总结出了AI时代的三个设计原则:
传统的技术分层:
src/ controllers/ services/ repositories/ models/
会把一个完整的业务逻辑分散到多个技术层中。而基于领域的组织:
src/ user-management/ ├── api/ # 对外接口 ├── business/ # 业务逻辑 └── data/ # 数据访问 order-processing/ # 或者传统的,问题也不大 ├── controllers/ ├── services/ ├── repositories/ └── models/
能让每个领域保持完整的上下文。
但现实中,很多框架都有自己的约定。比如Next.js要求特定的文件结构,Spring Boot有包结构规范。这时怎么办?用文档来弥补。
比如在Next.js项目中,我们就采用了类似下面的文档来说明:(注:这里只是一个例子,我们后来发现了更好的结构,但是一开始这个也能用,反而能说明AI能驾驭复杂的跨文件夹定义)
## 订单处理模块架构
本模块的代码分布在以下位置(Next.js框架约定):
app/orders/ # 页面路由
app/api/orders/ # API路由
├── route.ts # 订单列表接口
└── [orderId]/
└── route.ts # 单个订单接口
components/orders/ # UI组件
├── order-list.tsx # 订单列表组件
├── order-detail.tsx # 订单详情组件
└── order-form.tsx # 订单表单组件
lib/orders/ # 核心业务逻辑
├── order-service.ts # 订单处理核心逻辑
├── validations.ts # 订单验证规则
├── order-repo.ts # 数据访问层
└── order-types.ts # 类型定义
### 关键说明
- 核心业务逻辑都在 lib/orders/ 目录
- app/api/orders/ 只是的HTTP接口层
- components/orders/ 是纯UI组件,不含业务逻辑
- 修改订单处理逻辑时,主要关注 lib/orders/
- .....
通过这样的文档,AI能清楚地理解:虽然代码物理上是分散的,但逻辑上属于同一个业务领域。
这个原则包含几个要点:
StateHandler
(主线程)vsMessageGenerator
(子线程)记住:AI需要的是清晰的信号,而不是抽象的优雅。当AI看到OrderService
时,它应该立刻知道这是处理订单的;看到UserAuthenticator
时,知道这是验证用户的。不要让它猜测AbstractHandler
到底是干什么的。
传统设计追求"最小知识":
// 传统设计:参数最小化function processPayment(paymentId: string): boolean;
AI友好的设计提供完整上下文:
// AI友好设计:上下文完整function processPayment(context: { order: { id: string; userId: string; amount: number; items: Array<{productId: string; quantity: number}>; }; user: { id: string; level: 'regular' | 'vip'; balance: number; }; paymentMethod: 'credit' | 'debit' | 'wallet'; settings: SystemSettings;}): { success: boolean; transaction: Transaction; notifications: Notification[];}
是的,参数变"肥"了,但好处是:
AI时代的软件设计需要我们重新思考很多"常识"。层层封装、抽象复用、最小知识这些传统的"最佳实践",在AI面前可能反而成了负担。
记住:AI不需要你降低复杂度,它需要的是完整的上下文。
与其费尽心思设计精妙的抽象层次,不如老老实实把相关的代码放在一起。与其追求极致的复用,不如保持概念的清晰隔离。与其限制参数保持"纯洁",不如提供完整的信息让AI自由发挥。
这可能违反你学过的设计原则,但这可能是新时代的高效原则。
下次当你设计系统时,不妨问自己:
软件设计是一门艺术,在AI时代也需要审美,只不过可能是新的审美。而上下文切割,就是这种新审美的核心。
当然,这并不意味着我们要完全抛弃传统的设计智慧。在某些场景下,分层和抽象依然有其价值。关键是要识别什么时候该为人类优化,什么时候该为AI优化。这种平衡的艺术,或许才是未来软件架构师最重要的能力。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-06-17
火山引擎AI大爆发,一个人=一家公司的时代来了!
2025-06-17
160行需求文档,32分钟变成可玩游戏,我见证了AI开发的恐怖实力
2025-06-17
字节对 AI Coding 的理解,够深刻。
2025-06-17
长提示词:在私有代码库上拥有许愿体验 - 许愿驱动开发(Vibe Coding)S2-2
2025-06-17
你用ChatGPT的方式,可能全错了:把它当“实习生”管,别当“搜索引擎”用
2025-06-17
我为 Fortune 500 企业写大模型提示词:一线 Agent 编排实战经验总结
2025-06-17
企业效率大提升!Agentic Workflows带来自动化新突破
2025-06-17
AI产品的诞生
2025-05-29
2025-03-20
2025-03-21
2025-04-11
2025-03-20
2025-03-20
2025-03-20
2025-03-21
2025-04-01
2025-04-12