Skip to content

如何写高质量开发Prompt

同一个需求,交给 AI 编程助手,出来的代码质量可能天差地别。一个人拿到的是可以直接合入主干的整洁实现,另一个人拿到的却是需要推倒重写的「能跑就行」。差距不在模型能力,而在 Prompt 的质量。

开发场景下的 Prompt 编写,本质上是一种「面向机器的需求沟通」。它不是自然语言聊天,也不是写注释。它的核心目标是:让 AI 在最少追问的情况下,产出符合你预期的代码。

本文系统梳理开发 Prompt 的编写原则、结构化方法、常见场景模板、典型错误和优化策略,附带可直接复用的模板和对比案例。


开发 Prompt 的四个核心原则

原则一:明确(Clarity)

「明确」意味着 AI 不需要猜你的意图。每一个动词都应该指向具体的动作,每一个名词都应该有可识别的对象。

差的写法:

帮我优化这段代码

好的写法:

将下面这段 TypeScript 函数从嵌套回调改为 async/await 风格,
保留原有的错误处理逻辑,不改变函数签名。

差在哪里?「优化」是一个没有边界的词。是性能优化?可读性优化?类型安全优化?好的写法把「优化」拆解为具体的动作:改风格、保留逻辑、不变签名。

原则二:具体(Specificity)

具体性体现在三个维度:技术栈具体、行为具体、边界具体。

用 React 18 + TypeScript 写一个用户搜索组件

这比「写个搜索组件」好得多,但仍然不够具体。更具体的写法:

用 React 18 + TypeScript 写一个用户搜索组件:
- 使用 <input> 实时搜索,输入防抖 300ms
- 调用 GET /api/users?q=keyword 接口
- 下拉展示最多 10 条结果,显示用户名和邮箱
- 选中后触发 onSelect 回调,参数为用户对象
- 无结果时显示「未找到匹配用户」
- 不使用任何第三方 UI 库

原则三:有上下文(Context)

AI 不了解你的项目。你需要主动提供它需要的上下文:技术栈版本、现有代码结构、项目约定、相关文件的关键信息。

我们的项目使用 Next.js 15 App Router,数据库用 PostgreSQL,
ORM 是 Prisma。现有的用户模型定义在 prisma/schema.prisma 中,
包含 id、email、name、createdAt 四个字段。
现在需要在用户模型上新增一个 phone 字段(可选),
并实现一个 API route 来更新用户的手机号。

没有上下文的 Prompt,AI 只能凭训练数据中的「最大概率」来猜测。它可能给你写一个 Class Component,可能用 REST 而你的项目是 GraphQL,可能引入你项目里不存在的依赖。

原则四:有约束(Constraints)

约束是 Prompt 中被忽略最多的部分,也是产出质量差异最大的来源。约束包括:

  • 风格约束:「遵循项目现有的 Biome 配置,使用单引号、无尾分号」
  • 依赖约束:「只使用已有的依赖,不引入新包」
  • 架构约束:「业务逻辑放 service 层,不要在 route handler 里直接写」
  • 兼容性约束:「需要兼容 Node.js 20+」
  • 安全约束:「SQL 查询必须使用参数化查询,禁止字符串拼接」

没有约束的代码生成,就像让一个不知道公司规范的新人写代码——能用,但不一定能融入。


结构化 Prompt 的写法

一个完整的开发 Prompt 可以拆解为五个模块。不是每次都需要全部模块,但模块越完整,输出质量越稳定。

RTCO 结构

模块说明示例
Role(角色)让 AI 扮演特定身份「你是一个有 10 年经验的 Go 后端工程师」
Task(任务)要做什么,用动词开头「实现一个带重试逻辑的 HTTP 客户端」
Context(上下文)技术栈、现有代码、项目约定「项目使用 Go 1.22,标准库 net/http,错误处理用 sentinel error」
Output(输出)期望的输出格式和内容「只输出代码,不需要解释;每个函数加 GoDoc 注释」
Constraints(约束)风格、依赖、安全、性能等限制「不使用第三方库;超时 30s;最多重试 3 次,指数退避」

Prompt 结构模板

markdown
## 角色
你是一个资深的 [技术栈] 工程师,熟悉 [相关技术]。

## 任务
[用一句话描述要做什么]

## 上下文
- 技术栈:[语言、框架、版本]
- 现有代码:[贴出关键代码或描述结构]
- 项目约定:[命名规范、目录结构、编码风格]

## 约束
- [约束 1]
- [约束 2]
- [约束 3]

## 输出格式
- [期望输出什么,不要什么]

常见场景的 Prompt 模板

场景一:写新代码

markdown
## 任务
用 TypeScript 实现一个函数 parseDuration,将人类可读的时间字符串
(如 "1h30m"、"2d"、"500ms")转换为毫秒数。

## 约束
- 支持的单位:ms、s、m、h、d
- 不支持的输入抛出 TypeError,message 为 "Invalid duration format: {input}"
- 输入为空字符串返回 0
- 使用正则匹配,不使用任何第三方库
- 需要完整的 JSDoc 注释
- 同时提供对应的单元测试(vitest)

## 输出
只输出代码和测试代码,不需要解释。

场景二:改代码 / 重构

markdown
## 任务
将下面这段 Python 代码从同步风格改为异步风格。

## 当前代码
```python
def fetch_users():
    response = requests.get("https://api.example.com/users")
    data = response.json()
    return [User(**item) for item in data]

约束

  • 使用 httpx.AsyncClient 替代 requests
  • 保留 User 数据模型不变
  • 添加异常处理:网络超时返回空列表,HTTP 错误抛出
  • 函数签名改为 async def fetch_users() -> list[User]

输出

输出修改后的完整代码,用注释标注改动点。


### 场景三:解释代码

```markdown
## 任务
解释下面这段 Rust 代码的工作原理。

## 代码
```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

输出要求

  • 分三部分说明:函数签名中的生命周期标注、函数体逻辑、调用时的约束
  • 面向了解 Rust 基础语法但不熟悉生命周期的开发者
  • 给出一个编译通过和一个编译失败的调用示例

### 场景四:调试代码

```markdown
## 任务
排查一个 React useEffect 导致的无限渲染问题。

## 技术栈
React 18 + TypeScript + Zustand

## 现象
组件挂载后 console.log 持续输出,页面卡死。

## 相关代码
```tsx
const [filters, setFilters] = useState&lt;FilterState>(initialFilters)

useEffect(() => {
  const result = applyFilters(filters)
  setFilters(result) // 更新过滤结果
}, [filters])

我的判断

我怀疑是 setFilters 每次产生新引用导致 useEffect 重新触发, 但不确定 applyFilters 是否返回了新对象。

期望

  1. 确认问题原因
  2. 给出修复方案(至少两种)
  3. 说明各自的 trade-off

### 场景五:Code Review

```markdown
## 任务
审查以下代码,关注安全、性能和可维护性。

## 代码
```typescript
app.post("/api/upload", async (req, res) => {
  const file = req.files.file
  const path = `./uploads/${file.name}`
  file.mv(path)
  await db.files.create({ name: file.name, path, userId: req.user.id })
  res.json({ ok: true })
})

上下文

  • Express + express-file-upload
  • 部署在 Linux 服务器,uploads 目录有执行权限

审查维度

  1. 安全性(路径遍历、文件类型校验、权限)
  2. 错误处理(文件操作失败、数据库失败)
  3. 并发问题(同名文件)

输出格式

按严重程度排列:Critical > Warning > Suggestion, 每条给出问题描述、风险说明、修复建议。


---

## 好 Prompt vs 差 Prompt 对比

| 维度 | 差 Prompt | 好 Prompt | 差距在哪里 |
|------|----------|----------|-----------|
| 写新功能 | 「帮我写一个登录页面」 | 「用 Next.js 15 App Router 写登录页:email + password 表单,调用 POST /api/auth/login,成功后跳转 /dashboard,失败显示错误 Toast,表单用 react-hook-form + zod 校验」 | 技术栈、交互、错误处理、依赖 |
| 改 Bug | 「这段代码有 bug,帮我修」 | 「下面这段 Go 代码在并发调用时出现 data race,go test -race 报错在第 42 行。代码如下:[贴代码]。我怀疑是 map 的并发读写,请确认并给出修复方案」 | 复现步骤、错误信息、自己的判断 |
| 重构 | 「让这段代码更干净」 | 「将下面的 200 行函数拆分为多个子函数,每个子函数不超过 30 行。提取公共逻辑到工具函数。保持对外接口不变,所有现有测试通过」 | 可量化的目标、约束、验证标准 |
| 解释代码 | 「解释这段代码」 | 「解释下面这段 TypeScript 类型体操代码。面向中级开发者,分步说明每个类型的作用,给出每个中间类型的计算结果示例」 | 目标受众、输出结构、具体程度 |

---

## 不同工具的 Prompt 差异

| 工具 | Prompt 入口 | 上下文获取 | 适合的 Prompt 风格 | 注意事项 |
|------|------------|-----------|-------------------|---------|
| **Cursor(Composer)** | Chat / Cmd+K / Composer | 自动读取当前文件和 @ 引用的文件 | 在 Prompt 中引用 `@file` 提供上下文,任务描述要具体 | Composer 支持多文件编辑,适合跨文件重构任务 |
| **Claude Code** | 终端 CLI 对话 | 自动搜索和读取项目文件 | 可以用自然语言描述需求,Claude 会主动搜索相关代码 | 配合 CLAUDE.md 写项目级约定,减少重复上下文 |
| **GitHub Copilot** | 行内补全 / Chat | 基于当前文件和打开的 tab | 用注释描述意图,让 Copilot 根据注释补全代码 | 行内补全适合短函数和单文件操作,复杂任务用 Chat |
| **ChatGPT / Claude 对话** | Web / API 对话 | 需要手动贴代码 | 必须显式提供所有上下文:技术栈、代码、约束 | 没有项目感知能力,上下文越全越好 |

---

## 常见错误和优化方法

### 错误清单

| 编号 | 常见错误 | 问题表现 | 优化方法 |
|------|---------|---------|---------|
| 1 | 需求模糊 | AI 反复追问或给出无关代码 | 用「动词 + 具体对象 + 期望结果」描述任务 |
| 2 | 缺少技术栈 | 生成的代码用了你没安装的库 | 明确声明语言版本、框架、已有依赖 |
| 3 | 一次给太大任务 | 代码不完整、逻辑断裂 | 拆解为子任务,逐步生成 |
| 4 | 没有贴错误信息 | AI 瞎猜问题原因 | 直接贴完整的错误堆栈和复现步骤 |
| 5 | 不给现有代码 | AI 重写而非修改 | 贴出相关代码,说明哪些可以改、哪些不能动 |
| 6 | 没有输出约束 | 输出一大堆解释,代码反而难找 | 明确「只输出代码」「用 Markdown 代码块」 |
| 7 | 重复提交相同 Prompt | 得到不同的随机结果 | 换一种描述方式,增加约束条件 |
| 8 | 忽略项目约定 | 代码能跑但不符合项目风格 | 在 Prompt 中引用项目的规范文件或 CLAUDE.md |

### 错误示例拆解

**示例 A:模糊的调试请求**

我的 API 返回 500 了,帮我修一下


AI 不知道你的 API 是什么、用的什么框架、请求参数是什么。它只能给出通用的排查建议。

**优化后:**

Express + TypeScript 项目,POST /api/orders 接口返回 500。 请求体:{ "productId": "abc", "quantity": 2 } 错误日志:TypeError: Cannot read properties of undefined (reading 'price') at calculateTotal (src/services/order.ts:42:18) 相关代码:[贴出 order.ts 的 calculateTotal 函数] 数据库查询返回的 Product 对象结构:


**示例 B:过于庞大的重构任务**

把这个 2000 行的文件重构一下


AI 面对 2000 行代码,要么截断输出,要么产出一个面目全非的重构。

**优化后:**

我需要重构 src/services/payment.ts 中的 processPayment 函数(约 300 行)。 第一步:分析这个函数做了哪些事情,列出职责清单。 不要改代码,只输出分析结果。

等我确认分析后,再进行下一步拆分。


分步执行,每一步验证,远比一次性扔过去效果好。

---

## 案例:同一需求的不同 Prompt 效果对比

### 案例一:实现数据缓存

**需求:给 API 接口加缓存**

Prompt A:

帮我给 API 加个缓存


AI 可能的输出:一段 Redis 缓存代码,或者内存缓存,或者 CDN 缓存——完全看运气。代码可能是 Python 的,而你的项目是 Node.js。

Prompt B:

任务

为 Express API 的 GET /api/products 接口添加内存缓存。

上下文

  • Node.js 20 + Express 4 + TypeScript
  • 接口当前直接查 PostgreSQL,响应时间约 200ms
  • 产品数据变更频率低,每天最多更新 10 次
  • 项目已有 node-cache 依赖

约束

  • 缓存 TTL 5 分钟
  • 缓存 key 按请求参数区分
  • 产品更新时清除相关缓存(更新接口在 src/routes/products.ts 的 PUT /:id)
  • 不引入新的依赖

输出

  1. 缓存中间件代码
  2. 在更新接口中清除缓存的代码
  3. 不需要解释,只输出代码

Prompt A 的输出大概率不可用。Prompt B 的输出可以直接集成到项目中,只需要微调导入路径。

### 案例二:排查内存泄漏

**需求:Node.js 服务内存持续增长**

Prompt A:

我的 Node.js 服务内存泄漏了,怎么排查


AI 会给你一篇通用排查指南:用 heapdump、看事件监听器、检查全局变量……正确但无用,因为你需要的针对你代码的诊断。

Prompt B:

任务

诊断一个 Node.js 服务的内存泄漏问题。

上下文

  • Node.js 20 + Fastify + Prisma
  • 服务运行 48 小时后内存从 200MB 增长到 1.2GB
  • 使用 pm2 管理进程
  • 主要处理 WebSocket 连接,平均在线 500 个客户端

已排查

  • 用 --inspect 抓了两次 heap snapshot,对比后发现 Socket 对象和 callback 函数数量持续增长
  • 客户端断开连接后,相关对象没有被回收

相关代码

typescript
wss.on("connection", (ws) => {
  const handler = createMessageHandler(ws)
  ws.on("message", handler)
  // 没有 ws.on("close", ...) 清理逻辑
})

期望

  1. 确认问题根因
  2. 给出修复代码
  3. 建议一个防止类似问题再次发生的模式

Prompt B 给出了精确的修复方案和防御性编程模式,因为它有了足够的信息来定位问题。

---

## 写 Prompt 的思考流程

用 Mermaid 流程图表示编写 Prompt 时的决策过程:

```mermaid
flowchart TD
    A[明确要做什么] --> B{AI 能否看到相关代码?}
    B -->|能| C[引用文件路径或 @file]
    B -->|不能| D[贴出关键代码片段]
    C --> E{任务复杂度如何?}
    D --> E
    E -->|单步可完成| F[写一个完整 Prompt]
    E -->|需要多步| G[拆分为子任务序列]
    F --> H{需要约束吗?}
    G --> H
    H -->|有风格/依赖/安全要求| I[添加约束条件]
    H -->|无特殊要求| J[直接发送]
    I --> J
    J --> K{输出满意吗?}
    K -->|满意| L[完成]
    K -->|不满意| M{问题是出在哪?}
    M -->|结果偏离| N[补充上下文或约束]
    M -->|格式不对| O[明确输出格式]
    M -->|代码有 bug| P[贴出错误信息]
    N --> J
    O --> J
    P --> J

这个流程的核心思路:先想清楚,再写下来;一次不行,针对性补充。


Prompt 编写检查清单

每次编写 Prompt 时,对照以下清单逐项检查:

  • [ ] 任务是否用动词开头?「实现」「修改」「解释」「排查」比「弄一下」「看看」「搞搞」更有效
  • [ ] 是否声明了技术栈和版本?语言、框架、主要依赖的版本号
  • [ ] 是否提供了相关代码?不是「这段代码」,而是直接贴出代码或引用文件路径
  • [ ] 是否说明了期望行为?修改后应该怎样运行、返回什么结果
  • [ ] 是否包含了错误信息?排查问题时,完整的错误堆栈是必须的
  • [ ] 是否列出了约束条件?风格、依赖、安全、性能、兼容性
  • [ ] 是否指定了输出格式?只要代码?还是要带注释、带测试、带解释?
  • [ ] 任务是否足够小?一个 Prompt 解决一个问题,不要贪多
  • [ ] 是否避免了主观词汇?「更好的」「更干净的」→ 用可衡量的标准替代
  • [ ] 是否考虑了 AI 的信息盲区?它不知道你的项目结构、团队约定、历史决策
  • [ ] 是否在 Prompt 结尾提出了明确的问题?以问号结尾比句号更容易得到精准回答
  • [ ] 是否做过至少一轮迭代?第一版 Prompt rarely 完美,补充和修正才是常态

高级技巧

用「反面示例」约束行为

当你发现 AI 总是犯某个特定错误时,直接在 Prompt 中列出「不要这样做」:

## 约束
- 不要使用 any 类型,必须给出具体类型
- 不要用 console.log 做日志,使用项目已有的 logger 模块
- 不要创建新的工具函数文件,除非逻辑在 3 个以上文件中使用
- 不要在组件内部定义样式对象,提取到独立的 styles 常量

用「分步对话」处理复杂任务

对于涉及多个文件的修改,不要试图在一个 Prompt 中完成:

第一轮:分析现有代码结构,输出依赖关系图
第二轮:确认分析后,给出重构方案
第三轮:确认方案后,逐文件输出修改后的代码
第四轮:输出需要新增的测试用例

每一轮你都有一次检查机会,避免最终结果偏离预期。

用「项目规则文件」减少重复

大多数 AI 编程工具都支持项目级配置文件(CLAUDE.md、.cursorrules、COPILOT_INSTRUCTIONS.md)。把以下内容写入配置文件,每次对话自动生效:

  • 技术栈和版本
  • 编码风格规范
  • 目录结构约定
  • 常用命令
  • 禁止事项

这样你在每次 Prompt 中就不需要重复声明这些基础信息。


参考资料

  1. OpenAI. Best Practices for Prompt Engineering. OpenAI Help Center.
  2. Prompt Engineering Guide. Generating Code - Prompt Engineering Guide. 2025.
  3. JP Stevens. The Developer's Guide to Prompt Engineering for AI Coding Assistants. dev.to, 2026.
  4. 阿里云. 文生文 Prompt 指南. 阿里云帮助文档.
  5. 火山引擎. AI编程 Prompt Engineering:高效代码生成的核心技巧. 火山引擎技术博客.
  6. 少个分号. 如何写好提示词:AI 协同开发实用指南. 个人技术博客.
  7. SSOJet. 25 Best Prompts for AI Coding Agents. 2026.
  8. Anthropic. Prompt Engineering Guide. Anthropic Docs.

基于 MIT 协议开源