最近用 AI 实现了一个 Dota2 队长模式 BP 模拟器的前端项目。整个过程中,很明显能发现提示词的质量直接决定了生成代码的可用性,一个模糊的需求可能需要反复修改十几次,而一个精确的提示词往往能一次性生成 90% 可用的代码。结合这次实践,分享一些心得,抛砖引玉。
AI 生成代码的常见问题
在开始分享经验之前,先看看 AI 生成代码常见的几类问题:
1. 需求理解偏差
场景: “帮我实现一个倒计时功能”
问题: AI 可能实现一个简单的 setInterval 递减数字,但你实际需要的是:
- 倒计时归零后使用备用时间
- 时间不能显示负数
- 最后 10 秒要红色警告
- 时间用完后自动触发某个操作
这类问题的根源是:你知道你要什么,但 AI 不知道你的上下文。
2. 技术栈不匹配
场景: “实现一个状态管理”
问题: AI 可能使用:
- Redux(你的项目很小,用不着)
- Zustand(你想用原生方案)
- MobX(你不熟悉)
如果不明确指定技术栈和限制条件,AI 往往会选择它”认为”最流行的方案,而不是最适合你的。
3. 细节缺失
场景: “创建一个英雄卡片组件”
问题: AI 创建了一个基础组件,但缺少:
- 不同状态的样式(已选中、已禁用、可点击)
- 响应式适配
- 动画效果
- 无障碍支持
这类问题源于:你脑海中有完整的视觉效果,但提示词只描述了 20%。
4. 逻辑漏洞
场景: “实现 BP 流程”
问题: AI 实现了基本流程,但没考虑:
- 边界情况(时间用完怎么办?)
- 错误处理(非法操作如何拦截?)
- 状态一致性(数据可能冲突吗?)
经验丰富的开发者能预见这些问题,但如果不在提示词中明确,AI 往往会忽略。
提示词工程的核心原则
基于这些问题,我总结出了几个核心原则:
原则一:结构化 > 自然语言
❌ 模糊描述:1
帮我做一个 Dota2 的 BP 系统,要有英雄选择、倒计时这些功能
✅ 结构化描述:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20## 项目需求:Dota2 BP 模拟器
### 技术栈
- React 19 + TypeScript + Rsbuild
- CSS Modules(不用 Tailwind)
- Context + useReducer(不用 Redux)
### 核心功能
1. 英雄网格
- Grid 布局,50px 卡片
- 支持搜索和属性筛选
- 三种状态:已Ban/已Pick/可选
2. BP 流程
- 24 步固定顺序(见附录)
- 每回合 30 秒 + 所有回合总计 130 秒备用时间
- 时间用完自动随机选择
### 附录:BP 顺序
[详细列出 24 步...]
效果对比:
- 模糊描述:需要 5-10 轮对话才能理清需求
- 结构化描述:第一次就能生成 80% 可用的代码
原则二:约束 > 开放
给 AI 太多自由度反而是负担。明确的约束能让它专注在真正重要的部分。
❌ 开放式:1
实现一个状态管理方案
✅ 约束式:1
2
3
4
5
6
7
8使用 React Context + useReducer 实现状态管理:
- 不使用 Redux、Zustand 等第三方库
- 状态结构如下:
- roomId: string
- currentStep: number (0-23)
- bannedHeroes: number[]
- ...
- 需要的 Actions:BAN_HERO、PICK_HERO、NEXT_STEP...
为什么约束更好:
- 减少选择,提高效率
- 确保技术栈一致
- 代码风格更统一
原则三:示例 > 描述
一个好的示例胜过千言万语。
❌ 纯描述:1
BP 顺序面板要用两列布局,左边显示天辉的步骤,右边显示夜魇的步骤
✅ 带示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21BP 顺序面板采用两列布局:
布局示例:
┌─────────────────────┐
│ 天辉 │ 夜魇 │
├────────┼────────────┤
│ 1 Ban │ 2 Ban │
│ 4 Ban │ 3 Ban │
│ 7 Ban │ 5 Ban │
│ ... │ 6 Ban │
└────────┴────────────┘
实现方式:
const radiantSteps = BP_SEQUENCE.filter(step => step.team === 'radiant');
const direSteps = BP_SEQUENCE.filter(step => step.team === 'dire');
渲染两列:
<div className={styles.twoColumnLayout}>
<div className={styles.teamColumn}>{radiantSteps.map(...)}</div>
<div className={styles.teamColumn}>{direSteps.map(...)}</div>
</div>
原则四:规则 > 期望
与其告诉 AI 你期望什么结果,不如告诉它必须遵守什么规则。
❌ 期望式:
1 | 倒计时最好不要显示负数 |
✅ 规则式:
1 | 倒计时规则(必须严格遵守): |
原则五:上下文 > 片段
给 AI 足够的上下文,让它理解每个功能在整体中的位置。
❌ 孤立片段:1
实现一个英雄卡片组件
✅ 带上下文:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26## HeroCard 组件
### 在系统中的位置
- 父组件:HeroGrid(英雄网格)
- 数据来源:HEROES 常量(124 个英雄)
- 状态管理:通过 Context 获取 bannedHeroes、radiantPicks、direPicks
### 功能需求
显示单个英雄信息,根据 BP 状态展示不同样式:
1. 未使用:正常显示,可 hover
2. 已 Ban:灰度 + "已禁用"遮罩
3. 已 Pick:队伍颜色边框 + 队伍标识
4. 可选(当前轮次):亮色边框
5. 不可选(其他轮次):半透明 + 禁用点击
### Props 接口
interface HeroCardProps {
hero: Hero;
isBanned: boolean;
isPicked: boolean;
pickedByTeam: Team | null;
isSelected: boolean;
isAvailable: boolean;
onClick: () => void;
onHover: () => void;
}
实战:一个完整的提示词模板
基于以上原则,这是我总结的提示词模板:
1 | # [项目名称] |
src/
├── components/
│ ├── ComponentA/
│ │ ├── ComponentA.tsx
│ │ ├── ComponentA.module.css
│ │ └── index.ts
├── …1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## 四、核心功能
### 功能 A
**需求描述:** [清晰的业务需求]
**技术方案:** [指定实现方式]
**关键逻辑:** [伪代码或示例]
**边界情况:** [需要处理的特殊情况]
### 功能 B
[同上结构]
## 五、数据结构
### State 定义
```typescript
interface AppState {
field1: type; // 说明
field2: type; // 说明
}
Actions 列表
- ACTION_1: 描述
- ACTION_2: 描述
六、UI 规范
布局
- 整体:[描述总体布局]
- 区域 A:[尺寸、位置、内容]
- 区域 B:[同上]
颜色
- 主色:#xxx
- 辅色:#xxx
- 警告:#xxx
尺寸
- 组件 A:宽 x 高
- 间距:Xpx
- 圆角:Xpx
动画
- Hover:效果描述
- 点击:效果描述
- 过渡:时间 + 缓动函数
七、关键实现细节
问题 1:[描述一个技术难点]
错误做法: [常见错误]
正确做法: [正确方案]
关键代码: [示例代码]
问题 2:[同上]
八、测试清单
- 功能 A 正常工作
- 边界情况处理正确
- 响应式布局正常
- 无 linter 错误
九、注意事项
- [特别需要注意的点]
- [容易出错的地方]
- [性能考虑]实现一个倒计时功能,时间到了要有提示
1
2
3
4
5
6
## 实践案例:倒计时功能
让我们用这个模板实现刚才提到的倒计时功能:
### ❌ 第一版(模糊)1
2
3
4
5
6
7
8
9
10
11
12
13
**AI 生成结果:**
```typescript
const [time, setTime] = useState(30);
useEffect(() => {
const timer = setInterval(() => {
setTime(t => t - 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{time} 秒</div>;
问题:
- 时间会变成负数
- 没有备用时间机制
- 没有警告样式
- 时间到了没有任何操作
✅ 第二版(精确)
1 | ## 倒计时功能实现 |
必须处理的边界情况
- 防止负数显示: 所有时间值用
Math.max(0, value) - 防止 -1:-1 bug: 在
remainingTime <= 1时就处理,不要等到 0 - 随机选择逻辑:
1
2
3
4
5
6
7const usedHeroIds = new Set([
...bannedHeroes,
...radiantPicks,
...direPicks
]);
const available = HEROES.filter(h => !usedHeroIds.has(h.id));
const random = available[Math.floor(Math.random() * available.length)]; - 时间显示: 格式化为
MM:SS,如02:30
UI 要求
- 最后 10 秒:红色(#FF4444)+ 闪烁动画
- 字体:32px Courier New
- 警告动画:
1
2
3
4@keyframes warning {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
依赖项
useEffect 的依赖数组必须包含:
- state.phase
- state.remainingTime
- state.currentTeam
- state.currentAction
- state.bannedHeroes
- state.radiantPicks
- state.direPicks
- state.radiantReserveTime
- state.direReserveTime
- dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
**AI 生成结果:** 完整、正确、考虑周全的实现
## 提示词编写的实用技巧
### 1. 分层描述
将需求分为三个层次:
- **What(做什么):** 业务需求,给产品经理看的
- **How(怎么做):** 技术方案,给开发看的
- **Why(为什么):** 设计决策,给 AI 理解上下文
### 2. 先整体后局部
不要上来就让 AI 实现某个函数,而是:
1. 先描述整体项目结构
2. 再描述模块关系
3. 最后到具体实现
这样 AI 能理解代码在系统中的位置。
### 3. 用反例
告诉 AI 什么是错的,往往比告诉它什么是对的更有效:
```markdown
### 常见错误(不要这样做)
❌ 直接 `remainingTime - 1` 可能导致负数
❌ 在 `remainingTime === 0` 时处理太晚
❌ 忘记清理 setInterval
### 正确做法
✅ 使用 `Math.max(0, remainingTime - 1)`
✅ 在 `remainingTime <= 1` 时开始处理
✅ useEffect return 中清理定时器
4. 提供检查清单
在提示词最后加上验收标准:
1 | ## 完成标准 |
5. 迭代式提示词
不要期望一次就完美。我的做法是:
第一次: 给框架式提示词
- 项目结构
- 核心功能列表
- 技术栈
第二次: 看生成结果,补充细节
- “BP 顺序要改成两列”
- “倒计时有 bug,要修复”
第三次: 优化和调整
- “英雄卡片再小一点”
- “布局要更紧凑”
每次迭代,提示词都会更精确。
常见场景的提示词模式
场景 1:实现一个新组件
1 | ## [ComponentName] 组件 |
功能清单
- [功能 1]
- [功能 2]
样式要求
- 布局:[描述]
- 尺寸:[宽高]
- 颜色:[色值]
- 动画:[效果]
状态变化
- 状态 A:[样式描述]
- 状态 B:[样式描述]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
### 场景 2:修复一个 Bug
```markdown
## Bug 描述
**现象:** [详细描述问题]
**复现步骤:** [1, 2, 3]
**期望行为:** [应该怎样]
## 问题分析
**原因:** [技术原因]
**错误代码:**
```typescript
// 有问题的代码
修复方案
正确做法:1
// 修复后的代码
为什么这样改: [解释]
需要同时修改的地方
- 文件 A 的 X 函数
- 文件 B 的 Y 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
### 场景 3:优化性能
```markdown
## 性能问题
### 问题现象
- [卡顿/内存泄漏/重渲染...]
- 发生场景:[何时发生]
### 性能指标
- 当前:[FPS/加载时间/内存]
- 目标:[期望指标]
### 优化方案
1. **方案 A:** [描述]
- 优点:[...]
- 缺点:[...]
- 优先级:高/中/低
2. **方案 B:** [同上]
### 实施方案
选择方案 A,具体实现:
[详细步骤]
提示词工程的反模式
反模式 1:过度依赖 AI
错误心态:1
"反正 AI 会帮我想,我就简单说说"
问题:
- AI 不是你肚子里的蛔虫
- 模糊的输入 → 模糊的输出
- 反复返工浪费时间
正确心态:1
"我已经想清楚要什么,只是让 AI 帮我写代码"
反模式 2:期望 AI 理解隐含需求
错误示例:1
"实现一个专业的 BP 系统"
AI 不知道”专业”是什么标准。
正确做法:
明确定义”专业”:
- 布局紧凑,一屏展示
- 动画流畅
- 响应式适配
- 无障碍支持
- …
反模式 3:忽视技术约束
错误示例:1
"随便用什么技术栈都行"
问题:
- AI 可能选择你不熟悉的技术
- 代码风格不统一
- 维护成本高
正确做法:
明确技术栈和限制。
反模式 4:一次想要所有功能
错误示例:1
"实现完整的 Dota2 BP 系统,包括前端、后端、数据库、部署..."
问题:
- AI 容易顾此失彼
- 代码质量下降
- 整合困难
正确做法:
分模块、分阶段实现。
工具和辅助
1. 提示词模板库
我会维护一个自己的模板库:1
2
3
4
5
6templates/
├── component.md # 组件实现模板
├── feature.md # 功能实现模板
├── bugfix.md # Bug 修复模板
├── refactor.md # 重构模板
└── optimization.md # 优化模板
每次开始新任务,复制对应模板填空即可。
2. 需求清单
项目开始前,先列清单:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18## 技术栈确认
- [ ] 框架和版本
- [ ] 状态管理方案
- [ ] 样式方案
- [ ] 构建工具
## 功能清单
- [ ] 功能 A
- [ ] 功能 B
## UI 规范
- [ ] 颜色定义
- [ ] 尺寸标准
- [ ] 动画效果
## 技术决策
- [ ] 为什么用 X 不用 Y
- [ ] 核心难点如何解决
3. 代码审查清单
AI 生成代码后的检查项:1
2
3
4
5
6- [ ] 功能是否完整
- [ ] 边界情况是否处理
- [ ] 类型定义是否准确
- [ ] 性能是否有问题
- [ ] 代码风格是否一致
- [ ] 注释是否清晰
结语
回到文章开头的问题:如何让 AI 生成完美的代码?
答案是:完美的代码来自完美的需求。
AI 是工具,提示词是接口。就像我们调用 API 一样,参数传得清楚,返回结果才会准确。
如果你也有 AI 辅助编程的经验,欢迎一起分享~