最近用 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. 减少选择,提高效率
  2. 确保技术栈一致
  3. 代码风格更统一

原则三:示例 > 描述

一个好的示例胜过千言万语。

❌ 纯描述:

1
BP 顺序面板要用两列布局,左边显示天辉的步骤,右边显示夜魇的步骤

✅ 带示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BP 顺序面板采用两列布局:

布局示例:
┌─────────────────────┐
│ 天辉 │ 夜魇 │
├────────┼────────────┤
│ 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
倒计时规则(必须严格遵守):
1. 使用 Math.max(0, value) 确保不出现负数
2. 当 remainingTime <= 1 时开始处理归零逻辑
3. 归零后保持显示 0,不继续递减
4. 备用时间用完后自动执行随机选择

关键代码逻辑:
if (remainingTime <= 1) {
if (reserveTime > 0) {
使用备用时间,倒计时保持为 0
} else {
随机选择 + 进入下一步
}
} else {
正常递减:Math.max(0, remainingTime - 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# [项目名称]

## 一、项目背景
- 简述项目目的
- 目标用户
- 核心价值

## 二、技术栈(必选)
### 必须使用
- 框架:React 19
- 语言:TypeScript
- 构建工具:Rsbuild

### 必须不用
- 不用 Tailwind
- 不用组件库(MUI、Ant Design 等)
- 不用第三方状态管理(Redux、Zustand)

### 样式方案
- CSS Modules
- 手写响应式

## 三、项目结构

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. [性能考虑]
    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
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
## 倒计时功能实现

### 业务需求
实现 Dota2 BP 流程的计时器系统:
- 每回合基础时间:30 秒
- 每队备用时间:130 秒
- 基础时间用完后消耗备用时间
- 备用时间用完后自动随机选择未使用的英雄

### 技术方案
使用自定义 Hook:`useTimer`
- 只在 phase 为 'banning' 或 'picking' 时运行
- 每秒通过 dispatch 更新状态
- 返回格式化的时间和警告状态

### 关键逻辑(伪代码)
```typescript
每秒执行:
if (remainingTime <= 1) {
// 提前判断,防止显示负数
if (当前队伍备用时间 > 0) {
扣除 1 秒备用时间
主倒计时保持为 0
} else {
从未使用的英雄中随机选一个
执行 BAN 或 PICK
进入下一步
}
} else {
remainingTime = Math.max(0, remainingTime - 1)
}

必须处理的边界情况

  1. 防止负数显示: 所有时间值用 Math.max(0, value)
  2. 防止 -1:-1 bug:remainingTime <= 1 时就处理,不要等到 0
  3. 随机选择逻辑:
    1
    2
    3
    4
    5
    6
    7
    const usedHeroIds = new Set([
    ...bannedHeroes,
    ...radiantPicks,
    ...direPicks
    ]);
    const available = HEROES.filter(h => !usedHeroIds.has(h.id));
    const random = available[Math.floor(Math.random() * available.length)];
  4. 时间显示: 格式化为 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
2
3
4
5
6
7
## 完成标准
实现后必须满足:
- [ ] 倒计时不会显示负数
- [ ] 备用时间用完后自动选择
- [ ] 最后 10 秒红色警告
- [ ] 无 TypeScript 错误
- [ ] 无 ESLint 警告

5. 迭代式提示词

不要期望一次就完美。我的做法是:

第一次: 给框架式提示词

  • 项目结构
  • 核心功能列表
  • 技术栈

第二次: 看生成结果,补充细节

  • “BP 顺序要改成两列”
  • “倒计时有 bug,要修复”

第三次: 优化和调整

  • “英雄卡片再小一点”
  • “布局要更紧凑”

每次迭代,提示词都会更精确。

常见场景的提示词模式

场景 1:实现一个新组件

1
2
3
4
5
6
7
8
9
10
11
12
## [ComponentName] 组件

### 在系统中的位置
- 父组件:[Parent]
- 子组件:[Children]
- 数据来源:[Props/Context/API]

### Props 接口
```typescript
interface Props {
// 带注释
}

功能清单

  1. [功能 1]
  2. [功能 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
6
templates/
├── 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 辅助编程的经验,欢迎一起分享~