程序员的提示工程手册

和 Copilot、Cursor 这类助手协作,输出质量高度取决于你怎么开口。这份手册把提示工程拆成一套总原则加十种可复用技巧。

作者:小互 AI 小教程06-01 · 22 分钟⚑ 方法教程
AI 实操 · 实操库
一分钟速览
  • 先立总原则:提示工程不是玄学,是能写成方法的工程实践。
  • 十种技巧逐条给:角色设定、明确背景、输入输出示例、迭代链式拆解、模拟调试。
  • 每种技巧都配可直接抄的写法,不是抽象说明。
  • 重点在「怎么把模糊需求翻译成 AI 能执行的指令」,这是补全率的分水岭。
  • 适合已经在用 AI 编程助手、但总觉得输出不稳的人。

随着 AI 编程助手(AI pair programmers)的普及,“提示工程”(Prompt Engineering)已成为现代开发者的核心技能之一。

“提示工程”(Prompt Engineering)不是玄学,而是一个严谨的方法体系,是和 AI 工具(如 Copilot、ChatGPT、Cursor 等)高效协作的关键能力。

AI 助手越来越能完成代码补全、调试、优化、生成文档等任务。但它的输出质量高度依赖你的输入提示。你如何“问”,决定了 AI 回答的深度与准确性。

这篇文章系统性地介绍了如何通过构造高质量的提示语(prompts),最大程度发挥 AI 辅助编码工具的效能,涵盖:

  • 调试(Debugging)
  • 重构(Refactoring)
  • 新功能开发(Feature Implementation)
  • 性能优化、测试生成、风格控制等场景

核心原则(Prompt 编写总原则)

  1. 提供清晰上下文(Context)

> AI 无法“读心”,提供语言/框架/库、报错信息、目标功能细节是基本要求。

  1. 目标明确(Specificity)

> 不要问“为什么我的代码不工作”,而要说“我期望 X,结果是 Y,代码如下,哪里错了?”

  1. 逐步拆解复杂任务(Break Down)

> 大问题要拆分:先生成骨架,再补充细节,逐步推进。

  1. 给出输入输出示例(I/O Examples)

> 类似“few-shot learning”:明确展示输入、期望输出,可极大提升准确率。

  1. 设定角色(Role Prompting)

> 例如:“你是资深 TypeScript 开发者,请审查以下代码”,AI 会采用相应语境。

  1. 交互式迭代(Iterative Refinement)

> AI 并非一次性工具,多轮交互是常态,应不断反馈和补充新要求。

  1. 保持代码一致性和清晰性

> 整洁的代码、命名和注释不仅帮人,也能给 AI 提供学习模式,提升回答质量。

AI 代码助手·10 种高效提示技巧

1. 角色设定 Role Prompting

Prompt(EN): “You are a senior {language} developer. Review this function for {goal}.” 提示(中): “你是一名资深 {language} 开发者,请针对 {goal} 审查此函数。” 目的: 模拟专家级代码评审、调试或重构。

2. 明确问题背景 Explicit Context Setup

Prompt(EN): “Here’s the problem: {summary}. The code is below. It should do {expected behavior}, but instead it’s doing {actual behavior}. Why?” 提示(中): “这是问题:{summary}。下面是代码。本应 {expected behavior},但实际却 {actual behavior}。为什么?” 目的: 清晰框定问题,避免笼统回答。

3. 输入/输出示例 Input–Output Examples

Prompt(EN): “This function should return {expected output} when given {input}. Can you write or fix the code?” 提示(中): “当输入 {input} 时,此函数应返回 {expected output}。你能编写或修复代码吗?” 目的: 用示例引导模型精准理解意图。

4. 迭代链式拆解 Iterative Chaining

Prompt(EN): “First, generate a skeleton of the component. Next, we’ll add state. Then handle API calls.” 提示(中): “首先生成组件骨架;接着添加状态;然后处理 API 调用。” 目的: 将大型任务拆成步骤,避免过度泛化。

5. 模拟调试 Debug with Simulation

Prompt(EN): “Walk through the function line by line. What are the variable values? Where might it break?” 提示(中): “逐行检查该函数。各变量值是多少?可能在哪儿出错?” 目的: 让模型模拟运行时行为,暴露隐藏错误。

6. 功能蓝图 Feature Blueprinting

Prompt(EN): “I’m building {feature}. Requirements: {bullets}. Using: {tech stack}. Please scaffold the initial component and explain your choices.” 提示(中): “我在构建 {feature}。需求:{bullets}。技术栈:{tech stack}。请搭建初始组件并说明理由。” 目的: 借助 AI 进行规划与脚手架,快速启动功能开发。

7. 重构指导 Code Refactor Guidance

Prompt(EN): “Refactor this code to improve {goal} (e.g., readability, performance, idiomatic style). Use comments to explain changes.” 提示(中): “重构此代码以提升 {goal}(如:可读性、性能、代码风格),并用注释说明改动。” 目的: 使重构紧扣目标,而非随意改动。

8. 请求替代方案 Ask for Alternatives

Prompt(EN): “Can you rewrite this using a functional style? What would a recursive version look like?” 提示(中): “能否用函数式风格重写?递归版本会怎样?” 目的: 探索多种实现路径,扩展技术工具箱。

9. 橡皮鸭调试 Rubber Ducking

Prompt(EN): “Here’s what I think this function does: {your explanation}. Am I missing anything? Does this reveal any bugs?” 提示(中): “我认为该函数作用是:{your explanation}。有遗漏吗?暴露出任何 bug 吗?” 目的: 让 AI 质疑你的理解,发现潜在问题。

10. 约束锚定 Constraint Anchoring

Prompt(EN): “Please avoid (e.g., recursion) and stick to (e.g., ES6 syntax, no external libraries). Optimize for (e.g., memory). Here’s the function:” 提示(中): “请避免(如:递归),并保持(如:ES6 语法、无外部库)。针对(如:内存)进行优化。以下是函数:” 目的: 防止模型超范围发挥或引入不兼容模式。

用法小贴士

  • 在模板占位符 {} 内填写具体语言、目标、示例等,可立即生成高质量、上下文贴合的 AI 编程助手提示。
  • 结合多条技巧(如先角色设定,再链式拆解)往往效果更佳。

构建提示的通用模板(Prompt Template)

🎯 你希望做什么?(功能目标/问题说明)

📦 提供背景信息(语言/框架/环境/代码片段)

💥 出现了什么问题?(错误提示/不符合预期)

✅ 希望输出什么结果?(结构/格式/风格)

🔁 有哪些限制或偏好?(使用迭代实现、用某个库)

👤 AI 扮演什么角色?(资深架构师 / React 教练等)

📌 示例输入输出(可选)

以下为全文翻译

程序员的提示工程手册

开发者日益依赖 AI 代码助手来加速日常工作流。这些工具可以自动补全函数、提出 Bug 修复建议,甚至生成整个模块或 MVP。

不过,正如许多人已体会到的,AI 输出质量 很大程度上取决于你提供的 提示词(prompt)质量。换句话说,提示词工程 已成为一项必备技能。措辞不当的请求会得到含糊或套路化的回答,而措辞良好的请求可以产出深思熟虑、准确甚至颇具创意的代码解决方案。

本文将以实战视角,系统讲解如何为常见开发任务打造高效提示词。

AI 搭档程序员强大却并非魔法——除了你提供的上下文,它对你的具体项目或意图一无所知。信息越丰富,输出越精准。

我们将提炼关键的提示词模式、可复用框架 与令开发者印象深刻的示例。你将看到 好提示 vs. 坏提示 的并排对比及实际 AI 响应,并附有为何优劣分明的评注。以下是一张速查表:

高效代码提示的基础

给 AI 代码工具写提示词,有点像与一位非常字面、有时又博学的合作者沟通。想要获得有用结果,你必须清晰布景,引导 AI 明确 想要什么、如何要。

以下是本手册中所有示例所依赖的基础原则:

  • 提供丰富的上下文。 始终假设AI除了你提供的内容之外对你的项目一无所知。包括相关的细节,如编程语言、框架和库,以及所讨论的特定函数或片段。如果有错误,提供确切的错误消息并描述代码应该做什么。具体性和上下文是模糊建议和精确、可操作的解决方案之间的区别。在实践中,这意味着你的提示可能包括一个简短的设置,例如:“我有一个使用Express和Mongoose的Node.js函数,应该按ID获取用户,但它抛出了TypeError。这是代码和错误…”你提供的设置越多,AI需要猜测的就越少。
  • 对你的目标或问题要具体。 模糊的查询会导致模糊的答案。与其问类似“为什么我的代码不工作?”的问题,不如指明你需要什么见解。例如:“这个JavaScript函数返回undefined而不是预期的结果。鉴于以下代码,你能帮我识别为什么以及如何修复吗?”这样更有可能得到有用的答案。调试的一个提示公式是:“它应该做[预期行为],但在给定[示例输入]时却做了[当前行为]。错误在哪里?”类似地,如果你想要优化,请求一种特定的优化(例如“如何提高这个排序函数在10k项目上的运行时性能?”)。具体性引导AI的焦点。
  • 分解复杂的任务。 在实现新功能或解决多步骤问题时,不要在一个巨大的提示中提供整个问题。通常将工作分解成更小的块并迭代更有效。例如,“首先,为产品列表页面生成React组件骨架。接下来,我们将添加状态管理。然后,我们将集成API调用。”每个提示都建立在前一个的基础上。通常不建议一次性要求整个大功能;相反,从高层目标开始,然后迭代地请求每个部分。这种方法不仅使AI的响应保持集中和可管理,而且还反映了人类如何逐步构建解决方案。
  • 包括输入/输出的示例或预期行为。 如果你可以用一个示例来说明你想要什么,那就这样做。例如,“给定数组[3,1,4],这个函数应该返回[1,3,4]。”在提示中提供一个具体的示例有助于AI理解你的意图并减少歧义。这类似于给初级开发人员一个快速的测试用例——它澄清了需求。在提示工程术语中,这有时被称为“few-shot prompting”,即你向AI展示一个要遵循的模式。即使是一个正确行为的示例也可以显著指导模型的响应。
  • 利用角色或人物。 许多病毒式提示示例中流行的一种强大技术是要求AI“扮演”某个角色或角色。这可以影响答案的风格和深度。例如,“扮演高级React开发人员,审查我的代码以查找潜在错误”或“你是JavaScript性能专家。优化以下函数。”通过设置角色,你让助手采用相关的语气——无论是严格的代码审查员、帮助初级开发人员的老师,还是寻找漏洞的安全分析师。社区共享的提示显示了这种方法的成功,例如“扮演JavaScript错误处理程序,为我调试这个函数。数据没有从API调用中正确渲染。”在我们自己的使用中,我们仍然必须提供代码和问题细节,但角色扮演提示可以产生更结构化和专家级的指导。

``` Prompt 提示词 EN: “Act as a senior React developer and review my code for potential bugs.”

ZH: “扮演资深 React 开发者,审查我的代码并指出潜在 Bug。” 通过设定角色,你让助手承担相应语气与职责——严谨的代码审阅者、循循善诱的导师、或安全分析师。 ```

  • 迭代和完善对话。 提示工程是一个互动的过程,而不是一次性交易。开发人员通常需要审查AI的第一个答案,然后提出后续问题或进行更正。如果解决方案不完全正确,你可以说,“该解决方案使用递归,但我更喜欢迭代方法——你能不使用递归再试一次吗?”或者,“很好,现在你能改善变量名并添加注释吗?”AI在聊天会话中记住上下文,因此你可以逐步引导它达到预期的结果。关键是将AI视为你可以指导的合作伙伴——追求进步而不是第一次就完美。
  • 保持代码清晰度和一致性。 这一最后原则有点间接,但对于在你的代码上下文上工作的工具非常重要。编写干净、结构良好的代码和注释,即使在AI发挥作用之前。有意义的函数和变量名、一致的格式和文档字符串不仅使你的代码对人类更容易理解,而且给AI提供了关于你在做什么的更强提示。如果你展示一致的模式或风格,AI将继续它。将这些工具视为极其专注的初级开发人员——他们从你的代码和注释中获取每一个提示。

在牢记这些基础原则的基础上,让我们深入探讨具体场景。我们将从调试开始,这可能是最直接的用例:你有行为不正确的代码,你希望AI帮助弄清原因。

了解这些基础后,我们首先进入最直接的场景——调试。

Prompt patterns for debugging code / 调试代码的提示词模式

调试是AI助手的自然适合场景。这就像有一个橡皮鸭,不仅倾听,而且实际以建议回应。然而,成功在很大程度上取决于你如何向AI呈现问题。以下是如何系统地提示以帮助发现和修复错误:

1. 清晰地描述问题和症状。 首先描述出什么问题以及代码应该做什么。始终包括确切的错误消息或不正确行为。

例如,与其只说“我的代码不工作”,你可能会提示:

**“我有一个JavaScript函数,应该计算数字数组的和,但它返回NaN(不是数字)而不是实际的和。这是代码:[包含代码]。对于像[1,2,3]这样的数字数组,它应该输出一个数字(和),但我得到NaN。可能是什么导致了这个错误?”

**这个提示指定了语言、预期行为、观察到的错误输出,并提供了代码上下文——所有这些都是关键信息。

提供结构化的上下文(代码 + 错误 + 预期结果 + 你尝试过的)

为AI提供了一个坚实的起点。相比之下,像“为什么我的函数不工作?”这样的通用问题会产生微不足道的成果——模型只能在没有上下文的情况下提供最笼统的猜测。

2. 对于棘手的错误,使用逐步或逐行的方法。 对于更复杂的逻辑错误(没有抛出明显的错误消息但输出错误),你可以提示AI逐步执行代码。

例如:“逐行浏览这个函数,并在每一步跟踪total的值。它没有正确累积——逻辑在哪里出错?”这是橡皮鸭调试提示的一个例子——你本质上是在要求AI模拟人类使用打印或调试器进行的调试过程。

这样的提示通常会揭示微妙的问题,如变量未重置或不正确的条件逻辑,因为AI会阐明每一步的状态。

如果你怀疑代码的某个部分,你可以放大:

“解释这里的filter调用在做什么,以及它是否可能排除了比应该的更多的项目。”

让AI扮演解释角色可以在解释过程中揭示错误。

3. 尽可能提供最小的可重现示例。 有时你的实际代码库很大,但错误可以在一个小片段中重现。如果你可以提取或简化仍然重现问题的代码,请这样做并将其提供给AI。这不仅使AI更容易集中注意力,而且迫使你澄清问题(这本身就是一个有用的练习)。

例如,如果你在深层嵌套的函数调用中遇到TypeError,尝试用几行代码重现它。旨在用最少的代码隔离错误,假设可能出错的地方,测试它,并迭代。

你可以通过说:“这是一个精简的示例,仍然触发错误[包含片段]。为什么会发生这个错误?”来让AI参与其中。通过简化,你消除了噪音,帮助AI准确定位问题。(这种技术反映了许多高级工程师的建议:如果你不能立即找到错误,简化问题空间。AI可以帮助你进行这种分析,如果你向它展示一个更小的情况。)

4. 提出集中的问题和后续问题。 在提供上下文后,通常直接提出你需要什么,例如:“什么可能导致这个问题,以及我如何修复它?”这邀请AI诊断并提出解决方案。如果AI的第一个答案不清楚或部分有用,不要犹豫提出后续问题。**

你可以说,“那个解释有道理。你能告诉我如何修复代码吗?请提供更正后的代码。”在聊天设置中,AI有对话历史记录,因此可以直接输出修改后的代码。如果你在VS Code中使用Copilot或Cursor这样的内联工具而没有聊天,你可能会在代码上方写一个注释,如// BUG: returns NaN, fix this function,然后看它如何自动完成——但通常,交互式聊天会产生更彻底的解释。

另一个后续模式:如果AI给出了修复方法但你不明白为什么,可以问“你能解释为什么那个改变解决了问题吗?”**这样你为下次学习,并且你可以仔细检查AI的推理是否合理。

现在,让我们用一个具体的例子来说明这些调试提示原则,展示糟糕的提示与改进的提示以及AI响应的差异:

调试示例:糟糕的提示与改进的提示

想象我们有一个简单的Node.js函数,旨在将用户对象列表转换为按用户ID的查找映射。然而,它抛出了一个错误。这是错误的代码:

// 有错误的函数:将用户数组转换为按ID的映射
function mapUsersById(users) {
  const userMap = {};
  for (let i = 0; i <= users.length; i++) {
    // 注意:使用<=会在最后一次迭代时越界
    const user = users[i];
    userMap[user.id] = user;
  }
  return userMap;
}

// 示例用法:
const result = mapUsersById([{ id: 1, name: "Alice" }]);

这段代码应该遍历用户数组并构建一个以用户ID为键的对象。然而,有一个错误:循环使用i <= users.length而不是i < users.length。这将导致users[i]在最后一次迭代时为undefined,可能会导致运行时错误(例如,尝试访问undefined的id属性)。让我们看看AI助手如何响应关于这个问题的两个不同提示。

❌ 糟糕的提示: “为什么我的mapUsersById函数不工作?”

AI对糟糕提示的响应(模拟):

很难说没有更多的信息。你确定函数是用一个适当的用户数组调用的吗?也许问题是用户为空或不是数组,导致循环无法正确执行。还可能是设置userMap上的属性时出错。确保每个用户都有id属性。如果用户是undefined或null,那也会导致它不工作。

分析: 这个答案相当模糊。提示除了函数名之外没有提供任何细节,因此AI在猜测通用原因(“也许数组为空或不是数组”,“确保每个用户都有id”)。它没有识别出实际的错误。这是当问题不具体时你得到的回答类型——模型只能提供最笼统的调试提示。作为开发人员,这不是很有帮助,因为它没有解决真正的错误。缺乏上下文和具体性在提示中产生了笼统的答案(本质上是AI版的“您尝试过关闭再打开吗?”)。

✅ 改进的提示:

I have a JavaScript function mapUsersById that should convert an array of user objects into a map (object) keyed by user ID. However, it throws an error when I run it. For example, when I pass [ {id: 1, name: "Alice"} ], I get TypeError: Cannot read property 'id' of undefined. Here is the function code:

function mapUsersById(users) {
  const userMap = {};
  for (let i = 0; i <= users.length; i++) {  
    const user = users[i];
    userMap[user.id] = user;
  }
  return userMap;
}

我有一个JavaScript函数mapUsersById,应该将用户对象数组转换为按用户ID键入的映射(对象)。然而,当我运行它时,它抛出了一个错误。例如,当我传递[ {id: 1, name: "Alice"} ]时,我得到TypeError: Cannot read property 'id' of undefined。这是函数代码:

function mapUsersById(users) {
  const userMap = {};
  for (let i = 0; i <= users.length; i++) {  
    const user = users[i];
    userMap[user.id] = user;
  }
  return userMap;
}

AI对改进提示的响应(模拟):

*错误Cannot read property 'id' of undefined表明在某一点上,user是undefined,然后你尝试访问user.id。查看你的循环,问题是循环边界。你使用i <= users.length。如果users.length是1(数组中有一个用户),循环会以i = 0和i = 1运行。当i为1时,users[1]是undefined(因为数组中唯一的索引是0)。因此,在那次迭代中user变为undefined,user.id抛出TypeError。修复方法是将循环条件改为i < users.length,以便i从0到users.length-1。例如: `` for (let i = 0; i < users.length; i++) { const user = users[i]; userMap[user.id] = user; } ``

这样,i将为0(第一个也是唯一的元素),然后循环停止。有了这个改变,你的函数将不再命中undefined的用户,并应按预期工作,对于给定的输入返回{"1": {id: 1, name: "Alice"}}。*

分析: 这个响应直接指出了错误(在循环中使用<=)并解释了为什么它导致user为undefined。然后提供了正确的代码解决方案(i < users.length)并确认了预期的结果。改进的提示成功了,因为它遵循了我们的原则:它指定了语言,描述了函数的目的,包括确切的错误消息和示例输入,甚至提供了所讨论的代码片段。注意AI如何使用错误消息作为线索来关注循环边界——一个有针对性的提示使AI能够从事真正的解决问题,有效地模拟了人类调试器会思考的方式:“undefined可能来自哪里?可能来自循环索引。”这是详细提示益处的具体演示。

其他调试策略: 除了识别明显的错误外,你可以使用提示工程进行更深入的调试帮助:

  • 请求潜在原因。 如果你真的束手无策,你可以稍微拓宽问题:“在这个代码中,TypeError: cannot read property 'foo' of undefined的可能原因是什么?”以及代码。模型可能会列出一些场景(例如,对象未初始化,竞争条件,错误的变量作用域等)。这可以给你一些你没有考虑过的调查角度。这就像与同事头脑风暴。
  • “问橡皮鸭”——即,向AI解释你的代码。这可能听起来违反直觉(为什么要向助手解释?),但写作解释的行为可以澄清你自己的理解,然后你可以让AI验证或批评它。例如:“我将解释这个函数在做什么:[你的解释]。鉴于此,我的推理是否正确,它是否揭示了错误在哪里?”AI可能会捕捉到你解释中的缺陷,指向实际的错误。这种技术利用AI作为一个积极的橡皮鸭,不仅倾听而且回应。
  • 让AI创建测试用例。 你可以问:“你能提供几个可能破坏这个函数的测试用例(输入)吗?”助手可能会想出你没有想到的边缘情况(空数组,极大的数字,null值等)。这对于调试和为未来的稳健性生成测试都很有用。
  • 扮演代码审查员。 作为直接“调试这个”的提示的替代,你可以说:“扮演代码审查员。这是一个没有按预期工作的片段。审查它并指出可能导致问题的任何错误或不良做法:[代码]”。这将AI设置为批判模式。许多开发人员发现,将请求表述为代码审查会产生非常彻底的分析,因为模型会对代码的每一部分进行评论(通常,在这样做时,它会发现错误)。

总之,在使用AI助手进行调试时,细节和方向是你的朋友。提供场景、症状,然后提出尖锐的问题。我们在上面看到,一个无力的“它不工作,救命!”提示与一个精确的调试提示之间的差异是天壤之别。接下来,我们将转向另一个主要用例:重构和改进现有代码。

重构和优化代码的提示模式

重构代码——使其更干净、更快或更符合惯例而不改变其功能——是AI助手可以发挥作用的领域。它们在大量代码上进行了训练,其中包括许多结构良好、优化的解决方案的示例。然而,要有效地利用这些知识,你的提示必须澄清“更好”对你的情况意味着什么。以下是如何为重构任务提示:

1. 明确陈述你的重构目标。 “重构这段代码”本身太开放。你想提高可读性吗?降低复杂性?优化性能?使用不同的范式或库?AI需要一个目标。一个好的提示会框定任务,例如:“重构以下函数以提高其可读性和可维护性(减少重复,使用更清晰的变量名)。”或“优化这个算法以提高速度——它在大型输入上太慢。”通过陈述具体目标,你帮助模型决定应用哪些转换。例如,告诉它你关心性能可能会导致它使用更高效的排序算法或缓存,而专注于可读性可能会导致它将函数分解成更小的函数或添加注释。如果你有多个目标,列出它们。提示模板甚至建议列举问题:“我想解决的问题:1)[性能问题],2)[代码重复],3)[过时的API使用]。”这样,AI确切地知道要修复什么。记住,它不会天生知道你认为代码中的问题是什么——你必须告诉它。

2. 提供必要的代码上下文。 在重构时,你通常会将需要改进的代码片段包含在提示中。重要的是要包括你想要重构的完整函数或部分,有时如果相关的话,还包括一些周围的上下文(例如函数的使用或相关代码,这可能会影响你的重构方式)。还要提及语言和框架,因为“惯例”代码在例如Node.js与Deno之间或React类组件与函数组件之间有所不同。例如:“我有一个React组件,写为一个类。请将其重构为使用Hooks的函数组件。”AI将应用典型步骤(使用useState、useEffect等)。如果你只是说“重构这个React组件”而不澄清风格,AI可能不知道你特别想要Hooks。

  • 如果相关,包含版本或环境细节。 例如,“这是一个Node.js v14代码库”或“我们使用ES6模块”。这可以影响AI是否使用某些语法(例如import/export vs. require),这是正确重构的一部分。如果你想确保它不引入不兼容的东西,提及你的约束。

3. 鼓励代码随附的解释。 从AI主导的重构中学习(并验证其正确性)的一个好方法是要求对更改进行解释。例如:“请建议一个重构版本的代码,并解释你所做的改进。”这甚至内置在提示模板中:“…建议重构代码并解释你的更改。”当AI提供解释时,你可以评估它是否理解代码并满足你的目标。解释可能会说:“我将两个相似的循环合并为一个以减少重复,我使用字典进行更快的查找”等。如果解释中听起来有问题,那就是一个红旗,需要仔细检查代码。简而言之,使用AI解释的能力作为保障——这就像让AI对自己的重构进行代码审查。

4. 使用角色扮演来设定高标准。 如前所述,要求AI扮演代码审查员或高级工程师的角色可以非常有效。对于重构,你可以说:“扮演一个经验丰富的TypeScript专家,将这段代码重构为符合最佳实践和现代标准。”这通常不仅会产生表面上的变化,而且会产生更深刻的改进,因为AI试图达到“专家”的角色。提示指南中的一个流行示例是让AI扮演导师:“扮演一个经验丰富的Python开发人员,指导初级开发人员。提供解释并编写文档字符串。重写代码以优化它。”在这种情况下,结果是AI使用更高效的数据结构(集合以删除重复项)并为一个原始使用循环的函数提供了一行解决方案。角色扮演帮助它不仅重构而且解释为什么新方法更好(在这种情况下,使用集合是众所周知的唯一性优化)。

现在,让我们通过一个重构的例子来说明提示如何影响结果。我们将使用JavaScript(Node.js)中的一个场景,其中有一些不太理想的代码,我们希望改进它。

重构示例:糟糕的提示与改进的提示

假设我们有一个函数,它执行两次数据库调用并进行一些处理。它有效,但不漂亮——有重复的代码,难以阅读。我们希望重构它以提高清晰度和效率。这是原始代码:

// 原始函数:获取两个列表并处理它们(需要重构)
async function getCombinedData(apiClient) {
  // 获取用户列表
  const usersResponse = await apiClient.fetch("/users");
  if (!usersResponse.ok) {
    throw new Error("Failed to fetch users");
  }
  const users = await usersResponse.json();
  // 获取订单列表
  const ordersResponse = await apiClient.fetch("/orders");
  if (!ordersResponse.ok) {
    throw new Error("Failed to fetch orders");
  }
  const orders = await ordersResponse.json();
  // 组合数据(匹配用户与订单)
  const result = [];
  for (let user of users) {
    const userOrders = orders.filter((o) => o.userId === user.id);
    result.push({ user, orders: userOrders });
  }
  return result;
}

这段代码中的问题:我们看到用户和订单的fetch逻辑重复(可以重构为助手或循环),缺乏错误细节(只是一个通用的错误消息),并且可能并行运行fetch以提高速度。让我们看两种提示方法:

❌ 提示1(不具体): “重构上面的getCombinedData函数。”

AI对提示1的响应:

*当然。以下是getCombinedData的重构版本:

async function getCombinedData(apiClient) {
  // 并行获取用户和订单
  const [usersResponse, ordersResponse] = await Promise.all([
    apiClient.fetch("/users"),
    apiClient.fetch("/orders"),
  ]);
  if (!usersResponse.ok || !ordersResponse.ok) {
    throw new Error("Failed to fetch data");
  }
  const [users, orders] = await Promise.all([
    usersResponse.json(),
    ordersResponse.json(),
  ]);
  // 按userId映射订单以进行高效查找
  const ordersByUser = {};
  for (const order of orders) {
    if (!ordersByUser[order.userId]) {
      ordersByUser[order.userId] = [];
    }
    ordersByUser[order.userId].push(order);
  }
  // 组合数据
  return users.map((user) => ({
    user,
    orders: ordersByUser[user.id] || [],
  }));
}

我并行获取用户和订单以提高性能。我还整合了错误处理,并使用映射(ordersByUser)按用户分组订单,这使得组合步骤更干净且可能更快。*

分析: 这实际上是一个相当好的重构,实现了几个改进:并行获取、按用户分组订单以提高效率、使用.map和默认为空数组进行更干净的组合。AI还解释了它做了什么。那么为什么我们将这个提示标记为不具体或“糟糕”?在这种情况下,AI恰好做出了好的选择,但它假设了很多,因为提示是开放式的。它假设应该提高性能(并行请求,这是一个明智的改变,但如果并行不安全呢?),并且它将错误处理改为通用的“Failed to fetch data”消息,这在合并错误时失去了具体性。

✅ 提示2(面向目标): “重构上面的getCombinedData函数以消除重复代码并提高性能。具体来说:(1)避免为用户和订单重复fetch逻辑——也许使用助手或一起获取它们。(2)如果可能,并行获取两个列表。(3)为每个fetch保留错误处理(我们想知道哪个调用失败了)。(4)通过使用更高效的结构进行查找而不是嵌套循环来改进数据的组合。提供重构的代码并附有解释更改的注释。”

AI对提示2的响应:

*当然!以下是getCombinedData的改进版本,包含所要求的重点:

async function getCombinedData(apiClient) {
  // 1. 并行获取用户和订单以提高性能
  const [usersResponse, ordersResponse] = await Promise.all([
    apiClient.fetch("/users"),
    apiClient.fetch("/orders"),
  ]);

  // 2. 分别处理错误以了解哪个调用失败
  if (!usersResponse.ok) {
    throw new Error("Failed to fetch users");
  }
  if (!ordersResponse.ok) {
    throw new Error("Failed to fetch orders");
  }

  // 并行解析JSON响应
  const [users, orders] = await Promise.all([
    usersResponse.json(),
    ordersResponse.json(),
  ]);

  // 3. 使用按userId的查找映射进行高效匹配
  const ordersByUser = orders.reduce((map, order) => {
    if (!map[order.userId]) {
      map[order.userId] = [];
    }
    map[order.userId].push(order);
    return map;
  }, {});

  // 4. 使用查找映射组合数据
  return users.map((user) => ({
    user,
    orders: ordersByUser[user.id] || [], // 如果用户没有订单则为空数组
  }));
}

更改的解释:

  • 并行获取: 我使用Promise.all同时获取用户和订单(点1)。
  • 保留个别错误处理: 我为usersResponse.ok和ordersResponse.ok保留了单独的检查,以便任何错误清楚地表明哪个fetch失败了(点2)。
  • 并行JSON解析: 还为response.json()调用使用了Promise.all,这是一个小的改进。
  • 高效组合: 代替嵌套循环,我创建了一个ordersByUser映射(点3)。这在一次遍历中按userId分组订单。然后我映射用户以构建结果,从映射中以O(1)时间拉取每个用户的订单(点4)。这更高效且简洁。*

分析: 这个响应直接解决了所有指定的目标。代码被重构为更干净、更快,并且根据要求保留了单独的错误消息。AI的解释确认了我们列出的每一点,这表明它仔细遵循了提示的指示。这是一个伟大的结果,因为我们作为提示者定义了在这个上下文中“重构”意味着什么。通过这样做,我们引导AI产生了一个符合我们需求的解决方案,几乎不需要来回交流。

这个示例演示了一个关键教训:当你知道你想要改进什么时,明确说明。 AI擅长遵循指示,但它不会读心术。一个宽泛的“使这个更好”可能对简单的事情起作用,但对于非平凡的代码,你将通过列举对你来说“更好”是什么来获得最佳结果。

其他重构提示:

  • 逐步重构: 如果代码非常大或者你有很长的更改列表,你可以一次一个地处理它们。例如,首先要求AI“重构以提高可读性”(专注于重命名、拆分函数),然后“优化这个函数中的算法”。这防止了模型一次处理太多指令,并让你逐步验证每个更改。
  • 要求替代方法: 也许AI的第一次重构起作用了,但你对不同的角度感到好奇。你可以问,“你能用另一种方式重构它吗?也许使用函数式编程风格(例如,数组方法而不是循环)?”或者“这里使用递归而不是迭代方法会如何?只是为了比较。”这样,你可以评估不同的解决方案。这就像与同事头脑风暴多个重构选项。
  • 将重构与解释相结合以学习模式: 我们已经提到了这一点,但值得强调——将AI用作学习工具。如果它以巧妙的方式重构代码,研究输出和解释。你可能会发现一个你以前没有使用过的新的API或技术(例如,使用reduce构建映射)。这是要求解释的原因之一:它将答案变成一个迷你教程,强化你对最佳实践的理解。
  • 验证和测试: 在任何AI生成的重构之后,始终运行你的测试或用样本输入尝试代码。AI可能会无意中引入微妙的错误,特别是在提示没有指定重要约束的情况下。例如,在我们的重构中,如果原始代码有意为日志记录分离fetch错误,但我们没有提及日志记录,合并的错误可能不那么有用。审查时抓住这一点是我们的工作。AI也可以通过编写测试来帮助——你可以要求“为重构的函数生成一些单元测试”以确保它在预期输入上的行为与以前相同。

到目前为止,我们已经涵盖了调试和重构——改进现有代码。下一个逻辑步骤是使用AI辅助来实现新功能或生成新代码。我们将探讨如何有效地为该场景进行提示。

现代调试场景

React Hook依赖问题

❌ 糟糕的提示: "我的useEffect工作不正常"

✅ 增强的提示:

我有一个React组件,它获取用户数据,但它导致无限重新渲染。这是我的代码:

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(setUser).finally(() => setLoading(false));
  }, [userId, setUser, setLoading]); // 问题在这里

  return loading ? <div>Loading...</div> : <div>{user?.name}</div>;
};

预期行为: 当userId更改时应该获取用户数据一次 实际行为: 组件无限重新渲染 控制台中的错误: "Warning: Maximum update depth exceeded"

是什么导致了这个无限循环,我如何修复依赖数组?

为什么这有效: 提供了确切的代码、错误消息、预期与实际行为,并专注于一个通常被误解的React模式。

现代状态架构

❌ 糟糕的提示: "为我的Next.js电子商务应用构建状态管理”

✅ 增强的提示:

我正在构建一个Next.js 14电子商务应用,需要设计状态管理架构。以下是我的要求:

组件:

  • 产品列表页面(需要:products[]、filters、pagination)
  • 购物车(需要:cart items、totals、shipping info)
  • 用户认证(需要:user profile、auth status、preferences)
  • 实时通知(需要:toast messages、error states)

技术约束:

  • Next.js 14 with App Router and Server Components
  • TypeScript strict mode
  • 服务器端数据获取以支持SEO
  • 客户端交互以支持cart/user actions
  • 状态应在导航中持久

我应该使用:

  1. 每个域的Zustand stores(cart、auth、notifications)
  2. React Query/TanStack Query for server state + Zustand for client state
  3. 具有切片的单个Zustand store

请提供一个推荐的架构,并附有代码示例,展示如何结构化stores并与Next.js App Router模式集成。

为什么这有效: 具有具体技术栈、明确要求的真实场景,并要求架构指导和实现细节。

实现新功能的提示模式

AI代码助手最令人兴奋的用途之一是帮助你从头开始编写新代码或将新功能集成到现有代码库中。这可能范围从为React组件生成样板代码到在Express应用中编写新的API端点。这里的挑战通常是这些任务是开放式的——实现一个功能有多种方法。为代码生成进行提示工程是关于引导AI产生适合你的需求和风格的代码。以下是实现这一目标的策略:

1. 从高层指令开始,然后深入。 首先用平实的语言概述你想要构建的内容,可能将其分解为更小的任务(类似于我们之前关于分解复杂任务的建议)。例如,假设你想向现有Web应用添加一个搜索栏功能。你可能首先提示:“概述一个计划,在我的React应用中添加一个搜索功能,该功能按名称过滤产品列表。产品是从API获取的。”

AI可能会给你一个分步计划:“1. 为搜索查询添加一个输入字段。2. 添加状态以保存查询。3. 基于查询过滤产品列表。4. 确保不区分大小写等。”一旦你有了这个计划(你可以用AI的帮助进行完善),你可以用集中的提示处理每个项目。

例如:“好吧,实现步骤1:创建一个SearchBar组件,带有更新searchQuery状态的输入。”之后,“实现步骤3:给定searchQuery和产品数组,按名称(不区分大小写)过滤产品。”通过分解功能,你确保每个提示是具体的,响应是可管理的。这也反映了迭代开发——你可以测试每个构建的部分。

2. 提供相关的上下文或参考代码。 如果你正在向现有项目添加功能,展示AI在该项目中如何完成类似事情会非常有帮助。例如,如果你已经有一个与你想要的相似的组件,你可以说:“这是现有的UserList组件(代码…)。现在创建一个类似的ProductList组件,但包括一个搜索栏。”

AI将看到模式(也许你使用某些库或风格惯例)并应用它们。打开相关文件或在提示中引用它们可以提供上下文,从而导致更符合项目且一致的代码建议。另一个技巧:如果你的项目使用特定的编码风格或架构(例如Redux用于状态或某个CSS框架),提及它。“我们使用Redux进行状态管理——将搜索状态集成到Redux store中。”

一个训练有素的模型将生成与Redux模式等一致的代码。基本上,你在教AI关于你项目的环境,以便它可以定制输出。一些助手甚至可以使用你的整个存储库作为上下文;如果使用这些,确保你指向存储库中的类似模块或文档。

  • 如果开始新事物但你有首选方法,你也可以提及:“我希望使用函数式编程风格(没有外部状态,使用数组方法)来实现这个。”或者,“确保遵循MVC模式并将逻辑放在控制器中,而不是视图中。”这些是高级工程师可能会提醒初级工程师的细节,在这里你是高级工程师,告诉AI。

3. 使用注释和TODO作为内联提示。 当直接在IDE中使用Copilot时,一个有效的工作流程是编写描述你需要的下一段代码的注释,然后让AI自动完成它。例如,在Node.js后端中,你可以写:// TODO: 验证请求负载(确保提供了名称和电子邮件),然后开始下一行。Copilot经常会理解意图并生成执行该验证的代码块。这是因为你的注释实际上是一个自然语言提示。然而,准备好编辑生成的代码,如果AI误解——一如既往,验证其正确性。

4. 提供预期输入/输出或用法的示例。 类似于我们之前讨论的,如果你要求AI实现一个新函数,包括一个关于它将如何使用或一个简单的测试用例的快速示例。例如:“在JavaScript中实现一个函数formatPrice(amount),它接受一个数字(例如2.5)并返回以USD格式化的字符串(例如$2.50)。例如,formatPrice(2.5)应返回'$2.50'。”

通过给出那个示例,你限制AI产生与之一致的函数。如果没有示例,AI可能会假设其他格式或货币。差异可能是微妙的但重要的。Web上下文中的另一个示例:“实现一个Express中间件,记录请求。例如,对/users的GET请求应在控制台记录‘GET /users’。”这明确了输出应该是什么样子。在提示中包括预期行为作为AI将尝试满足的测试。

5. 当结果不是你想要的时,用更多细节或约束重写提示。 第一次尝试生成新功能并不完美是很常见的。也许代码运行但不符合惯例,或者它错过了一个要求。不要沮丧,将AI视为初级开发人员,他们给出了初稿——现在你需要提供反馈。例如,“解决方案有效,但我希望你使用内置的数组filter方法而不是for循环。”或者,“你能将生成的组件重构为使用React Hooks进行状态管理而不是类组件吗?我们的代码库都是函数组件。”你还可以添加新的约束:“还要确保函数在O(n)时间或更好地运行,因为n可能很大。”这种迭代提示是强大的。一个现实世界的场景:一位开发人员要求LLM生成使用JS画布库绘制冰淇淋蛋筒的代码,但它不断给出不相关的输出,直到他们用更具体的细节和上下文完善提示。教训是,不要在一次尝试后放弃。找出提示中缺少或误解的内容并澄清它。这是提示工程的精髓——每次调整都可以引导模型更接近你设想的。

让我们用一个示例场景来说明功能实现提示:

功能实现示例:使用AI助手构建React组件

假设我们有一个React应用程序,我们想添加一个新组件:一个带有搜索过滤器和排序下拉列表的产品列表组件。这是一个相当典型的Web应用功能。为了简洁起见,我们将专注于为这个组件中的搜索功能进行提示。

初始提示(功能请求): “创建一个名为ProductList的React函数组件,它显示产品列表并包括一个文本输入以按名称过滤产品。组件应:从/api/products获取产品数组(假设它返回{id, name, ...}的JSON数组),将它们存储在状态中,并允许用户在搜索框中键入以按名称(不区分大小写)过滤显示的产品。在一个简单的

  • 中显示过滤后的列表,其中包含产品名称。包括基本的错误处理(例如,如果API调用失败,显示错误消息)和加载状态。”

这是一个很长的提示,但它是具体的。假设我们在聊天中将此提供给LLM。

AI的响应(它可能产生的摘要): AI可能会输出一个ProductList组件,类似于:

import React, { useState, useEffect } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);
  const [search, setSearch] = useState('');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchProducts() {
      try {
        const res = await fetch('/api/products');
        if (!res.ok) throw new Error('Network response was not ok');
        const data = await res.json();
        setProducts(data);
      } catch (err) {
        console.error('Failed to fetch products:', err);
        setError('Failed to load products.');
      } finally {
        setLoading(false);
      }
    }
    fetchProducts();
  }, []);

  const handleSearchChange = (e) => {
    setSearch(e.target.value);
  };

  // 按搜索查询过滤产品(不区分大小写)
  const filteredProducts = products.filter(product =>
    product.name.toLowerCase().includes(search.toLowerCase())
  );

  if (loading) return <div>加载产品...</div>;
  if (error) return <div>{error}</div>;

  return (
    <div className="product-list">
      <input
        type="text"
        placeholder="搜索产品"
        value={search}
        onChange={handleSearchChange}
      />
      <ul>
        {filteredProducts.map(prod => (
          <li key={prod.id}>{prod.name}</li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;

(如果我们要求,还可能在代码中添加一些解释的注释。)

现在,让我们分析这个结果并考虑提示的改进:

  • 假设上述代码回来了,但我们的项目使用上下文或全局存储进行数据获取,而不是在组件内部获取。AI选择在useEffect中获取。如果我们更喜欢使用自定义钩子或上下文,我们应该提示这一点。因此,我们可以完善我们的提示:“实际上,在我们的应用中,我们使用一个自定义钩子useProducts(),它已经处理了获取。请重构组件以使用useProducts钩子而不是直接调用fetch。”AI会调整,假设useProducts返回{ products, loading, error }并相应地简化组件。
  • 另一个改进:也许我们意识到我们还想要一个排序下拉菜单(我们最初没有提及)。我们现在可以扩展对话:“很好,现在添加一个下拉菜单以按名称(A-Z或Z-A)对产品进行排序。下拉菜单应让用户选择升序或降序,列表应相应地排序,除了过滤。”因为AI有现有代码的上下文,它可以插入一个排序状态并调整渲染。我们提供了一个明确的新要求,它将尝试满足它,可能是通过添加类似于:
const [sortOrder, setSortOrder] = useState("asc");
// ... 一个用于sortOrder的选择输入 ...
// 并在渲染前对filteredProducts进行排序:
const sortedProducts = [...filteredProducts].sort((a, b) => {
  if (sortOrder === "asc") return a.name.localeCompare(b.name);
  else return b.name.localeCompare(a.name);
});
  • (加上下拉UI)。

通过这样迭代,功能一个接一个,我们模拟了与AI的开发周期。这比试图在初始提示中一次性提示整个复杂组件的所有功能要有效得多。它减少了错误,并允许在需求变得更清晰时进行中途更正。

  • 如果AI犯了一个微妙的错误(例如,忘记使搜索过滤器不区分大小写),我们只需指出这一点:“使搜索不区分大小写。”它将调整过滤器以使用小写比较(在我们的伪输出中它已经这样做了,但如果没有,它会修复它)。

这个示例表明,使用AI实现功能完全是关于增量开发和提示改进。Twitter线程可能会惊叹于某人通过持续提示LLM来构建一个小程序——这基本上就是这种方法:构建、审查、完善、扩展。每个提示都像开发过程中的一次提交。

功能实现的其他提示:

  • 让AI搭建脚手架,然后你填写细节: 有时让AI生成粗略的结构很有用,然后你调整它。例如,“为用户注册生成带有验证和错误处理的Node.js Express路由的骨架。”它可能会产生一个带有占位符的通用路由。然后你可以填写实际的验证规则或数据库调用,这些是你的应用所特有的。AI为你节省了编写样板代码的时间,你处理自定义逻辑,如果它是敏感的。
  • 要求处理边缘情况: 在生成功能时,你可以提示AI思考边缘情况:“对于这个功能,我们应该考虑哪些边缘情况(你能在代码中处理它们吗)?”例如,在搜索示例中,边缘情况可能是“如果用户键入时产品尚未加载会怎样?”(尽管我们的代码通过加载状态处理了这一点)或“如果两个产品有相同的名称”(不是大问题,但也许提及它)。AI可能会提到空结果处理、非常大的列表(可能需要为搜索输入去抖动)等。这是利用AI在常见陷阱上的训练的一种方式。
  • 文档驱动开发: 一些人采取的一个巧妙方法是首先编写文档字符串或使用示例,然后让AI实现与匹配的函数。例如:
/**
 * 返回第n个斐波那契数。
 * @param {number} n - 斐波那契序列中的位置(0索引)。
 * @returns {number} 第n个斐波那契数。
 *
 * 示例:fibonacci(5) -> 5  (sequence: 0,1,1,2,3,5,…)
 */
function fibonacci(n) {
  // ... 实现
}
  • 如果你编写了上述注释和函数签名,LLM可能会正确填写实现,因为注释确切地描述了要做什么,甚至给出了一个示例。这种技术确保你首先用文字澄清功能(这通常是一个好习惯),然后AI使用它作为规范来编写代码。

在涵盖了调试、重构和新代码生成的提示策略后,让我们将注意力转向提示工程中的一些常见陷阱和反模式。理解这些将帮助你避免在无效的交互上浪费时间,并在AI没有给你需要的东西时快速调整。

常见提示反模式及如何避免

并非所有提示都生而平等。到目前为止,我们已经看到了许多有效提示的示例,但同样重要的是要认识到反模式——导致AI响应不佳的常见错误。

以下是一些频繁的提示失败以及如何修复它们:

  • 反模式:模糊提示。 这是经典的“它不工作,请修复它”或“编写做X的事情”没有足够的细节。我们在问题“为什么我的函数不工作?”得到无用答案时看到了这个例子。模糊的提示迫使AI猜测上下文,并且通常导致笼统的建议或不相关的代码。修复很简单:添加上下文和细节。如果你发现自己在问一个问题,答案感觉像魔法8球的响应(“你试过检查X吗?”),停下来,用更多细节(错误消息、代码摘录、预期与实际结果等)重新构建你的查询。一个好的做法是阅读你的提示并问,“这个问题可以适用于数十种不同的场景吗?”如果是,它太模糊了。使它如此具体,以至于它只能适用于你的场景。
  • 反模式:过载提示。 这是相反的问题:要求AI一次做太多事情。例如,“生成一个带有认证、React前端和部署脚本的完整Node.js应用。”或者在较小的规模上,“一次性修复这5个错误并添加这3个功能。”AI可能会尝试,但你可能会得到一个混乱或不完整的结果,或者它可能会忽略请求的某些部分。即使它解决了所有问题,响应也会很长,难以验证。补救措施是拆分任务。优先考虑:一次做一件事,正如我们之前强调的。这使得更容易捕捉错误并确保模型保持集中。如果你发现自己在写一个带有多个“和”的段落,考虑将其分解成单独的提示或连续的步骤。
  • 反模式:缺少问题。 有时用户会提供大量信息但从未明确提出问题或指定他们需要什么。例如,倾倒一个大代码片段并只是说“这是我的代码。”这会让AI感到困惑——它不知道你想要什么。始终包括一个明确的请求,例如“识别上述代码中的任何错误”、“解释这段代码做什么”或“完成代码中的TODO”。提示应该有一个目的。如果你只是提供文本而没有问题或指令,AI可能会做出错误的假设(例如,总结代码而不是修复它等)。确保AI知道为什么你向它展示了一些代码。即使是一个简单的补充,如“这段代码有什么问题?”或“请继续实现这个函数。”也会给它方向。
  • 反模式:模糊的成功标准。 这是微妙的——有时你可能会要求优化或改进,但你没有定义成功是什么样子。例如,“使这个函数更快。”更快是按什么指标?如果AI不知道你的性能约束,它可能会微优化一些不重要的事情或使用一种理论上更快但实际上微不足道的方法。或者“使这段代码更干净”——“更干净”是主观的。我们通过明确陈述目标来处理这个问题,如“减少重复”或“改善变量名”等。修复:量化和限定改进。例如,“将这个函数优化为在线性时间内运行(当前版本是二次的)”或“重构这个以移除全局变量并使用类。”基本上,明确你通过重构或功能解决的问题。如果你让它太开放,AI可能会解决一个与你关心的不同的问题。
  • 反模式:忽略AI的澄清或输出。 有时AI可能会回应一个澄清问题或假设。例如:“你是在使用React类组件还是函数组件?”或“我假设输入是一个字符串——请确认。”如果你忽略这些,只是重申你的请求,你就错过了改善提示的机会。AI在发出信号,它需要更多信息。始终回答它的问题或完善你的提示以包括那些细节。此外,如果AI的输出明显偏离(例如,它误解了问题),不要只是重试相同的提示。花点时间调整你的措辞。也许你的提示有歧义的短语或遗漏了重要的东西。像对话一样对待它——如果人类误解了,你会用不同的方式解释;对AI也一样。
  • 反模式:变化的风格或不一致。 如果你不断改变你如何询问或在一次中混合不同的格式,模型会感到困惑。例如,在指令中在第一人称和第三人称之间切换,或以令人困惑的方式将伪代码与实际代码混合。尝试在单个提示中保持一致的风格。如果你提供示例,确保它们被清楚地描绘(使用Markdown三重反引号用于代码,引号用于输入/输出示例等)。一致性帮助模型正确解析你的意图。此外,如果你有首选的风格(例如,ES6 vs ES5语法),始终提及它,否则模型可能在一次提示中建议一种方式,在后来建议另一种。
  • 反模式:模糊的引用,如“上面的代码”。 在使用聊天时,如果你说“上面的函数”或“先前的输出”,确保引用是明确的。如果对话很长,你说“重构上面的代码”,AI可能会丢失线索或选择错误的代码片段进行重构。更安全的是要么再次引用代码,要么具体命名你想要重构的函数。模型有有限的注意力窗口,尽管许多LLM可以参考对话的先前部分,但再次提供明确的上下文可以帮助避免混淆。如果从代码展示到现在已经过去了一段时间(或几条消息),这一点尤其重要。

最后,这是一个重写提示的战术方法,当事情出错时:

  • 识别AI响应中缺少或不正确的内容。 它是否解决了不同的问题?它是否产生了错误或不适合的解决方案?例如,也许你要求TypeScript解决方案,但它给出了纯JavaScript。或者它写了一个递归解决方案,而你明确想要迭代的。准确定位差异。
  • 在新的提示中添加或强调那个要求。 你可以说,“解决方案应该是TypeScript,而不是JavaScript。请包括类型注解。”或者,“我提到我想要一个迭代解决方案——请避免递归并使用循环。”有时在提示中使用“注意:”或“重要:”这样的短语会有所帮助,以突出关键约束(模型没有情感,但它确实将某些措辞视为重要性的指标)。例如:“重要:不要为此使用任何外部库。”或“注意:代码必须在浏览器中运行,所以没有Node特定的API。”。
  • 如果需要,进一步分解请求。 如果AI在复杂的请求上反复失败,尝试先请求一个更小的部分。或者问一个可能启发情况的问题:“你理解我所说的X是什么意思吗?”模型可能会解释它认为你是什么意思,如果你错���,你可以纠正它。这是元提示——讨论提示本身——有时可以解决误解。
  • 如果线程卡住了,考虑重新开始。 有时在多次尝试后,对话可能会达到混乱的状态。开始一个新的会话(或暂时清除聊天历史记录)并用你基于先前失败制定的更精细的请求从头开始提示会有所帮助。模型不介意重复,新的上下文可以消除先前消息中积累的混淆。

通过意识到这些反模式及其解决方案,你将能够在运行中更快地调整你的提示。开发人员的提示工程非常是一个迭代的、反馈驱动的过程(就像任何编程任务一样!)。好消息是,你现在在你的工具箱中有很多模式和示例可以借鉴。

结论

提示工程既是一门艺术也是一门科学——正如我们所见,它正迅速成为使用AI代码助手的开发人员的必备技能。通过制作清晰、上下文丰富的提示,你本质上教AI你需要什么,就像你会引导一个人类团队成员或向同行解释问题一样。在整篇文章中,我们探讨了如何系统地处理调试、重构和功能实现的提示:

  • 我们学会了向AI提供你会给同事的相同信息:代码应该做什么,它的行为不正确,相关的代码片段等——从而获得更有针对性的帮助。
  • 我们看到了与AI迭代的威力,无论是逐行逐步执行函数的逻辑,还是通过多个提示完善解决方案(例如,将递归解决方案转换为迭代解决方案,然后改善变量名)。耐心和迭代将AI变成真正的配对程序员,而不是一次性代码生成器。
  • 我们利用角色扮演和人物来提升响应的水平——将AI视为代码审查员、导师或某个堆栈的专家。这通常会产生更严格和解释丰富的输出,不仅解决问题,而且在过程中教育我们。
  • 对于重构和优化,我们强调定义“良好”是什么样子(更快、更干净、更符合惯例等),AI展示了当受到引导时,它可以应用已知的最佳实践(例如并行化调用、消除重复、正确处理错误)。这就像可以访问无数代码审查员的集体智慧——但你必须提出正确的问题才能利用它。
  • 我们还演示了逐步使用AI助手构建新功能,表明即使是复杂的任务也可以分解并一次一个提示地处理。AI可以搭建样板代码、建议实现,甚至在提示时突出边缘情况——充当一个知识渊博的共同开发人员,随时可用。
  • 在此过程中,我们确定了要避免的陷阱:保持提示既不模糊也不过载,始终指定我们的意图和约束,并在AI的输出不准确时准备好调整。我们引用了糟糕提示的具体示例,并看到了微小的变化(例如包括错误消息或预期输出)如何显著改善结果。

当你将这些技术融入你的工作流程时,你可能会发现与AI合作变得更加直观。你会发展出一种感觉,什么样的措辞能获得最佳结果,以及当模型偏离时如何引导它。记住,AI是其训练数据的产物——它见过许多代码和解决问题的示例,但是你提供关于哪些示例现在相关的方向。实质上,你设置上下文,AI执行。

值得注意的是,提示工程是一种不断发展的实践。 开发人员社区不断发现新的技巧——一个巧妙的一行提示或结构化模板可能突然在社交媒体上病毒式传播,因为它解锁了人们没有意识到的能力。关注这些讨论(在Hacker News、Twitter等上),因为它们可以启发你自己的技术。但也不要害怕自己实验。将AI视为一个灵活的工具——如果你有一个想法(“如果我让它画一个我的架构的ASCII图表会怎样?”),就试试。你可能会对结果感到惊讶,如果失败了,也没有伤害——你学到了关于模型的限制或需求的一些东西。

总而言之,提示工程使开发人员能够从AI助手中获得更多。 这是令人沮丧的体验(“这个工具没用,它给了我废话”)和富有成效的体验(“这感觉像与一个为我编写样板代码的专家配对编程”)之间的区别。通过应用我们涵盖的策略手册——从提供详尽的上下文到推动AI的风格和思考——你可以将这些以代码为中心的AI工具变成你开发工作流程的真正扩展。最终结果不仅是你的编码速度更快,而且你经常在过程中获得新的见解和模式(当AI解释事情或建议替代方案时),提升你自己的技能。

作为最后的收获,记住提示是一个迭代的对话。 以你与另一位工程师沟通时使用的清晰、耐心和彻底性来对待它。这样做,你会发现AI助手可以显著增强你的能力——帮助你更快地调试、更智能地重构,并更轻松地实现功能。

祝提示愉快,编码愉快!

进一步阅读:

原文:https://addyo.substack.com/p/the-prompt-engineering-playbook-for

来源
小互 AI 小教程小互 AI 小教程
↑ 正文结束
✓ 本篇全文免费开放

会员可复制、下载全文 Markdown,还能让你的 AI 订阅和提问。

📮 新解读第一时间进邮箱

读完觉得有用?留个邮箱,每篇新解读发布就送达,随时退订。