返回笔记首页

SDD 规范驱动开发 — 老项目增量开发实操

主题配置

场景:80 万行代码的老项目,增量加小功能,AI 看不到全部代码时的完整解决方案。


核心挑战与解法

挑战 解法
AI 看不到全部代码 代码考古 → 精准喂 5~10 个最相关文件
不知道老项目规范 让 AI 反向归纳,你纠正,形成 code-style.spec.md
视觉无法高保真还原 截图已有组件 → AI 读截图 → 截图对比循环修正
AI 顺手改了不该改的 Spec 明确写改动范围,git diff
事后验收

阶段一:代码考古 — 给 AI 建"上下文地图"

Step 1 — 生成项目结构快照

bash
find . -type f \( -name "*.vue" -o -name "*.ts" -o -name "*.tsx" \) \
  | grep -v node_modules | grep -v dist \
  | head -200 > project-tree.txt

project-tree.txt 喂给 Claude:

plain
这是项目的文件列表,我要新增 [XX功能],
请告诉我哪些文件最相关,我需要给你读哪些?

Step 2 — 精准喂 5~10 个相关文件

不要一次性扔所有代码,按功能类型精准喂:

plain
# 要加"用户标签筛选"功能,相关文件:
@src/components/UserList.vue      ← 要改的组件
@src/api/user.ts                  ← 相关接口
@src/store/userStore.ts           ← 状态管理
@src/types/user.d.ts              ← 类型定义
@src/utils/filter.ts              ← 已有的筛选工具函数

# 告诉 AI:只读这几个文件,理解现有模式后再动手

Step 3 — 让 AI 反向归纳老代码规范(核心步骤)

不是你告诉 AI 规范,而是让 AI 读代码自己归纳,再由你确认:

plain
请阅读以上 5 个文件,总结这个项目的编码规范,包括:
1. 组件结构顺序(script/template/style 顺序?Options API 还是 Composition API?)
2. 接口调用方式(axios 封装?统一 try/catch?还是 .catch 链式?)
3. 状态管理模式(Pinia 还是 Vuex?actions 怎么命名?)
4. CSS 规范(BEM?scoped?用了哪些 CSS 变量?)
5. TypeScript 使用习惯(interface 还是 type?枚举怎么定义?)

总结完后等我确认,不要先写代码。

AI 归纳完后,你只需纠正偏差,形成 code-style.spec.md。这份文件是项目隐性知识的显性化,后续所有新功能都引用它。


阶段二:建三份 Spec — 规范、视觉、功能各一份

Spec A — code-style.spec.md(编码规范)

由阶段一 Step 3 的 AI 归纳结果整理而来:

markdown
# code-style.spec.md

## 组件结构(必须按此顺序)
<script setup lang="ts"> → <template> → <style scoped>

## 接口调用
- 统一用 src/utils/request.ts 封装的 axios
- 错误统一在 request.ts 拦截,组件层不写 try/catch
- 接口函数命名:动词 + 名词,如 fetchUserList / updateUserTag

## 类型定义
- 业务实体用 interface,工具类型用 type
- 所有接口响应类型放在 src/types/ 目录
- 禁止在组件内 inline 定义接口类型

## CSS 规范
- 统一用 scoped + CSS 变量(变量定义在 src/styles/vars.css)
- 类名用小写 kebab-case,不用 BEM
- 禁止写内联 style,除非是动态绑定

## 命名规范
- 组件:PascalCase(UserTagFilter.vue)
- 函数:camelCase(getUserTags)
- 常量:UPPER_SNAKE_CASE(MAX_TAG_COUNT)

Spec B — design-token.spec.md(视觉规范)

从老项目 CSS 变量 / UI 库配置中提取,这是高保真还原的核心:

markdown
# design-token.spec.md

## 色彩系统(从 vars.css 提取)
--color-primary:        #1677FF      ← 主色,按钮/链接
--color-success:        #52C41A      ← 成功态
--color-warning:        #FAAD14      ← 警告态
--color-danger:         #FF4D4F      ← 危险/错误态
--color-text-primary:   #1A1A1A
--color-text-secondary: #666666
--color-text-disabled:  #BFBFBF
--color-border:         #E5E5E5
--color-bg-base:        #F5F5F5

## 间距系统(8px 基准)
--space-xs: 4px   --space-sm: 8px
--space-md: 16px  --space-lg: 24px  --space-xl: 32px

## 字体规范
--font-size-sm:   12px
--font-size-base: 14px
--font-size-md:   16px
--font-size-lg:   20px
正文行高:1.6,标题行高:1.3

## 圆角规范
--radius-sm: 4px  --radius-md: 6px  --radius-lg: 8px

## 已有公共组件(禁止重复造轮子)
BaseButton / BaseInput / BaseTable / BaseModal
TagBadge / StatusDot / PageHeader

Spec C — feature.spec.md(本次新功能)

只描述这次要加的功能,规范引用 A/B 即可:

markdown
# user-tag-filter.spec.md

## 引用规范
遵循 @code-style.spec.md 和 @design-token.spec.md

## 功能描述
在 UserList 页面顶部新增标签多选筛选栏,筛选后实时刷新列表

## 改动范围(明确写,防止 AI 过度改动)
- 新增:src/components/UserTagFilter.vue(新文件)
- 修改:src/views/UserList.vue(仅顶部插槽区域)
- 修改:src/api/user.ts(新增 tags 参数到 fetchUserList)
- 不改:UserList 的表格列、分页、排序逻辑(保持原样)

## 交互定义
- 标签来源:GET /api/tags,页面加载时请求一次,缓存在组件内
- 多选模式:可同时选多个标签,全不选 = 不筛选
- 触发时机:选中/取消立即触发,不需要确认按钮
- 加载态:筛选期间表格显示 loading,不清空现有数据
- URL 同步:筛选状态写入 query 参数 ?tags=1,2,3,支持分享链接

## 视觉要求
- 标签用已有的 TagBadge 组件,选中态加 --color-primary 描边
- 整体样式和 UserList 现有筛选区保持一致

阶段三:截图 + Spec 双驱动生成代码

Step 1 — 截图已有的相似组件给 AI

plain
[截图1] 现有的"状态筛选栏"区域(已有的筛选组件样式)
[截图2] TagBadge 组件现有的选中态 / 未选中态

Prompt:
新的 UserTagFilter 组件视觉风格需要和截图1完全一致,
标签的选中态参考截图2,请先描述你看到的视觉规律,
我确认后再生成代码。

让 AI 先"说"它看到了什么再动手,可以提前发现视觉理解偏差,避免生成完再大改。

Step 2 — 一次性喂全部上下文生成代码

plain
请基于以下所有上下文生成代码:

【规范】@code-style.spec.md
【视觉】@design-token.spec.md
【功能】@user-tag-filter.spec.md
【参考代码】@src/components/StatusFilter.vue(风格最接近的已有组件)
【相关代码】@src/api/user.ts @src/types/user.d.ts
【视觉参考】[截图1] [截图2]

生成任务:
1. 新建 UserTagFilter.vue,复用 TagBadge,CSS 变量全部用 design-token 中定义的
2. 修改 UserList.vue,只动顶部插槽,其余代码一行不改
3. 修改 user.ts,只新增 tags 参数,不改已有函数签名

输出完后告诉我改动了哪些行,方便我 diff 检查。

Step 3 — 截图对比循环修正视觉

plain
[截图A] 老的 StatusFilter 筛选栏(目标风格)
[截图B] 刚生成的 UserTagFilter(实际效果)

Prompt:
对比两张截图,列出所有视觉不一致的地方
(间距/颜色/字体大小/圆角/边框),逐条修复。

这个"截图对比"循环平均跑 2~3 轮就能达到高保真,比手动调样式快 5 倍。


阶段四:最小化改动验证

Step 1 — 用 git diff 做改动范围审查

bash
git diff --stat    # 看改了哪些文件
git diff           # 看具体改了什么

把 diff 输出发给 AI:

plain
以下是你生成代码后的 git diff,
请确认改动范围是否超出了 feature.spec.md 中定义的范围,
如有超出,告诉我哪里需要还原。

Step 2 — 跑类型检查和已有测试

bash
npx tsc --noEmit                       # 确保没有引入新的类型错误
npx vitest run                         # 跑已有单测,不能有新增失败
npx eslint src --ext .vue,.ts          # 确保符合老项目 lint 规则

如果 tsc 或测试报错,把报错信息发给 AI:"请修复以下错误,不要改动其他逻辑。"

Step 3 — Spec 归档,下次增量继续复用

plain
docs/
  specs/
    code-style.spec.md                  ← 长期维护,有新规范就更新
    design-token.spec.md                ← 长期维护,UI 库升级时同步
    features/
      2024-11-user-tag-filter.spec.md   ← 归档,可追溯
      2024-12-export-excel.spec.md      ← 下次新功能继续写这里

两个核心认知

视觉高保真的本质:给截图,不给描述

你告诉 AI"间距要和现有组件一致",AI 会猜。你把截图直接给它,它看到的就是你看到的。两张截图对比让 AI 自己 diff,比你逐条说"这里少了 4px"精准得多。

改动最小化原则:写清楚"不改什么"比"改什么"更重要

feature.spec.md 里明确写"不改 XX 文件、不动 XX 逻辑",AI 有重构冲动,要在 Spec 层把边界锁死,然后用 git diff 做事后验收。


Spec 文件复用关系

plain
code-style.spec.md     ← 长期资产,所有新功能共用
design-token.spec.md   ← 长期资产,所有新功能共用
         ↓                        ↓
     feature-A.spec.md  ←  引用以上两份
     feature-B.spec.md  ←  引用以上两份
     feature-C.spec.md  ←  引用以上两份

三份 Spec 中 A 和 B 是复用的,只有 C 每次新写。边际成本越来越低。