最近在做一个紧急项目,需求是快速搭建一个包含用户管理、数据展示和简单交互的管理后台。按照传统开发流程,从需求分析、UI设计、前后端开发到联调测试,一个3-4人的小团队至少需要一个月。然而,借助 AI 编程工具,我仅用半小时就完成了核心功能的原型搭建,这背后是Codex与Spec Coding工作流的深度结合。
本文将完整复盘这次“极限压缩工期”的实战过程,手把手带你体验如何用 AI 重构前端乃至全栈开发流程。无论你是想提升个人效率的前端开发者,还是寻求技术转型的全栈工程师,这套方法都能让你直观感受到生产力工具的变革。
1. 背景与核心概念:为什么是 Codex 与 Spec Coding?
在深入实战之前,我们有必要厘清几个关键概念,理解它们为何能带来效率的质变。
1.1 什么是 Codex?
Codex 是 OpenAI 基于 GPT-3 微调的大型语言模型,专门用于将自然语言转换为代码。它最著名的落地产品就是 GitHub Copilot。你可以把它理解为一个“超级代码补全工具”,但它做的远不止补全一个变量名或函数调用。
Codex 的核心能力:
- 上下文理解:能理解你当前文件、甚至整个项目的代码上下文,生成风格一致、逻辑连贯的代码。
- 多语言支持:精通数十种编程语言,尤其擅长 Python、JavaScript、TypeScript、Go、Java 等。
- 从注释生成代码:你只需用自然语言描述你想实现的功能(如“写一个函数,接收用户数组,返回年龄大于18的用户列表”),它就能生成可运行的代码。
- 代码转换与解释:可以将代码从一种语言翻译成另一种,或者为复杂代码段添加注释。
在本次实战中,Codex(通过 Copilot 或类似工具)是我们主要的“代码生成器”。
1.2 什么是 Spec Coding?
Spec Coding,即规格化编码或说明书驱动开发,是一种新兴的开发范式。其核心思想是:开发者不再逐行编写具体代码,而是编写精确的、机器可读的“规格说明书”(Specification),然后由 AI 工具自动生成符合规格的代码。
这听起来有点像“低代码”,但本质不同。低代码平台提供的是封装好的可视化组件和有限逻辑。而 Spec Coding 中,你编写的“规格”可以非常灵活和底层,能够描述复杂的业务逻辑、数据结构、API 接口和交互行为,最终生成的是标准的、可维护的源代码。
Spec Coding 的工作流:
- 定义规格:用结构化的方式(如 YAML、JSON、或特定的 DSL)描述数据模型、API 端点、组件属性、页面布局、业务规则等。
- AI 生成:将规格输入给 AI 工具(如结合了 Codex 能力的定制化引擎)。
- 生成代码:AI 工具输出完整的前端组件、后端控制器、服务层、数据库迁移脚本等。
- 微调与集成:开发者在生成的代码基础上进行微调、逻辑补充和集成测试。
1.3 AI 如何重构全栈开发?
传统全栈开发要求开发者精通前端框架、后端语言、数据库、部署运维等多方面知识,学习曲线陡峭,协作成本高。AI 的介入改变了这一模式:
- 降低认知负荷:开发者无需记忆所有 API 语法和框架细节,可以将更多精力集中在业务逻辑和系统设计上。
- 加速原型构建:描述需求即可获得可运行的原型,快速验证想法,缩短反馈循环。
- 打破技能壁垒:前端开发者可以更容易地完成后端接口描述,后端开发者也能快速生成前端界面,向“全栈”能力迈进。
- 提升代码一致性:AI 生成的代码遵循固定的模式和最佳实践,有助于保持项目代码风格统一。
接下来,我们将在一个具体的实战项目中,体验这套组合拳的威力。
2. 环境准备与工具选型
工欲善其事,必先利其器。我们的目标是半小时内搭建一个管理后台,因此工具链的选择必须高效、轻量且与 AI 工具集成度好。
2.1 核心工具清单
- AI 编程助手:GitHub Copilot。这是目前最成熟、与开发环境集成最好的 Codex 应用。我们将使用它来完成大部分代码生成工作。确保你的 IDE 已安装 Copilot 插件并完成激活。
- 前端框架:Vue 3 + TypeScript + Vite。Vue 3 的 Composition API 和 TypeScript 对 AI 代码生成非常友好,结构清晰。Vite 提供极速的启动和热更新。
- UI 组件库:Element Plus。作为成熟的 Vue 3 UI 库,它组件丰富,API 稳定,Copilot 对其支持度很高,能准确生成相关代码。
- 后端模拟:Mock.js + Axios。为了在半小时内聚焦前端和全栈逻辑,我们暂不搭建真实后端,而是用 Mock.js 在浏览器端拦截 API 请求并返回模拟数据。Axios 用于处理 HTTP 请求。
- 开发环境:
- Node.js (版本 16 或以上)
- npm 或 yarn 或 pnpm
- VS Code (强烈推荐,对 Copilot 支持最佳)
2.2 初始化项目
打开终端,我们快速初始化项目。
# 使用 Vite 官方模板创建 Vue+TS 项目 npm create vite@latest ai-admin-dashboard -- --template vue-ts cd ai-admin-dashboard # 安装依赖 npm install # 安装 UI 库、HTTP 客户端和 Mock 工具 npm install element-plus axios mockjs npm install -D @types/node @types/mockjs # 启动开发服务器 npm run dev项目启动后,访问http://localhost:5173可以看到默认的 Vue 页面。接下来,我们将清空src/views/和src/components/目录,开始我们的 AI 驱动开发。
3. Spec Coding 实战:定义数据模型与 API 规格
我们的管理后台需要管理“用户”和“产品”。传统的做法是先设计数据库表,然后写后端 CRUD,最后画前端页面。现在,我们从编写“规格”开始。
3.1 创建规格文件
在项目根目录下创建spec文件夹,并在其中创建># spec/data-models.yaml models: User: description: 系统用户 properties: id: type: integer format: int64 description: 用户ID,主键 username: type: string description: 用户名,唯一 minLength: 3 maxLength: 20 email: type: string format: email description: 邮箱地址 role: type: string enum: [admin, editor, viewer] description: 用户角色 status: type: string enum: [active, inactive, banned] description: 账号状态 createdAt: type: string format: date-time description: 创建时间 Product: description: 产品信息 properties: id: type: integer format: int64 description: 产品ID name: type: string description: 产品名称 category: type: string description: 产品分类 price: type: number format: float description: 价格 minimum: 0 stock: type: integer description: 库存数量 minimum: 0 description: type: string description: 产品描述
接着,创建api-spec.yaml文件,定义前端需要调用的 API 接口。
# spec/api-spec.yaml apis: UserAPI: basePath: /api/v1/users operations: listUsers: method: GET path: / description: 获取用户列表(分页、筛选、排序) parameters: - name: page in: query type: integer default: 1 - name: pageSize in: query type: integer default: 10 - name: role in: query type: string enum: [admin, editor, viewer] - name: status in: query type: string enum: [active, inactive, banned] response: type: array items: $ref: '#/models/User' pagination: true getUserById: method: GET path: /{id} description: 根据ID获取单个用户 parameters: - name: id in: path type: integer required: true response: $ref: '#/models/User' createUser: method: POST path: / description: 创建新用户 requestBody: $ref: '#/models/User' response: $ref: '#/models/User' ProductAPI: basePath: /api/v1/products operations: # ... 类似地定义产品的 list, get, create, update, delete 操作为什么这么做?编写规格文件的过程,本身就是一次严谨的需求梳理和系统设计。它迫使你思考数据的完整性和接口的规范性。这份文件不仅是给 AI 看的,更是给未来维护项目的开发者(包括你自己)看的一份清晰文档。
4. AI 辅助开发:从规格到代码
有了清晰的规格,我们就可以请出 Copilot 这位“开发助手”了。
4.1 生成 TypeScript 类型定义
首先,我们需要将 YAML 规格转换为 TypeScript 类型,以便在项目中获得完整的类型提示。
在src/types/目录下,新建一个文件index.ts。在文件中,我们输入以下注释作为给 Copilot 的指令:
// src/types/index.ts // 根据以下 YAML 规格,生成对应的 TypeScript 接口定义。 // YAML 内容: // models: // User: // properties: // id: integer // username: string // email: string // role: 'admin' | 'editor' | 'viewer' // status: 'active' | 'inactive' | 'banned' // createdAt: string // Product: // properties: // id: integer // name: string // category: string // price: number // stock: integer // description: string然后,按下Ctrl+Enter(或 Copilot 的触发快捷键),Copilot 会给出生成建议。通常它会直接生成完美的接口定义:
export interface User { id: number; username: string; email: string; role: 'admin' | 'editor' | 'viewer'; status: 'active' | 'inactive' | 'banned'; createdAt: string; } export interface Product { id: number; name: string; category: string; price: number; stock: number; description: string; } // 分页响应通用接口 export interface PaginatedResponse<T> { data: T[]; total: number; page: number; pageSize: number; totalPages: number; }效率对比:手动编写这些接口需要5-10分钟,且容易出错。AI 在几秒钟内完成,并且保证了与规格的绝对一致。
4.2 生成 API 服务层代码
接下来,我们创建 API 服务。在src/services/目录下创建userApi.ts。
在文件中输入:
// src/services/userApi.ts // 使用 axios 创建用户相关的 API 服务函数。 // 需要实现以下功能: // 1. listUsers: GET /api/v1/users,支持分页查询参数 page, pageSize, role, status。返回 PaginatedResponse<User>。 // 2. getUserById: GET /api/v1/users/{id} // 3. createUser: POST /api/v1/users // 所有函数都应该是异步的,并返回 Promise。 // 使用定义在 src/types/index.ts 中的类型。Copilot 会根据上下文(它能看到src/types/index.ts)生成如下代码:
// src/services/userApi.ts import axios from 'axios'; import type { User, PaginatedResponse } from '@/types'; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000'; const userApi = axios.create({ baseURL: API_BASE_URL, timeout: 10000, headers: { 'Content-Type': 'application/json', }, }); export const userService = { // 获取用户列表 async listUsers(params: { page?: number; pageSize?: number; role?: string; status?: string; }): Promise<PaginatedResponse<User>> { const response = await userApi.get<PaginatedResponse<User>>('/api/v1/users', { params }); return response.data; }, // 根据ID获取用户 async getUserById(id: number): Promise<User> { const response = await userApi.get<User>(`/api/v1/users/${id}`); return response.data; }, // 创建用户 async createUser(userData: Omit<User, 'id' | 'createdAt'>): Promise<User> { const response = await userApi.post<User>('/api/v1/users', userData); return response.data; }, // 更新用户 (可以后续补充) async updateUser(id: number, userData: Partial<User>): Promise<User> { const response = await userApi.put<User>(`/api/v1/users/${id}`, userData); return response.data; }, // 删除用户 (可以后续补充) async deleteUser(id: number): Promise<void> { await userApi.delete(`/api/v1/users/${id}`); }, };注意:AI 甚至为我们补充了updateUser和deleteUser的骨架,并正确使用了Omit和Partial工具类型,确保了类型安全。
4.3 生成 Mock 数据与服务
由于没有真实后端,我们需要用 Mock.js 来拦截请求。在src/mocks/目录下创建handler.ts。
输入注释:
// src/mocks/handler.ts // 使用 Mock.js 模拟用户相关的 API。 // 1. 拦截 GET /api/v1/users,根据查询参数返回分页的用户模拟数据。 // 2. 拦截 GET /api/v1/users/:id,返回对应的用户数据。 // 3. 拦截 POST /api/v1/users,生成一个ID和创建时间,将数据加入模拟列表并返回。 // 模拟数据列表初始应有20个用户,字段符合 User 接口。Copilot 生成:
// src/mocks/handler.ts import Mock from 'mockjs'; import type { User } from '@/types'; // 生成模拟用户列表 const userList: User[] = Mock.mock({ 'list|20': [{ 'id|+1': 1, 'username': '@name', 'email': '@email', 'role|1': ['admin', 'editor', 'viewer'], 'status|1': ['active', 'inactive', 'banned'], 'createdAt': '@datetime', }], }).list; // 拦截 GET /api/v1/users Mock.mock(RegExp(`${import.meta.env.VITE_API_BASE_URL || ''}/api/v1/users`), 'get', (options: any) => { const url = new URL(options.url, window.location.origin); const page = parseInt(url.searchParams.get('page') || '1', 10); const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10); const role = url.searchParams.get('role'); const status = url.searchParams.get('status'); let filteredList = [...userList]; if (role) { filteredList = filteredList.filter(user => user.role === role); } if (status) { filteredList = filteredList.filter(user => user.status === status); } const start = (page - 1) * pageSize; const end = start + pageSize; const paginatedData = filteredList.slice(start, end); return { data: paginatedData, total: filteredList.length, page, pageSize, totalPages: Math.ceil(filteredList.length / pageSize), }; }); // 拦截 GET /api/v1/users/:id Mock.mock(RegExp(`${import.meta.env.VITE_API_BASE_URL || ''}/api/v1/users/\\d+`), 'get', (options: any) => { const id = parseInt(options.url.split('/').pop(), 10); const user = userList.find(u => u.id === id); return user || { code: 404, message: 'User not found' }; }); // 拦截 POST /api/v1/users Mock.mock(RegExp(`${import.meta.env.VITE_API_BASE_URL || ''}/api/v1/users`), 'post', (options: any) => { const newUser: User = JSON.parse(options.body); newUser.id = userList.length > 0 ? Math.max(...userList.map(u => u.id)) + 1 : 1; newUser.createdAt = new Date().toISOString(); userList.unshift(newUser); // 添加到列表开头 return newUser; });然后在src/main.ts中引入并启用 Mock(仅限开发环境):
// src/main.ts import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' if (import.meta.env.DEV) { import('./mocks/handler').then(() => { console.log('Mock APIs are enabled.'); }); } const app = createApp(App) app.use(ElementPlus) app.mount('#app')4.4 生成 Vue 组件:用户管理页面
这是最体现效率的一环。我们创建src/views/UserManagement.vue。
在<template>部分,我们只需输入一个简单的开头和注释:
<!-- src/views/UserManagement.vue --> <template> <div class="user-management"> <h2>用户管理</h2> <!-- 顶部:搜索和新增按钮 --> <!-- 中间:用户数据表格,包含分页 --> <!-- 底部:暂无 --> </template>将光标放在注释行后,Copilot 会自动补全一个完整的 Element Plus 表格和搜索栏结构。我们稍作调整,最终<template>如下:
<template> <div class="user-management"> <h2>用户管理</h2> <el-card> <!-- 搜索和操作栏 --> <div class="filter-container"> <el-form :inline="true" :model="listQuery" class="demo-form-inline"> <el-form-item label="角色"> <el-select v-model="listQuery.role" placeholder="请选择角色" clearable> <el-option label="管理员" value="admin" /> <el-option label="编辑" value="editor" /> <el-option label="查看者" value="viewer" /> </el-select> </el-form-item> <el-form-item label="状态"> <el-select v-model="listQuery.status" placeholder="请选择状态" clearable> <el-option label="活跃" value="active" /> <el-option label="未激活" value="inactive" /> <el-option label="禁用" value="banned" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" @click="fetchUserList">查询</el-button> <el-button @click="resetFilter">重置</el-button> </el-form-item> </el-form> <div style="float: right;"> <el-button type="success" @click="handleCreate">新增用户</el-button> </div> </div> <!-- 用户表格 --> <el-table :data="userList" border style="width: 100%; margin-top: 20px;" v-loading="loading"> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="username" label="用户名" /> <el-table-column prop="email" label="邮箱" /> <el-table-column prop="role" label="角色"> <template #default="scope"> <el-tag :type="scope.row.role === 'admin' ? 'danger' : (scope.row.role === 'editor' ? 'warning' : '')"> {{ scope.row.role === 'admin' ? '管理员' : (scope.row.role === 'editor' ? '编辑' : '查看者') }} </el-tag> </template> </el-table-column> <el-table-column prop="status" label="状态"> <template #default="scope"> <el-tag :type="scope.row.status === 'active' ? 'success' : (scope.row.status === 'inactive' ? 'info' : 'danger')"> {{ scope.row.status === 'active' ? '活跃' : (scope.row.status === 'inactive' ? '未激活' : '禁用') }} </el-tag> </template> </el-table-column> <el-table-column prop="createdAt" label="创建时间" /> <el-table-column label="操作" width="180"> <template #default="scope"> <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <div class="pagination-container"> <el-pagination v-model:current-page="listQuery.page" v-model:page-size="listQuery.pageSize" :page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="fetchUserList" @current-change="fetchUserList" /> </div> </el-card> <!-- 新增/编辑用户对话框 --> <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px"> <el-form ref="userFormRef" :model="currentUser" :rules="userRules" label-width="80px"> <el-form-item label="用户名" prop="username"> <el-input v-model="currentUser.username" placeholder="请输入用户名" /> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="currentUser.email" placeholder="请输入邮箱" /> </el-form-item> <el-form-item label="角色" prop="role"> <el-select v-model="currentUser.role" placeholder="请选择角色"> <el-option label="管理员" value="admin" /> <el-option label="编辑" value="editor" /> <el-option label="查看者" value="viewer" /> </el-select> </el-form-item> <el-form-item label="状态" prop="status"> <el-select v-model="currentUser.status" placeholder="请选择状态"> <el-option label="活跃" value="active" /> <el-option label="未激活" value="inactive" /> <el-option label="禁用" value="banned" /> </el-select> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="submitUserForm">确认</el-button> </span> </template> </el-dialog> </div> </template>接下来,在<script setup lang="ts">部分,我们同样可以借助 Copilot。输入以下注释开头:
<script setup lang="ts"> // 用户管理页面的逻辑 // 1. 引入 userService 和类型定义。 // 2. 定义列表查询参数 listQuery (page, pageSize, role, status)。 // 3. 定义响应式数据:userList, total, loading, dialogVisible, currentUser。 // 4. 定义表单验证规则 userRules。 // 5. 定义方法:fetchUserList, resetFilter, handleCreate, handleEdit, handleDelete, submitUserForm。 // 6. 在 onMounted 中调用 fetchUserList。 </script>Copilot 会生成几乎完整的逻辑代码,我们只需做少量调整和补充:
<script setup lang="ts"> import { ref, reactive, onMounted } from 'vue'; import type { FormInstance, FormRules } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus'; import { userService } from '@/services/userApi'; import type { User } from '@/types'; // 查询参数 const listQuery = reactive({ page: 1, pageSize: 10, role: '', status: '', }); // 响应式数据 const userList = ref<User[]>([]); const total = ref(0); const loading = ref(false); const dialogVisible = ref(false); const dialogTitle = ref(''); const currentUser = reactive<Partial<User>>({ username: '', email: '', role: 'viewer', status: 'active', }); const userFormRef = ref<FormInstance>(); // 表单验证规则 const userRules: FormRules = { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 20, message: '长度在 3 到 20 个字符', trigger: 'blur' }, ], email: [ { required: true, message: '请输入邮箱地址', trigger: 'blur' }, { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }, ], role: [{ required: true, message: '请选择角色', trigger: 'change' }], status: [{ required: true, message: '请选择状态', trigger: 'change' }], }; // 获取用户列表 const fetchUserList = async () => { loading.value = true; try { const response = await userService.listUsers(listQuery); userList.value = response.data; total.value = response.total; } catch (error) { ElMessage.error('获取用户列表失败'); console.error(error); } finally { loading.value = false; } }; // 重置筛选条件 const resetFilter = () => { listQuery.role = ''; listQuery.status = ''; listQuery.page = 1; fetchUserList(); }; // 处理新增 const handleCreate = () => { dialogTitle.value = '新增用户'; Object.assign(currentUser, { username: '', email: '', role: 'viewer', status: 'active' }); dialogVisible.value = true; nextTick(() => { userFormRef.value?.clearValidate(); }); }; // 处理编辑 const handleEdit = (row: User) => { dialogTitle.value = '编辑用户'; Object.assign(currentUser, { ...row }); dialogVisible.value = true; nextTick(() => { userFormRef.value?.clearValidate(); }); }; // 处理删除 const handleDelete = async (row: User) => { try { await ElMessageBox.confirm(`确定删除用户 "${row.username}" 吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }); await userService.deleteUser(row.id); ElMessage.success('删除成功'); fetchUserList(); } catch (error) { // 用户点击了取消 } }; // 提交表单(新增/编辑) const submitUserForm = async () => { if (!userFormRef.value) return; const valid = await userFormRef.value.validate(); if (!valid) return; loading.value = true; try { if (currentUser.id) { // 更新 await userService.updateUser(currentUser.id, currentUser); ElMessage.success('更新成功'); } else { // 新增 await userService.createUser(currentUser as Omit<User, 'id' | 'createdAt'>); ElMessage.success('创建成功'); } dialogVisible.value = false; fetchUserList(); } catch (error) { ElMessage.error('操作失败'); console.error(error); } finally { loading.value = false; } }; onMounted(() => { fetchUserList(); }); </script> <style scoped> .filter-container { margin-bottom: 20px; } .pagination-container { margin-top: 20px; text-align: right; } </style>至此,一个功能完整的用户管理页面(包含列表、分页、筛选、增删改查、表单验证)已经完成。从写下第一行规格到生成这个可运行的页面,耗时大约 15 分钟。
5. 常见问题与排查思路
在 AI 辅助开发过程中,你可能会遇到一些典型问题。
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
| Copilot 不给出建议或建议质量差 | 1. 注释描述不够清晰、具体。 2. 文件上下文太少,AI 无法理解项目结构。 3. 代码风格与现有项目差异太大。 | 1. 使用更精确的英文或中文描述需求,包括输入、输出、约束条件。 2. 在文件开头添加相关 import 语句或类型定义,提供更多上下文。 3. 先手动写一小段符合项目风格的代码,再让 AI 续写。 |
| 生成的代码有语法错误或类型错误 | 1. AI 模型本身的幻觉或知识截止问题。 2. 对最新 API 不熟悉。 | 1.永远要审查 AI 生成的代码。将其作为初稿,而不是最终成品。 2. 结合 IDE 的类型检查和 ESLint 提示进行修正。 3. 对于不熟悉的 API,查阅官方文档确认。 |
| Mock 数据不生效,请求报 404 | 1. Mock.js 拦截的 URL 与 Axios 请求的 URL 不匹配。 2. Mock 在开发环境下未正确引入。 | 1. 检查Mock.mock中的正则表达式是否匹配请求的完整 URL(包括 baseURL)。2. 确认 import.meta.env.DEV为 true,且 Mock 在main.ts中正确引入。3. 在浏览器开发者工具的 Network 面板查看实际请求地址。 |
| 组件样式错乱或 Element Plus 组件未注册 | 1. Element Plus 未全局注册或按需引入不正确。 2. 样式文件未导入。 | 1. 确认main.ts中已app.use(ElementPlus)。2. 确认已安装并导入了 element-plus/dist/index.css。3. 对于按需引入,检查 unplugin-vue-components等插件的配置。 |
| TypeScript 类型报错 | 1. 生成的类型与实际数据结构不匹配。 2. 第三方库类型声明缺失。 | 1. 核对spec文件与生成的interface是否一致。2. 安装对应的类型声明包,如 @types/mockjs。3. 在暂时无法解决时,可以使用 // @ts-ignore忽略单行,但需尽快修复。 |
6. 最佳实践与工程建议
将 AI 工具高效集成到日常开发中,需要遵循一些最佳实践,以确保代码质量和项目可维护性。
6.1 Spec Coding 规范
- 规格即文档:将
spec/目录纳入版本管理。规格文件的变更应像代码变更一样经过评审。 - 单一可信源:确保业务逻辑的核心定义(如数据模型、API 契约)只存在于规格文件中。代码应从规格生成,避免手动维护两套定义。
- 渐进式细化:可以从简单的接口描述开始,逐步增加数据验证规则、业务约束等细节。不要试图一次性写出完美的规格。
- 版本化:当 API 或模型发生重大变更时,考虑使用版本号(如
/api/v2/)并在规格中明确标识。
6.2 AI 提示(Prompt)工程
- 提供充足上下文:在请求生成代码前,确保当前文件已经包含了必要的导入、类型定义和相关的函数签名。AI 根据上下文生成的内容质量更高。
- 分步骤引导:对于复杂功能,不要期望一个提示生成整个文件。可以分步进行,例如:“首先,生成这个组件的模板结构”,“现在,为这个组件添加响应式数据”,“接下来,添加处理表单提交的方法”。
- 指定框架和库:在提示中明确指出使用的技术栈,如“使用 Vue 3 Composition API 和 Element Plus 组件”。
- 要求遵循模式:如果项目有特定的代码风格或架构模式(如使用 Pinia 进行状态管理),在提示中说明,例如“使用 Pinia store 来管理这个列表的状态”。
6.3 代码审查与质量控制
- AI 生成,人工审核:必须对 AI 生成的所有代码进行人工审查。重点检查:业务逻辑是否正确、是否存在安全漏洞(如 SQL 注入、XSS)、性能是否合理、是否符合项目约定。
- 编写单元测试:AI 可以帮助生成测试用例的骨架,但关键的边界条件和业务逻辑测试仍需人工设计和补充。生成的代码必须通过测试。
- 保持一致性:使用 ESLint、Prettier 等工具强制统一代码风格。AI 有时会生成风格不一致的代码,需要用工具自动修复。
- 不要过度依赖:AI 擅长生成模板化、模式化的代码。对于极其复杂、独特的业务核心算法或需要深度优化的部分,仍然需要开发者亲自操刀。
6.4 项目结构规划
一个良好的项目结构能让 AI 更好地理解上下文,也便于团队协作。
src/ ├── apis/ # 自动生成的 API 客户端(未来可由 spec 直接生成) ├── components/ # 通用组件 ├── views/ # 页面组件 ├── stores/ # 状态管理 (Pinia) ├── services/ # 业务服务层(调用 API) ├── types/ # TypeScript 类型定义(由 spec 生成) ├── utils/ # 工具函数 ├── mocks/ # 模拟数据 └── specs/ # 规格文件 (YAML/JSON)通过将Spec Coding作为设计源头,用AI(Codex)作为代码生成引擎,再辅以严格的人工审查和工程化实践,我们构建了一个高效且可靠的全栈开发新流程。这半小时的实战演示的不仅仅是工具的使用,更是一种思维模式的转变:从“如何编写代码”转向“如何精确描述需求”,让开发者真正专注于创造价值的上层逻辑。