支持私有化部署
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


AI时代的软件设计:上下文切割的艺术

发布日期:2025-06-17 08:25:49 浏览次数: 1540
作者:铜剑技校

微信搜一搜,关注“铜剑技校”

推荐语

AI时代的软件设计需要重新思考:层层封装对AI不友好,上下文切割才是关键。

核心内容:
1. 传统分层架构在AI时代的局限性
2. AI处理代码的独特特点与需求
3. 上下文切割设计原则的实际应用案例

杨芳贤
53A创始人/腾讯云(TVP)最具价值专家

(以下内容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不喜欢层层封装?

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的错误代码@Servicepublic class VIPUserService {    @Autowired    private UserRepository userRepository;    @Autowired    private OrderRepository orderRepository;  // 不应该直接依赖Repository层!
    public List<UserfindActiveVIPUsers() {        // 违反了分层原则,Service不应该直接操作另一个领域的Repository        List<Order> recentOrders = orderRepository.findByCreatedAtAfter(date);        // ...    }}

为啥会这样?因为AI看到了所有这些层,但它不理解为什么要分层,也不理解层与层之间的边界在哪。对它来说,这些都只是可以调用的代码而已。

历史总在重演

有意思的是,这种层层封装的重量级框架,即便在人类主导的时代也没有取得压倒性胜利。

看看Java生态:

  • Hibernate那么成熟,JPA + Spring Data那么"先进",但MyBatis这种"土"框架反而用的人更多
  • EJB那么"企业级",最后还是被Spring干掉了
  • SOAP那么"标准",最后大家都去用REST了

为啥?因为每一层抽象都是有成本的。这个成本包括:

  1. 学习成本:新人要学习你发明的概念
  2. 理解成本:出问题时要穿透层层抽象找到根因
  3. 约束成本:高层抽象往往意味着灵活性的丧失

而在AI时代,这个成本被放大了。因为AI就像一个永远的新人,每次对话你都要重新"教育"它。你的抽象层次越多,教育成本越高。

上下文切割:AI时代的设计新思路

那么,AI时代我们该怎么设计软件呢?核心思路是:与其通过分层来降低局部复杂度,不如通过合理的边界切割来保证上下文的完整性。

什么意思?我们来看个对比。

传统的分层设计

Controller层    ↓Service层      ↓Repository层    ↓数据库

每一层都试图对上层隐藏复杂度,结果是理解一个完整的功能需要跨越多层。

AI友好的领域切割

用户管理领域 ←→ 订单处理领域 ←→ 支付领域   垂直切片        垂直切片        垂直切片

每个领域内部包含完整的功能实现,领域之间通过清晰的接口交互。

具体怎么做?不要这样组织代码:

src/  controllers/  services/  repositories/  models/

而要这样:

src/  user-management/  order-processing/  payment/  inventory/

到了代码里,也不要过分介意分层,必要的话,这样也没什么不行:

// order-processing/OrderService.jsclass 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 };  }}

看到了吗?这个代码:

  1. 没有复杂的分层,但逻辑清晰
  2. AI只需要看这一个文件就能理解完整的订单创建流程
  3. 需要修改时,上下文就在眼前

(这里只是拿常用分层举个例子,实际上,仅仅Java这种常用的分层,对AI来说还是比较熟悉的,但是你在常用分层上搞了自己的分层之后,才会开始碰到问题。所以当我们引入我们自己的设计的时候就要考虑了,怎么把相关上下文都放在一起,这样对于RAG工具也方便,自己构建上下文也简单)

故事二:概念复用的陷阱

第二个故事是我自己踩的坑。

我在做一个类似Deep Research的ChatBot系统时,需要处理两种"线程"(注意,这里的线程不是并发编程里的thread,而是像论坛里一个帖子和它的回复那样的thread,最早ChatGPT也管一个对话列表叫一个Thread):

  • 主线程:用户和AI的主对话,需要根据不同阶段采用不同的处理策略。比如先澄清用户意图,再生成研究报告。
  • 子线程:在生成报告时,用ReAct技术模拟的内部对话,AI需要自问自答来调用各种工具。

最开始,我想:"都是对话线程嘛,肯定有共性,复用一下能省不少代码。"于是设计成这样:

// 初始设计:追求复用class Thread {  messagesMessage[];  stateHandlerStateHandler;  // 主线程和子线程都用这个
  async processNextMessage() {    return this.stateHandler.handle(this.messages);  }}
class StateHandler {  handle(messages) {    // 根据当前状态决定如何处理  }}

看起来很优雅对吧?主线程用StateHandler管理"澄清→生成"的状态转换,子线程用它管理ReAct的对话流程。一石二鸟!

结果呢?AI总是出错:

  • 在处理子线程时,它会试图等待用户输入(主线程的行为)
  • 在处理主线程时,它会自动生成user消息(子线程的行为)
  • 更离谱的是,它会在子线程里切换到"澄清"状态

为啥会这样?因为主线程和子线程虽然都有"状态",但它们的本质完全不同:

  • 主线程的状态管理是为了应对用户的不确定行为,需要预测和引导
  • 子线程的状态其实是固定的对话模式,行为是完全可预期的

强行复用,就像让一个交警既指挥马路上的车,又指挥流水线上的机器人。看起来都是"指挥",实际上完全是两码事。

后来我改成了概念隔离:

// 改进设计:概念隔离class MainThread {  messagesMessage[];  stateHandlerStateHandler;      // 主线程专属,处理用户交互状态  phasestring;                   // 当前对话阶段}
class SubThread {  messagesMessage[];  messageGeneratorMessageGenerator;    // 子线程专属,生成内部对话  interactionUnitInteractionUnit;      // 控制对话对的生成}
// 明确不同的职责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的正确率立刻飙升。它再也不会搞混这两种场景了,因为:

  1. 看到StateHandler就知道是在处理用户交互
  2. 看到MessageGenerator就知道是在生成内部对话
  3. 没有模糊的"通用"概念来迷惑它

这个教训让我深刻理解了:在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         // 只传任务描述);

这看起来很"正确"——函数职责单一,参数精确,没有多余的依赖。

但问题来了:需求总是在变。今天产品说"生成回复时要考虑用户的偏好设置",明天又说"要根据对话的总时长调整语气"。每次需求变更,我都要:

  1. 修改函数签名
  2. 找到所有调用的地方
  3. 修改调用代码
  4. 祈祷没有漏掉哪里

更要命的是,当我想让AI帮我改时,光是解释清楚"这个参数是从哪来的"、"为什么要传这个不传那个",花的时间比我自己改还长。有次我跟AI解释了半天,它还是理解错了,最后我一怒之下自己改完了。

但是,人啊,一旦习惯了天上飞,就无法忍受在地上跑。我不甘心回到手写代码的时代,于是开始反思:为什么我们需要最小知识原则?

传统的理由有两个:

  1. 防止耦合:参数少,依赖就少
  2. 防止误操作:不该改的数据,就不要传进去

但对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 });

这样设计的好处:

  1. 需求变更时,大部分情况下只需要改函数内部
  2. AI能看到完整上下文,更容易理解和修改
  3. 调用代码极其稳定,不会因为加个参数就到处改

你可能会说:"这不是违反了软件设计的基本原则吗?"

是的,但请记住:这些原则是在人工编码时代总结出来的。当AI可以在几秒钟内重写整个函数时,"解耦"的价值就没那么大了。相反,让AI理解你的意图变得更重要。

而且说实话,当我们发现真的有耦合问题时,让AI重写也就是一句话的事。与其提前做过度设计,不如出现问题时快速重构。这就是AI时代的新玩法。

上下文切割:三个层面的思考

经历了这三个故事,我终于明白了:这三个看似不同的问题,其实都是关于上下文切割和管理的问题,只是发生在不同的层面:

  1. 架构层面:层层封装打散了业务逻辑的上下文
  2. 概念层面:强行复用混淆了不同领域的上下文
  3. 接口层面:参数最小化割裂了数据的上下文

而AI最需要的恰恰是完整、清晰、独立的上下文。

AI时代的设计原则

基于这些经验,我总结出了AI时代的三个设计原则:

1. 领域完整性优于技术分层

传统的技术分层:

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能清楚地理解:虽然代码物理上是分散的,但逻辑上属于同一个业务领域。

2. 概念清晰性优于代码复用

这个原则包含几个要点:

  1. 不同领域的概念要明确区分,即使它们看起来相似
  2. 命名要体现领域特征,比如StateHandler(主线程)vsMessageGenerator(子线程)
  3. 避免过度抽象,宁可有适度的代码重复也要保持概念独立,比起代码的复用,边界的清晰更有价值
  4. 如无必要,接口不要有继承关系,除非它们真的是同一个概念的不同实现,而且你几乎不会在文档或提示词里同时使用到他们

记住:AI需要的是清晰的信号,而不是抽象的优雅。当AI看到OrderService时,它应该立刻知道这是处理订单的;看到UserAuthenticator时,知道这是验证用户的。不要让它猜测AbstractHandler到底是干什么的。

3. 接口完整性优于参数最小化

传统设计追求"最小知识":

// 传统设计:参数最小化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理解这段代码,需要给它看多少文件?
  • 如果需求变了,我是改接口容易还是改实现容易?
  • 这个抽象是在帮助AI还是在迷惑AI?

软件设计是一门艺术,在AI时代也需要审美,只不过可能是新的审美。而上下文切割,就是这种新审美的核心。

当然,这并不意味着我们要完全抛弃传统的设计智慧。在某些场景下,分层和抽象依然有其价值。关键是要识别什么时候该为人类优化,什么时候该为AI优化。这种平衡的艺术,或许才是未来软件架构师最重要的能力。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询