返回笔记首页

第 11 集:Vibe Coding 工程规范

主题配置

一、为什么 AI 时代规范更重要

一个常见的误解:用了 AI 写代码,规范就不重要了,因为 AI 每次生成的代码都不一样,规范约束不了 AI。

实际情况是反过来的:规范在 AI 时代变得更重要

原因一:规范是给 AI 的精准指令。.cursorrules 里写的规范,AI 每次生成代码都会遵守。规范越详细,AI 输出越稳定。

原因二:AI 生成的代码量大大增加,没有规范约束,代码库会以更快的速度混乱。

原因三:AI 有"默认行为",不明确禁止,它会悄悄做你不想要的事情(用错 ORM、忘记加 Swagger、返回明文密码等)。


二、代码风格规范(code-style.md)

文件路径:docs/engineering/code-style.md

markdown
# 代码风格规范

> 本规范适用于所有代码,包括 AI 辅助生成的代码。
> AI 生成代码后,必须对照本规范进行审查。

## 文件命名

- 所有源码文件:kebab-case
    - ✅ user-profile.service.ts
    - ❌ UserProfileService.ts
    - ❌ userProfileService.ts
- 测试文件:与被测文件同名,加 .spec.ts
    - ✅ user.service.spec.ts

## 变量和函数命名

- 变量和函数:camelCase
    - ✅ getUserById、totalAmount、isActive
    - ❌ get_user_by_id、TotalAmount
- 类名:PascalCase
    - ✅ UserService、CreateUserDto
- 常量(不变的配置值):SCREAMING_SNAKE_CASE
    - ✅ MAX_RETRY_COUNT、DEFAULT_PAGE_SIZE
- 布尔值变量:以 is、has、can、should 开头
    - ✅ isActive、hasPermission、canDelete、shouldRetry

## 函数规范

- 单个函数不超过 30 行(不含注释和空行)
- 函数只做一件事(单一职责)
- 函数参数不超过 4 个,超过就用对象传
    - ❌ createUser(name, email, password, role, isActive, avatar)
    - ✅ createUser(createUserDto: CreateUserDto)
- 所有 public 方法必须有 JSDoc 注释

## JSDoc 注释格式

所有 Service 里的 public 方法按以下格式写:

/\*\*

- 根据 ID 查找用户
- @param id 用户 ID
- @returns 用户对象(不含 password 字段),用户不存在时抛出 NotFoundException
  \*/
  async findOne(id: number): Promise<SafeUser>

## 注释规范

- 行内注释:解释"为什么",不解释"是什么"
    - ❌ // 给 age 加 1
    - ✅ // 用户年龄从出生年份计算,需要加 1 因为还没到生日

## TypeScript 规范

- 禁止使用 any 类型
    - 不确定类型时用 unknown,配合类型守卫
    - 实在需要宽松类型时用 Record<string, unknown>
- 优先使用 interface 定义数据结构(不用 class 的场景)
- 可选字段用 ?
    - ✅ name?: string
    - ❌ name: string | undefined
- 枚举值用 enum
    - ✅ enum UserRole { USER = 'USER', ADMIN = 'ADMIN' }
    - ❌ type UserRole = 'USER' | 'ADMIN'

## 绝对禁止项

- ❌ console.log(用 NestJS Logger)
- ❌ any 类型
- ❌ 硬编码配置值(密钥、URL、端口、账号密码)
- ❌ 空的 catch 块
- ❌ 注释掉的无用代码(直接删,Git 有历史记录)
- ❌ throw new Error(...)(用 NestJS 内置异常类)

三、Git Commit 规范(commit-guide.md)

文件路径:docs/engineering/commit-guide.md

markdown
# Git Commit 规范

使用 Conventional Commits 规范:https://www.conventionalcommits.org

## 格式

<type>(<scope>): <subject>

[body - 可选]

[footer - 可选]

## type 类型

| type     | 使用场景                                 |
| -------- | ---------------------------------------- |
| feat     | 新增功能                                 |
| fix      | Bug 修复                                 |
| docs     | 文档更新                                 |
| style    | 代码格式调整(不影响逻辑,如空格、分号) |
| refactor | 重构(不是新功能也不是修 Bug)           |
| test     | 添加或修改测试                           |
| chore    | 构建工具、依赖更新、CI 配置等            |
| perf     | 性能优化                                 |
| revert   | 回滚某个 commit                          |

## scope 范围

按项目的模块划分:user、order、product、auth、common、config、prisma

## subject 写法规则

- 不超过 50 个字符
- 动词开头,现在时态("添加"不是"添加了")
- 末尾不加句号
- 中英文均可,团队内统一即可

## 完整示例

feat(user): 新增用户软删除功能

使用 deletedAt 字段实现软删除,避免物理删除导致关联数据异常。
findAll 查询自动过滤已删除的用户。

Closes #123

---

fix(auth): 修复 JWT Token 过期后未返回 401 的问题

---

refactor(order): 提取重复的用户验证逻辑为 validateUser 私有方法

---

test(user): 补充 UserService.remove 软删除的单元测试

---

chore: 升级 Prisma 到 5.8.0

## 用 Claude Code 辅助写 Commit

在 git add 之后,用 Claude Code 根据 diff 生成 commit message:

````bash
claude "看一下 git diff --staged 的内容,按照 Conventional Commits 规范,
用中文帮我写一条 commit message"
```text

复制生成的 message 使用,或者在此基础上微调。

## 自动校验配置(commitlint + Husky)

安装:

```bash
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
chmod +x .husky/commit-msg
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
```text

配置后效果:

- 不符合规范的 commit message 会被自动拦截
- 强制所有人(包括 AI 生成的代码)的提交都符合规范

```plain
---

## 四、PR Review 规范(pr-review-guide.md)

文件路径:`docs/engineering/pr-review-guide.md`

```markdown
# PR 提交与 Review 规范

## PR 创建规范

1. 一个 PR 只做一件事,不混入不相关的改动
2. PR 标题格式和 Commit 规范一致
3. PR 描述必须填写(使用项目 PR 模板)
4. 提交前本地运行通过:npm test + npx tsc --noEmit

## AI Code Review 步骤(提交 PR 前完成)

### 第一步:安全扫描

```bash
claude "扫描本次改动涉及的文件,检查安全漏洞:
SQL注入、硬编码密钥、权限缺失、敏感字段暴露、路径穿越"
```text

### 第二步:规范检查

```bash
claude "检查改动的代码是否符合 @docs/engineering/code-style.md 里的规范,
列出不符合的地方"
```text

### 第三步:测试覆盖

```bash
npm test -- --coverage
```text

确认覆盖率没有下降。

### 第四步:填写 PR 模板

把 AI 扫描结果摘要粘贴到 PR 描述的"AI Review 结果摘要"部分。

## Vibe Coding 时代的 Review 原则

AI 帮你保证了代码风格的一致性,Review 者应该把精力放在:

- 业务逻辑是否正确
- 边界条件是否考虑全了
- 数据库设计是否合理
- 接口设计是否符合 RESTful 规范
- 性能问题(N+1 查询、缺少索引)

不需要花时间在:

- 命名是否规范(.cursorrules 保证)
- 是否有注释(.cursorrules 保证)
- 返回格式是否统一(.cursorrules 保证)

## 合入规则
- 至少 1 个 Reviewer Approve
- CI 全部通过(TypeScript 检查 + 测试 + AI 安全扫描)
- 没有未解决的 Review 评论
- PR 描述的 Checklist 全部勾选

## Review 者行为规范
- PR 创建后 24 小时内给出 Review
- 发现问题用 Request Changes,并给出明确的修改建议
- 不纠结代码风格(那是 AI 和 Rules 的责任)
- 讨论方案时给出两种以上的备选,不只是"这样不好"

```plain
---

## 五、演示操作步骤

### 准备工作

**Step 1**:创建规范文件目录

```bash
mkdir -p docs/engineering
touch docs/engineering/code-style.md
touch docs/engineering/commit-guide.md
touch docs/engineering/pr-review-guide.md
````
text

**Step 2**:把第二、三、四章的内容分别写入对应文件

---

### 配置 Husky + commitlint

**Step 1**:安装依赖

```bash
npm install --save-dev husky @commitlint/cli @commitlint/config-conventional
```

**Step 2**:初始化 Husky

```bash
npx husky init
```

执行后会生成 `.husky/` 目录和 `pre-commit` 文件。

**Step 3**:创建 commit-msg hook

```bash
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
chmod +x .husky/commit-msg
```

**Step 4**:创建 commitlint 配置文件

```bash
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
```

**Step 5**:在 `package.json` 里确认有 `prepare` 脚本(Husky 需要)

```json
{
    "scripts": {
        "prepare": "husky"
    }
}
```

**Step 6**:验证 Husky 配置

```bash
# 目录结构确认
ls -la .husky/
cat .husky/commit-msg

# 测试不规范的 commit(应该被拦截)
git add docs/
git commit -m "改了一些东西"
# 期望:报错,提示不符合规范

# 测试规范的 commit(应该通过)
git commit -m "docs: 添加工程规范文件"
# 期望:成功提交
```

---

### 演示 AI 辅助写 Commit

**Step 1**:修改任意一个文件(比如给 `user.service.ts` 加一个方法)

**Step 2**:暂存改动

```bash
git add src/modules/user/user.service.ts
git diff --staged   # 查看 diff 内容
```

**Step 3**:用 Claude Code 生成 commit message

```bash
claude "看一下 git diff --staged 的内容,按照 Conventional Commits 规范,
用中文帮我写一条 commit message,格式:type(scope): subject"
```

**Step 4**:用生成的 message 提交

```bash
git commit -m "feat(user): 新增用户搜索功能,支持按 username 模糊匹配"
```

---

### 在 .cursorrules 里引用规范文件

**Step 1**:打开 `.cursorrules`,加入以下内容:

```plain
## 代码规范参考
- 代码风格规范:@docs/engineering/code-style.md
- 生成代码时必须符合上述规范文件里的所有约束
```

**Step 2**:新建 Cursor Chat,测试 AI 是否会读取规范文件

```plain
帮我在 UserService 里新增一个 findByEmail 方法,
根据 email 查找用户,找不到时抛出异常
```

**Step 3**:检查生成的代码是否符合 `code-style.md` 里的规范:

- 是否有 JSDoc 注释(规范要求)
- 方法名是否是 camelCase
- 是否用了 NestJS 异常类(不是 `throw new Error`)
- 是否有 `any` 类型(不应该有)

---

## 六、规范的维护原则

### 第一版要简单

第一次建立规范,只写最核心的 10 条,不要写 50 条。

没人看完 50 条的规范,AI 也可能因为文件过长而忽略后半段。

### 问题驱动追加

每次发现 AI 生成了不符合预期的代码,问一下:

1. 这个问题规范里有没有提到?
2. 如果没有,追加对应的约束
3. 如果有,可能是约束写得不够精确,改得更具体

### 定期回顾

每个月回顾一次规范文件:

- 删掉不再适用的条目
- 合并重复或相似的条目
- 给模糊的条目补充示例

### 团队一起维护

规范文件提交到 Git,团队成员都可以提 PR 修改。

修改规范的 PR 需要至少 2 个人 Approve,避免个人主观偏好主导规范。

---

## Spec Coding 实战补充:11 VibeCoding 工程规范

> 来源:`Spec Coding实战/11 VibeCoding 工程规范.md`,已合并到本章节。

### 1. 为什么 VibeCoding 需要工程规范

VibeCoding 降低了写代码的门槛,但同时也带来了一个新问题:**每个人(包括 AI)生成代码的风格都不一样**。

没有规范的 AI 辅助开发团队,代码库会快速退化成"每个模块像不同的人写的,风格各异,无法维护"。

工程规范解决的是:**让团队里所有人(以及 AI)生成的代码,看起来像是同一个人写的。**

这不是强迫症,这是可维护性。

---

### 2. 规范文件的组织结构

```plain
project-standards/
├── README.md                # 规范总览,新人必读
├── code-style.md            # 代码风格规范
├── directory-structure.md   # 目录结构规范
├── commit-guide.md          # 提交规范
├── pr-template.md           # PR 模板
├── api-design.md            # 接口设计规范
└── testing-guide.md         # 测试规范

# 项目根目录的配套文件
.cursorrules                 # AI 工具规范
.eslintrc.js / eslint.config.js
prettier.config.js
.commitlintrc.js
.husky/
  pre-commit
  commit-msg
```

---

### 3. 目录结构规范

#### 后端(NestJS)

```plain
src/
├── common/                          # 全局公共基础设施
│   ├── constants/                   # 常量、枚举、错误码
│   │   ├── error-code.ts
│   │   └── app.constants.ts
│   ├── decorators/                  # 自定义装饰器
│   │   ├── current-user.decorator.ts
│   │   └── roles.decorator.ts
│   ├── dto/                         # 公共 DTO(分页等)
│   │   └── page-query.dto.ts
│   ├── exceptions/                  # 自定义异常
│   │   └── business.exception.ts
│   ├── filters/                     # 全局 Filter
│   │   └── all-exceptions.filter.ts
│   ├── guards/                      # 全局 Guard
│   │   └── jwt-auth.guard.ts
│   ├── interceptors/                # 全局 Interceptor
│   │   └── transform.interceptor.ts
│   └── utils/                       # 工具函数(纯函数,无副作用)
│       └── pagination.util.ts
├── config/                          # 配置模块
│   └── configuration.ts
├── modules/                         # 业务模块(每个模块独立目录)
│   └── [feature-name]/
│       ├── [feature].module.ts
│       ├── [feature].controller.ts
│       ├── [feature].service.ts
│       ├── [feature].service.spec.ts
│       └── dto/
│           ├── create-[feature].dto.ts
│           ├── update-[feature].dto.ts
│           └── query-[feature].dto.ts
└── prisma/
    ├── prisma.module.ts
    └── prisma.service.ts
```

##### 规则

- 每个业务模块在 `modules/` 下独立目录,禁止跨模块直接引用 Service(通过 Module 导出)
- `common/` 只放真正通用的东西,业务逻辑不进 common
- 文件命名全部 kebab-case,与类名用 PascalCase 一一对应

---

#### 前端(Vue3)

```plain
src/
├── api/                     # 接口层
│   ├── request.ts           # axios 实例(统一拦截器)
│   ├── user.api.ts          # 用户模块接口(文件名 = 模块.api.ts)
│   └── types/               # 接口类型定义(与后端 DTO 对齐)
│       └── user.types.ts
├── assets/                  # 静态资源(图片、字体、图标)
├── components/              # 可复用组件(与业务无关)
│   └── [ComponentName]/
│       └── index.vue
├── composables/             # 组合式函数(use 前缀)
│   ├── usePageQuery.ts
│   └── useRequest.ts
├── layouts/                 # 布局组件
│   ├── DefaultLayout.vue
│   └── AuthLayout.vue
├── router/
│   └── index.ts
├── stores/                  # Pinia store(use + 名词 + Store)
│   └── useUserStore.ts
├── styles/                  # 全局样式
│   ├── variables.scss       # SCSS 变量
│   └── global.scss          # 全局样式(慎用)
├── utils/                   # 工具函数
│   └── format.ts
└── views/                   # 页面级组件(按模块分目录)
    └── user/
        ├── UserList.vue
        └── UserDetail.vue
```

---

### 4. 代码风格规范

#### prettier.config.js

```javascript
// prettier.config.js
module.exports = {
    semi: false, // 无分号
    singleQuote: true, // 单引号
    tabWidth: 2, // 2 空格
    printWidth: 100, // 每行最大 100 字符
    trailingComma: 'all', // 多行时末尾逗号
    bracketSpacing: true, // 对象括号内空格 { a: 1 }
    arrowParens: 'always', // 箭头函数参数始终加括号 (x) => x
    endOfLine: 'lf', // 换行符 LF(跨平台一致)
}
```

#### eslint.config.js(ESLint v9 flat config)

```javascript
// eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import prettierConfig from 'eslint-config-prettier'

export default tseslint.config(
    js.configs.recommended,
    ...tseslint.configs.recommended,
    prettierConfig,
    {
        rules: {
            '@typescript-eslint/no-explicit-any': 'error', // 禁止 any
            '@typescript-eslint/no-unused-vars': 'error', // 禁止未使用变量
            '@typescript-eslint/explicit-function-return-type': 'warn', // 函数返回类型
            'no-console': 'warn', // 提醒用 logger
            'prefer-const': 'error', // 优先 const
        },
    }
)
```

#### 命名规范速查

```plain
文件名:         kebab-case       user-profile.service.ts
类名:           PascalCase       UserProfileService
接口/类型:      PascalCase       CreateUserDto, UserStatus
变量/函数:      camelCase        getUserById, currentUser
常量:           UPPER_SNAKE_CASE JWT_SECRET, MAX_PAGE_SIZE
枚举:           PascalCase       OrderStatus
枚举成员:       UPPER_SNAKE_CASE OrderStatus.PENDING
CSS 类名:       BEM              .user-card__avatar--active
数据库表名:     snake_case       order_items
数据库列名:     snake_case       created_at, is_deleted
```

---

### 5. 提交规范(Conventional Commits)

#### commit-guide.md

格式:`type(scope): description`

```plain
feat(user): add email verification on registration
fix(order): fix N+1 query in findAll method
refactor(auth): extract token validation to guard
docs(api): update user module swagger annotations
test(user): add edge case tests for create method
chore(deps): upgrade prisma to 5.10.0
perf(product): add index on product.status field
```

##### type 枚举

| type       | 用途                       |
| ---------- | -------------------------- |
| `feat`     | 新功能                     |
| `fix`      | Bug 修复                   |
| `refactor` | 重构(不改功能,不修 bug) |
| `docs`     | 文档更新                   |
| `test`     | 测试相关                   |
| `chore`    | 构建、依赖、配置等         |
| `perf`     | 性能优化                   |
| `style`    | 代码格式(不影响逻辑)     |
| `ci`       | CI/CD 配置                 |

###### scope 约定

- 用模块名:`user`、`order`、`auth`、`product`
- 用基础设施层:`prisma`、`config`、`common`
- 全局改动用 `*`:`chore(*): update eslint config`

#### .commitlintrc.js

```javascript
module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'type-enum': [
            2,
            'always',
            [
                'feat',
                'fix',
                'refactor',
                'docs',
                'test',
                'chore',
                'perf',
                'style',
                'ci',
            ],
        ],
        'subject-max-length': [2, 'always', 100],
        'subject-case': [0], // 不强制大小写,中文友好
    },
}
```

---

### 6. Git Hooks(Husky v9 配置)

```bash
# 安装
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional

# 初始化
npx husky init
```

#### .husky/pre-commit

```plain
#!/bin/sh
npx lint-staged
```

##### .husky/commit-msg

```plain
#!/bin/sh
npx commitlint --edit $1
```

###### package.json 中的 lint-staged 配置

```json
{
    "lint-staged": {
        "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
        "*.{json,md,yml}": ["prettier --write"]
    }
}
```

这样每次 `git commit`:

1. `pre-commit`:对暂存文件跑 ESLint + Prettier
2. `commit-msg`:验证 commit message 格式

不符合规范的提交直接被拒绝,不需要人工 Review 代码风格。

---

### 7. PR 模板

```markdown
<!-- .github/pull_request_template.md -->

## 改动说明

<!-- 这次 PR 做了什么,为什么要做 -->

## 改动类型

- [ ] feat:新功能
- [ ] fix:Bug 修复
- [ ] refactor:重构
- [ ] docs:文档
- [ ] test:测试
- [ ] chore:配置/依赖

## 关联 Issue

closes #

## 测试说明

<!-- 如何验证这次改动是正确的 -->

- [ ] 单元测试通过(npm test)
- [ ] 手动测试通过(描述测试步骤)
- [ ] 新功能有对应的测试用例

## 数据库变更

- [ ] 无数据库变更
- [ ] 有新的 Migration(已在本地验证 migrate deploy 能成功)

## 需要特别注意

<!-- Review 时需要重点关注的地方,或者有意为之但看起来奇怪的地方 -->

## 截图(如有)
```

---

### 8. 团队协作与知识沉淀

#### 把规范纳入 AI 工具链

所有规范文件要和 `.cursorrules` 联动。`.cursorrules` 是规范的执行层,`project-standards/` 是规范的说明层。

当规范更新时,两者同步更新:

```bash
# 规范更新后的提交流程
1. 更新 project-standards/xxx.md(人可读的说明)
2. 更新 .cursorrules(AI 执行的约束)
3. git commit -m "docs(*): update naming convention for stores"
```

#### 规范的生命周期

规范不是写完就不变的。随着项目演进,要定期回顾:

- 每个 Sprint 结束时,回顾本轮 Code Review 中频繁出现的问题,新问题加进规范
- 技术栈升级时(如 Prisma 大版本升级),同步更新规范里的使用示例
- 新人入职时,让新人读规范并提问,这是发现规范不清楚地方的好时机

#### 用 AI 生成规范文档

```plain
读取 @src/modules/user/ 目录下的所有文件,
分析这个团队当前实际遵循的代码风格和组织方式,
生成一份 user-module-convention.md 文档,
描述:DTO 的写法、Service 的结构、Controller 的惯例、错误处理的方式。

这份文档将作为新团队成员了解项目规范的参考。
```

---

### 9. 小结

工程规范的本质是**把最佳实践变成默认行为**。

好的规范体系有三层:

- **工具层**(ESLint、Prettier、Husky):自动格式化,自动检查,无法绕过
- **约束层**(.cursorrules):告诉 AI 应该怎么写
- **说明层**(project-standards/):告诉人为什么这么写

三层配合,规范才能真正落地,而不是挂在墙上没人看的文档。