主题
如何写高质量开发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<FilterState>(initialFilters)
useEffect(() => {
const result = applyFilters(filters)
setFilters(result) // 更新过滤结果
}, [filters])我的判断
我怀疑是 setFilters 每次产生新引用导致 useEffect 重新触发, 但不确定 applyFilters 是否返回了新对象。
期望
- 确认问题原因
- 给出修复方案(至少两种)
- 说明各自的 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 目录有执行权限
审查维度
- 安全性(路径遍历、文件类型校验、权限)
- 错误处理(文件操作失败、数据库失败)
- 并发问题(同名文件)
输出格式
按严重程度排列: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)
- 不引入新的依赖
输出
- 缓存中间件代码
- 在更新接口中清除缓存的代码
- 不需要解释,只输出代码
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", ...) 清理逻辑
})期望
- 确认问题根因
- 给出修复代码
- 建议一个防止类似问题再次发生的模式
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 中就不需要重复声明这些基础信息。
参考资料
- OpenAI. Best Practices for Prompt Engineering. OpenAI Help Center.
- Prompt Engineering Guide. Generating Code - Prompt Engineering Guide. 2025.
- JP Stevens. The Developer's Guide to Prompt Engineering for AI Coding Assistants. dev.to, 2026.
- 阿里云. 文生文 Prompt 指南. 阿里云帮助文档.
- 火山引擎. AI编程 Prompt Engineering:高效代码生成的核心技巧. 火山引擎技术博客.
- 少个分号. 如何写好提示词:AI 协同开发实用指南. 个人技术博客.
- SSOJet. 25 Best Prompts for AI Coding Agents. 2026.
- Anthropic. Prompt Engineering Guide. Anthropic Docs.