课程大纲

系统思维:先画蓝图,再动代码

进阶进行中

本章主记忆点:代码不值钱,大局系统价万金。

你看完能拿走什么

  • 一套"先画蓝图再写代码"的方法,避免做到一半结构塌了
  • 模块化拆解 + 三层分离 + 对接规范的具体操作
  • 一张每次新项目开工前都能用的架构自检清单

前置条件

已完成本系列前面的章节。Ch 1-3 帮你把方向筛对、需求说清,Ch 4 教你让东西看起来像产品。这一章开始讲怎么把东西做得不容易塌

为什么要讲系统思维

先说一个我踩了很多次的坑。

AI 写代码真的很快。一个功能丢给 Codex,20 分钟就能跑。再加一个功能,又 20 分钟。第三个功能加完,你发现——

前两个功能坏了。

你开始修,修第一个的时候第三个又坏了。你修到第三天,整个项目变成了一坨"牵一发动全身"的意大利面条代码。

这不是 AI 的问题。是你一开始没有画蓝图。

AI 帮你解决的是"局部代码的生产速度"。但整体结构——哪些模块、模块之间怎么通信、哪些东西共用哪些东西隔离——这个 AI 不会替你想。

你不画蓝图就开始写,等于让一个手速极快但不看全局的人帮你砌墙。他每块砖放得都很快,但墙歪了你才发现——拆重来比从头来更慢。

局部代码是便宜的。整体结构错了才贵。

这一章教你在写第一行代码之前,先花 30 分钟把蓝图画出来。这 30 分钟是你整个项目里成本最低、收益最高的投入。

第一步:模块化拆解

"模块化"这三个字听起来很抽象。具体到你要做的事,就是一个动作:

把你要做的东西拆成 3-7 个互相不知道对方存在的块。

为什么是 3-7 个?少于 3 个说明你没拆够——每个块做的事太多,以后改一个会牵动另一个。多于 7 个说明你拆过头了——管理这些块本身就变成了一个工程。

怎么拆

拿上一章(Ch 3)的"发票生成器"做例子。你的 VDF 写完了,现在要把它拆成模块。

text
模块 1: 表单输入
  - 接收 4 个字段(金额、服务内容、客户名、日期)
  - 校验字段是否填完
  - 输出: 一个结构化的 invoice 对象

模块 2: PDF 生成
  - 接收一个 invoice 对象
  - 按模板生成 A4 PDF
  - 输出: 一个可下载的 PDF 文件

模块 3: 预览
  - 接收一个 invoice 对象
  - 在页面右侧实时渲染 A4 预览
  - 不生成 PDF,只是视觉展示

模块 4: 页面布局
  - 左边放表单,右边放预览
  - 底部放"生成 PDF"按钮
  - 管理表单 ↔ 预览 ↔ PDF 生成之间的数据流

注意这四个模块之间的关系:

  • 模块 1 不知道 PDF 怎么生成,它只负责"收集输入,吐出一个对象"
  • 模块 2 不知道表单长什么样,它只负责"拿到一个对象,生成 PDF"
  • 模块 3 不知道 PDF 的事,它只负责"拿到一个对象,画在屏幕上"
  • 模块 4 是唯一知道"其他三个模块存在"的块,它负责把数据从 1 传给 2 和 3

这就是模块化的核心:每个块只知道自己的事,不知道别人的事。

以后你改 PDF 模板,不会影响表单;改表单校验规则,不会影响预览。"牵一发动全身"消失了。

模块化的自检方法

拆完之后问自己一个问题:

如果我把模块 2 整个删掉,模块 1 和模块 3 还能不能正常运行?

能 → 拆对了。不能 → 模块之间耦合了,需要重新拆。

第二步:三层分离

模块化是"横向"拆——把功能拆成块。三层分离是"纵向"拆——把每个块的内部分成三个层。

这三个层你可能听过:

  • 展示层(View):用户看到什么、点什么
  • 逻辑层(Controller / Logic):点了之后发生什么
  • 数据层(Model / Store):数据存在哪、怎么读写

为什么要分?因为 AI 特别擅长写展示层的代码(HTML、CSS、组件),但逻辑层和数据层它经常会"就近塞"——把逻辑直接写在组件里,把数据直接存在状态变量里。

第一天没问题。第三天你加第二个页面的时候,发现两个页面要共享同一份数据——但数据被锁死在第一个页面的组件里了。

拆三层之后:

text
展示层: 只管"画什么"
  → 一个 React 组件,接收 props,渲染 UI
  → 不做任何业务判断

逻辑层: 只管"判断什么"
  → 表单校验、计算金额、触发 PDF 生成
  → 不知道 UI 长什么样

数据层: 只管"存什么"
  → invoice 对象存在 zustand/context/useState 里
  → 不知道谁在用这个数据

三层分离的自检方法

问自己:如果我把展示层从 React 换成 Vue,逻辑层和数据层需要改吗?

不需要 → 分对了。需要 → 逻辑和展示耦合了。

这个自检不是说你真的要换框架,而是用它来验证你的分层是否干净

第三步:先画蓝图

模块化 + 三层分离是方法论。接下来要做的是在写代码之前把它画出来

"画蓝图"不是画 UML 图。你只需要一个文本文件,写清楚三件事:

text
蓝图模板:

1. 模块列表
   - 模块名 / 职责(一句话) / 输入 / 输出

2. 模块之间的数据流
   - 谁把什么传给谁
   - 传的是什么格式

3. 每个模块内部的三层
   - 展示层做什么
   - 逻辑层做什么
   - 数据层做什么

发票生成器的蓝图长这样:

text
模块列表:
  [表单输入] → invoice 对象 → [PDF 生成] → PDF 文件
                           → [预览渲染] → 屏幕展示
  [页面布局] 协调以上三个模块

数据流:
  表单输入 → 产出 InvoiceData { amount, service, client, date }
  PDF 生成 ← 消费 InvoiceData,产出 Blob
  预览渲染 ← 消费 InvoiceData,产出 JSX

每个模块的三层:
  表单输入:
    展示: 4 个 input + 1 个 submit button
    逻辑: 校验非空 + 金额格式
    数据: useState 管理 form state

  PDF 生成:
    展示: 无(后台操作)
    逻辑: 按模板填充字段 + 调 PDF 库
    数据: 接收 InvoiceData,不持有状态

  预览渲染:
    展示: A4 比例的 div + 内容排版
    逻辑: 金额大写转换
    数据: 接收 InvoiceData,不持有状态

这个蓝图你花 20 分钟就能写完。写完之后把它丢给 AI(Codex / Claude Code),让 AI 按蓝图写代码。

你会发现 AI 按蓝图写出来的代码比"一句话需求"写出来的代码好 3 倍——因为你已经帮它把结构想清楚了,它只需要填实现。

第四步:对接规范

这一步很多人会跳过,但它是后期返工率最高的地方

"对接规范"就是:模块和模块之间传递的数据,格式是什么?

上面蓝图里的 InvoiceData { amount, service, client, date } 就是一个对接规范。但你需要把它写得更具体:

typescript
type InvoiceData = {
  amount: number;        // 单位: 分(避免浮点精度问题)
  service: string;       // 服务内容,1-200 字
  client: string;        // 客户全称
  date: string;          // 格式: YYYY-MM-DD
}

为什么要写这么细?因为 AI 在两个不同的 session 里写"表单输入"和"PDF 生成"的时候,如果你不明确规定 amount 的单位,一个可能用"元",一个可能用"分"。两个模块各自都是对的,但对接的时候 3500 元变成了 35 元

这种 bug 最隐蔽,因为每个模块单独测都没问题,合在一起才爆。

对接规范的自检方法

问自己:如果我让两个完全不认识的人分别写模块 A 和模块 B,他们只看这份规范,写出来的东西能不能直接拼起来用?

能 → 规范够细。不能 → 规范里有模糊地带,补上。

一个具体的复盘

去年底我做一个"AI 客服机器人"的 side project。

第一次我没画蓝图,直接让 Codex 开写。一天之内出了能用的原型——消息输入框 + AI 回复 + 历史记录。

第二天我加"多轮对话上下文"功能。加完之后历史记录显示出了问题——因为"历史记录"和"对话上下文"共用了同一个 state,改上下文的逻辑影响了历史记录的渲染。

第三天我加"知识库检索"功能。加完之后整个消息流变得一团乱——因为"知识库检索"的结果需要插入到"对话上下文"里,但"对话上下文"的数据结构没有预留这个位置。

第四天我放弃修补,从头开始。

第二次我先花 30 分钟画蓝图:

text
模块: 消息输入 / 对话引擎 / 知识库 / 历史记录 / 消息展示
数据流:
  消息输入 → 用户消息 → 对话引擎
  对话引擎 ←→ 知识库(检索相关文档)
  对话引擎 → AI 回复 → 消息展示
  对话引擎 → 完整对话 → 历史记录
对接规范:
  Message { role: 'user'|'assistant', content: string, sources?: string[] }
  对话引擎只操作 Message[],不知道 UI 和知识库的实现细节

画完之后按蓝图让 Codex 写。两天之内三个功能全部跑通,没有一次"加新功能导致旧功能坏掉"的情况。

差别不是 AI 的能力,是我第二次给了它结构。

本章主记忆点

代码不值钱,大局系统价万金。

AI 帮你写代码的速度越来越快。但"这个系统应该长什么样"这件事——模块怎么拆、层怎么分、数据怎么流——AI 不会替你想。

你画蓝图的那 30 分钟,价值超过 AI 帮你写的所有代码。

执行步骤

  1. 打开你当前项目(或者你正准备做的新项目)的 VDF 文档
  2. 把 VDF 里描述的功能拆成 3-7 个模块,每个模块写一句话职责
  3. 画出模块之间的数据流——谁把什么传给谁
  4. 给每个模块拆三层(展示 / 逻辑 / 数据)
  5. 写出模块之间传递数据的对接规范(字段名、类型、单位、格式)
  6. 把整个蓝图和 VDF 一起丢给 AI,让它按蓝图写代码

验证方式

蓝图画完之后,用下面这张清单自检:

检查项过关标准
模块数量3-7 个,每个有清晰的一句话职责
模块独立性删掉任一模块,其他模块不报错
三层分离换掉展示层框架,逻辑层和数据层不用改
对接规范两个不认识的人各写一个模块,拼起来能用
数据流能画出"谁把什么传给谁"的一张图

五项都过 → 可以开始写代码。有一项过不了 → 回去改蓝图,不要急着写代码。

常见问题

Q:画蓝图是不是会拖慢开发速度?

A:恰恰相反。画蓝图的 30 分钟省下的是后面"加功能导致旧功能坏掉 → 修 → 又坏了 → 重写"的 3 天。我现在的经验是:不画蓝图的项目,做到第三个功能必出结构问题。 画了蓝图的项目,做到第十个功能结构还是稳的。

Q:蓝图需要用什么工具画?Figma?draw.io?

A:一个 markdown 文件就够了。不要用可视化工具,因为你要把蓝图丢给 AI 看——AI 读文本比读图强。上面的模板直接复制到一个 .md 文件里填就行。

Q:如果我做的是一个很小的东西(比如一个单页工具),也需要画蓝图吗?

A:如果你的东西只有 1 个功能点,不需要。如果超过 2 个功能点,画。因为第 2 个功能点加进去的时候就是结构问题开始出现的时候。判断标准不是"项目大不大",而是"功能点多不多"。

Q:三层分离是不是就是 MVC?

A:本质是一个意思,但我刻意不用 MVC 这个术语,因为不同框架对 MVC 的定义不一样(React 生态里 MVC 的概念特别模糊)。你只需要记住"展示 / 逻辑 / 数据"三层分开就行,不要纠结术语。

Q:对接规范一定要用 TypeScript 写吗?

A:不一定。用什么语言写你的项目,就用什么语言写规范。TypeScript 的好处是 AI 能直接当类型定义用。如果你用 Python,写个 dataclass;用 Go,写个 struct。关键是把字段名、类型、单位写清楚,不要留模糊地带。

为什么值得收藏这一章

每次你开新项目,写完 VDF 之后、动代码之前,回到这一章把蓝图画一遍

具体来说,你要做的动作是:

  1. 复制蓝图模板到一个空文档
  2. 把 VDF 里的功能拆成模块
  3. 画数据流和对接规范
  4. 把蓝图 + VDF 一起丢给 AI

这 30 分钟是你整个项目里结构稳不稳的分水岭。

系统思维:先画蓝图,再动代码 | 资讯狗 | Zixungou