微信扫码
添加专属顾问
我要投稿
掌握提示工程,让AI编程助手成为你的高效搭档!本文揭秘提升代码生成质量的7大实用技巧。 核心内容: 1. AI编程助手的优势与局限性分析 2. 7种高效提示模板的实战应用解析 3. 好提示与坏提示的对比案例详解
开发者们正日益依赖 AI 编程助手来加速日常工作流程。这些工具可以自动补全函数、建议错误修复,甚至能生成整个模块或最小可行产品(MVP)。然而,我们中许多人已经体会到,AI 输出的质量在很大程度上取决于你提供的提示词质量。换言之,提示工程已成为一项必备技能。一句措辞不当的请求可能只会得到无关或泛泛的答案,而一个精心设计的提示则能产出深思熟虑、准确甚至富有创意的代码解决方案。本文将从实践角度出发,探讨如何系统地为常见的开发任务构建有效的提示。
AI 结对程序员虽然强大,但并非无所不能——除了你告知或作为上下文包含的信息外,它们对你的具体项目或意图一无所知。你提供的信息越多,输出的效果就越好。我们将提炼出关键的提示模式、可复用的框架以及那些在开发者中引起共鸣且易于记忆的例子。你将看到好提示与坏提示的并排对比,以及它们得到的真实 AI 回应,并附有评论,以理解为何一个成功而另一个失败。这是一份快速入门的备忘单:
1. 角色提示 (Role Prompting) | ||
2. 明确上下文 (Explicit Context Setup) | ||
3. 输入/输出示例 (Input/Output Examples) | ||
4. 迭代式链条 (Iterative Chaining) | ||
5. 模拟调试 (Debug with Simulation) | ||
6. 功能蓝图 (Feature Blueprinting) | ||
7. 重构指导 (Code Refactor Guidance) | ||
8. 寻求替代方案 (Ask for Alternatives) | ||
9. 小黄鸭调试法 (Rubber Ducking) | ||
10. 约束锚定 (Constraint Anchoring) |
向 AI 编码工具提问,有点像与一个极其刻板、但有时知识渊博的合作者沟通。为了得到有用的结果,你需要清晰地设定场景,并引导 AI 明白你想要什么以及你希望它如何做。
以下是贯穿本手册所有示例的基础原则:
有了这些基础原则,让我们深入到具体场景中。我们将从调试开始,这可能是最直接的用例:你有一段行为异常的代码,你希望 AI 帮助找出原因。
调试是 AI 助手的天然应用场景。这就像拥有一个不仅会倾听,还会用建议回应你的“小黄鸭”。然而,成功与否很大程度上取决于你如何向 AI 呈现问题。以下是如何系统地为寻求和修复错误提供帮助的提示方法:
1. 清晰描述问题和症状。 在提示的开头,描述哪里出了问题以及代码应该做什么。始终包含确切的错误信息或不正确的行为。例如,不要只说“我的代码不工作”,你可以这样提示:“我有一个 JavaScript 函数,它应该计算一个数字数组的总和,但它返回的是 NaN (Not a Number),而不是实际的总和。这是代码:[包含代码]。对于像 [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 的推理是否合理。
现在,让我们用一个具体的例子来说明这些调试提示原则,展示一个糟糕的提示 vs. 改进的提示以及 AI 响应的差异:
假设我们有一个简单的 Node.js 函数,旨在将用户对象列表转换为按用户 ID 查找的映射表。然而,它抛出了一个错误。这是有问题的代码:
// 有问题的函数:将用户数组转换为按 ID 索引的 map
functionmapUsersById(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" }]);
这段代码本应遍历 users
数组并构建一个以用户 ID 为键的对象。然而,存在一个错误:循环使用了 i <= users.length
而不是 i < users.length
。这会导致在最后一次迭代中 users[i]
变为 undefined
,很可能导致运行时错误(例如,尝试访问 undefined
的 id
属性)。让我们看看 AI 助手对关于这个问题的两个不同提示会如何回应。
❌ 糟糕的提示:“为什么我的 mapUsersById 函数不工作?”
AI 对糟糕提示的回应(模拟):
没有更多信息很难说。你确定函数是用一个合法的用户数组调用的吗?也许问题在于
users
是空的或者不是一个数组,导致循环没有正确执行。也可能是设置userMap
属性时出错。请确保每个用户都有一个id
属性。如果users
是undefined
或null
,也会导致它不工作。
分析: 这个回答相当模糊。提示除了函数名外没有提供任何细节,所以 AI 只能猜测一些常见原因(“也许数组是空的或不是数组”,“确保每个用户都有一个 id”)。它没有识别出真正的错误。当你提问不够具体时,你就会得到这种回应——模型只能复述一些通用的调试技巧。作为开发者,这没什么帮助,因为它没有解决真正的错误。提示中缺乏上下文和明确性导致了泛泛的回答(基本上相当于 AI 版的“你试过重启吗?”)。这与社区的建议一致,即模糊的提示会导致无关或泛泛的答案。
✅ 改进的提示:“我有一个 JavaScript 函数 mapUsersById
,它应该将一个用户对象数组转换为一个以用户 ID 为键的 map(对象)。然而,当我运行它时,它抛出了一个错误。例如,当我传入 [ {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;
}
它应该返回 { "1": {id: 1, name: "Alice"} }
。错误是什么,我该如何修复它?”
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
)并确认了预期的结果。改进的提示之所以成功,是因为它遵循了我们的原则:它指明了语言(JavaScript),描述了函数的目的,包含了确切的错误信息和示例输入,甚至提供了相关的代码片段。注意 AI 如何利用错误信息作为线索,将注意力集中在循环边界上——一个有针对性的提示让 AI 能够进行真正的问题解决,有效地模拟了人类调试者的思考过程:“undefined
可能来自哪里?很可能是循环索引的问题”。这是一个具体的例子,展示了详细提示的好处。
额外的调试策略: 除了识别明显的错误,你还可以使用提示工程来进行更深层次的调试辅助:
TypeError: cannot read property 'foo' of undefined
的可能原因有哪些?” 并附上代码。模型可能会列出几种情况(例如,对象未初始化、竞态条件、变量作用域错误等)。这可以为你提供一些你未曾考虑过的调查角度。这就像和同事进行头脑风暴。null
值等)。这对于调试和为未来的健壮性生成测试都很有用。总之,在使用 AI 助手进行调试时,细节和方向是你的朋友。提供场景、症状,然后提出有针对性的问题。正如我们上面看到的,一个含糊的“它不工作,帮帮我!”提示和一个精准的调试提示之间的差别是天壤之别。接下来,我们将转向另一个主要用例:重构和改进现有代码。
重构代码——在不改变其功能的前提下,使其更清晰、更快或更符合语言习惯——是 AI 助手可以大放异彩的领域。它们接受了大量代码的训练,其中包括许多结构良好、经过优化的解决方案的例子。然而,要有效地利用这些知识,你的提示必须阐明“更好”对你的情况意味着什么。以下是如何为重构任务提出提示:
1. 明确陈述你的重构目标。 单独的“重构这段代码”太开放了。你是想提高可读性?降低复杂性?优化性能?还是使用不同的范式或库?AI 需要一个目标。一个好的提示会框定任务,例如:“重构以下函数以提高其可读性和可维护性(减少重复,使用更清晰的变量名)。” 或者 “优化这个算法的速度——它在处理大输入时太慢了。” 通过陈述具体目标,你帮助模型决定应用哪些转换。例如,告诉它你关心性能可能会让它使用更高效的排序算法或缓存,而专注于可读性则可能让它将一个函数分解成更小的函数或添加注释。如果你有多个目标,请一一列出。Strapi 指南中的一个提示模板甚至建议列举问题:“我想解决的问题:1) [性能问题], 2) [代码重复], 3) [过时的 API 使用]。”。这样,AI 就确切地知道要修复什么。记住,它不会天生就知道你认为代码中的问题是什么——你必须告诉它。
2. 提供必要的代码上下文。 在重构时,你通常会在提示中包含需要改进的代码片段。重要的是要包含你想要重构的完整函数或部分,有时如果相关的话,还包括一些周围的上下文(比如函数的使用方式或相关代码,这可能会影响你如何重构)。同时也要提及语言和框架,因为“符合语言习惯”的代码在不同的环境中是不同的,比如说,符合 Node.js 习惯的代码与符合 Deno 习惯的代码,或者 React 类组件与函数式组件。例如:“我有一个用类编写的 React 组件。请将其重构为使用 Hooks 的函数式组件。” AI 随后会应用典型的步骤(使用 useState、useEffect 等)。如果你只说“重构这个 React 组件”而没有阐明风格,AI 可能不知道你特指想要用 Hooks。
3. 鼓励在提供代码的同时附上解释。 从 AI 主导的重构中学习(并验证其正确性)的一个好方法是要求对更改进行解释。例如:“请建议一个重构版本的代码,并解释你所做的改进。” 这甚至被内置到了我们引用的提示模板中:“……建议重构后的代码并附上对你更改的解释。”。当 AI 提供解释时,你可以评估它是否理解了代码并达到了你的目标。解释可能会说:“我将两个相似的循环合并为一个以减少重复,并使用字典进行更快的查找,”等等。如果解释中有什么听起来不对劲,那就是仔细检查代码的警示信号。简而言之,利用 AI 的解释能力作为一种保障——这就像让 AI 对自己的重构进行代码审查。
4. 使用角色扮演来设定高标准。 如前所述,要求 AI 扮演代码审查员或高级工程师的角色可能非常有效。对于重构,你可能会说:“扮演一位经验丰富的 TypeScript 专家,重构这段代码以符合最佳实践和现代标准。” 这通常不仅会产生表面的更改,还会带来更有洞察力的改进,因为 AI 会努力达到“专家”的身份。一个来自提示指南的流行例子是让 AI 扮演导师的角色:“像一位经验丰富的 Python 开发者指导初级开发者一样。提供解释并编写文档字符串。重写代码以优化它。”。在这种情况下,结果是 AI 使用了更高效的数据结构(用集合去重)并为一个原本使用循环的函数提供了一行解决方案。角色扮演不仅帮助它重构,还帮助它解释了为什么新方法更好(在这种情况下,使用集合是众所周知的去重优化)。
现在,让我们通过一个重构的例子来看看提示如何影响结果。我们将使用一个 JavaScript (Node.js) 的场景,我们有一些不太理想的代码,并希望改进它。
假设我们有一个函数,它进行两次数据库调用并做一些处理。它能工作,但不够美观——有重复的代码,而且难以阅读。我们希望为了清晰和效率而重构它。这是原始代码:
// 原始函数:获取两个列表并处理它们(需要重构)
asyncfunctiongetCombinedData(apiClient) {
// 获取用户列表
const usersResponse = await apiClient.fetch('/users');
if (!usersResponse.ok) {
thrownewError('Failed to fetch users');
}
const users = await usersResponse.json();
// 获取订单列表
const ordersResponse = await apiClient.fetch('/orders');
if (!ordersResponse.ok) {
thrownewError('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;
}
这段代码中的问题:我们看到了用户和订单的重复获取逻辑(可以重构为一个辅助函数或循环),错误细节不足(只有一个通用的错误消息),而且它或许可以并行运行获取操作以提高速度。让我们看看两种不同的提示方法:
❌ 提示 1 (不具体):“重构上面的 getCombinedData 函数。”
AI 对提示 1 的回应:
*当然。这是 getCombinedData 的一个重构版本:
async functiongetCombinedData(apiClient) {
// 并行获取用户和订单
const [usersResponse, ordersResponse] = awaitPromise.all([
apiClient.fetch('/users'),
apiClient.fetch('/orders')
]);
if (!usersResponse.ok || !ordersResponse.ok) {
thrownewError('Failed to fetch data');
}
const [users, orders] = awaitPromise.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] || []
}));
}
我并行获取了用户和订单以提高性能。我还整合了错误处理,并使用了一个 map (ordersByUser) 来按用户分组订单,这使得合并步骤更清晰,也可能更快。*
分析: 这实际上是一个相当不错的重构,实现了几个改进:并行获取、为提高效率按用户分组订单、使用 .map
进行更清晰的合并并默认为空数组。AI 也解释了它做了什么。那么我们为什么将这个提示标记为不具体或“糟糕”呢?在这种情况下,AI 碰巧做出了好的选择,但它假设了很多,因为提示是开放式的。它假设性能应该被提高(并行请求,这是一个聪明的改变,但如果在这里并行不安全呢?),并且它将错误处理改为一个通用的消息“Failed to fetch data”,这在合并错误时失去了具体是哪个调用失败的信息。
使用一个泛泛的“重构这个”提示的风险在于,AI 可能会做出以意想不到的方式改变行为的更改。在这里,合并错误处理可能是可接受的,也可能不是,这取决于需求。助手还做了一个结构性改变(使用字典来分组订单)——这很好,可能更好,但如果我们有特定的限制(比如想要流式处理结果或保持低内存使用),我们没有告知它。所以虽然结果看起来不错,但 AI 对“重构”的自我解释与我们想要的相符,这有些幸运。这说明一个不具体的提示有时在简单的案例中可以得到好的结果,但对于复杂的代码,它可能会走向一个不希望的方向或错过你关心的某些改进。
✅ 提示 2 (目标导向):“重构上面的 getCombinedData 函数以消除重复代码并提高性能。具体来说:(1) 避免重复用户和订单的获取逻辑——也许使用一个辅助函数或将它们一起获取。(2) 如果可能,并行获取两个列表。(3) 保留每次获取的错误处理(我们想知道是哪个调用失败了)。(4) 改进数据合并,可能通过使用更高效的查找结构而不是嵌套循环。提供重构后的代码,并附上解释更改的注释。”
AI 对提示 2 的回应:
*当然!这是 getCombinedData 的一个改进版本,带有请求的重构:
async functiongetCombinedData(apiClient) {
// 1. 并行获取用户和订单以提高性能
const [usersResponse, ordersResponse] = awaitPromise.all([
apiClient.fetch('/users'),
apiClient.fetch('/orders')
]);
// 2. 分别处理错误以知道是哪个调用失败了
if (!usersResponse.ok) {
thrownewError('Failed to fetch users');
}
if (!ordersResponse.ok) {
thrownewError('Failed to fetch orders');
}
// 并行解析 JSON 响应
const [users, orders] = awaitPromise.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
的单独检查,这样任何错误都能清楚地指出是哪个获取失败了(第 2 点)。response.json()
调用使用了 Promise.all
,这是一个微小的改进。ordersByUser
映射表(第 3 点)。这在一个单次遍历中按 userId
分组订单。然后我遍历用户来构建结果,以 O(1) 的时间从映射表中拉取每个用户的订单(第 4 点)。这更高效、更简洁。*分析: 这个回应直接解决了所有指定的目标。代码被重构得更清晰、更快,并且按要求保留了单独的错误消息。AI 的解释确认了我们列出的每一点,这表明它仔细遵循了提示的指示。这是一个很好的结果,因为我们作为提示者,定义了在这种情况下“重构”意味着什么。通过这样做,我们引导 AI 产生了一个符合我们需求的解决方案,而且几乎不需要来回沟通。如果 AI 忽略了其中一点(比如说它仍然合并了错误处理),我们可以很容易地再次提示:“看起来不错,但请确保用户和订单的错误消息保持不同。”——然而,在这种情况下,这没有必要,因为我们的提示很详尽。
这个例子展示了一个关键的教训:当你知道你想要改进什么时,把它说清楚。 AI 擅长遵循指令,但它不会读心术。一个宽泛的“让这个更好”可能对简单的事情有效,但对于不平凡的代码,通过列举“更好”对你意味着什么,你会得到最好的结果。这与社区的见解一致,即清晰、结构化的提示会产生显著改善的结果。
额外的重构技巧:
reduce
来构建一个映射表)。这是要求解释的一个原因:它将一个答案变成了一个迷你教程,加深了你对最佳实践的理解。至此,我们已经涵盖了调试和重构——改进现有代码。合乎逻辑的下一步是使用 AI 辅助实现新功能或生成新代码。我们将探讨如何有效地为那种场景提出提示。
❌ 糟糕的提示: "我的 useEffect 不正常"
✅ 增强的提示:
// 我有一个 React 组件,它获取用户数据,但导致了无限次的重新渲染。这是我的代码:
constUserProfile = ({ 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 的电商应用,需要设计状态管理架构。以下是我的需求:
组件:
技术限制:
我应该使用:
请提供一个推荐的架构,并附上代码示例,展示如何构建 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 传授你项目的环境,以便它能量身定制输出。一些助手甚至可以利用你的整个仓库作为上下文来借鉴;如果使用这些工具,请确保将其指向你仓库中类似的模块或文档。
3. 使用注释和 TODO 作为内联提示。 当直接在带有 Copilot 的 IDE 中工作时,一个有效的工作流程是编写一条描述你需要的下一块代码的注释,然后让 AI 自动补全它。例如,在 Node.js 后端,你可能会写:// TODO: 验证请求负载(确保提供了 name 和 email),然后开始下一行。Copilot 通常会捕捉到意图并生成执行该验证的代码块。这是因为你的注释实际上是一个自然语言提示。然而,如果 AI 误解了,要准备好编辑生成的代码——一如既往,验证其正确性。
4. 提供预期输入/输出或用法的示例。 与我们之前讨论的类似,如果你要求 AI 实现一个新函数,请包含一个关于它将如何被使用的快速示例或一个简单的测试用例。例如:“用 JavaScript 实现一个函数 formatPrice(amount),它接收一个数字(如 2.5)并返回一个格式化为美元的字符串(如 2.50'。”
通过给出那个例子,你限制了 AI 生成与之一致的函数。没有这个例子,AI 可能会假设其他一些格式或货币。差异可能很微妙但很重要。另一个在 Web 上下文中的例子:“实现一个记录请求的 Express 中间件。例如,对 /users 的 GET 请求应该在控制台记录 ‘GET /users’。” 这清楚地表明了输出应该是什么样子。在提示中包含预期行为就像一个 AI 会尝试满足的测试。
5. 当结果不符合你的期望时,用更多细节或约束重写提示。 生成新功能的第一次尝试没有成功是很常见的。也许代码可以运行但不够地道,或者它遗漏了一个要求。不要感到沮丧,把 AI 当作一个提交了初稿的初级开发者——现在你需要给出反馈。例如,“这个解决方案能用,但我更希望你使用内置的数组 filter 方法而不是 for 循环。” 或者,“你能将生成的组件重构为使用 React Hooks 进行状态管理而不是类组件吗?我们的代码库都是函数式组件。” 你还可以添加新的约束:“另外,确保函数在 O(n) 或更好的时间内运行,因为 n 可能很大。” 这种迭代式提示非常强大。一个真实世界的场景:一个开发者要求一个大语言模型生成代码来用 JS canvas 库画一个冰淇淋甜筒,但它一直给出不相关的输出,直到他们用更多具体细节和上下文来完善提示。教训是,不要一次尝试就放弃。找出提示中缺少或被误解了什么,并加以澄清。这就是提示工程的精髓——每一次微调都能引导模型更接近你的设想。
让我们用一个示例场景来说明功能实现的提示:
假设我们有一个 React 应用,我们想添加一个新组件:一个带有搜索过滤器和排序下拉菜单的产品列表。这是一个 Web 应用中相当典型的功能。为简便起见,我们将重点放在这个组件内的搜索功能的提示上。
初始提示(功能请求): “创建一个名为 ProductList 的 React 函数式组件,用于显示产品列表,并包含一个文本输入框以按名称过滤产品。该组件应:从 /api/products 获取产品数组(假设它返回 {id, name, ...} 的 JSON 数组),将其存储在状态中,并允许用户在搜索框中键入以按名称过滤显示的产品(不区分大小写匹配)。在一个简单的 <ul> 中显示过滤后的列表,并显示产品名称。包含基本的错误处理(例如,如果 API 调用失败,则显示错误消息)和获取数据时的加载状态。”
这是一个很长的提示,但很具体。我们假设我们把这个交给一个聊天式的大语言模型。
AI 的回应(它可能产生的内容摘要): AI 可能会输出一个类似这样的 ProductList
组件:
import React, { useState, useEffect } from'react';
functionProductList() {
const [products, setProducts] = useState([]);
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
asyncfunctionfetchProducts() {
try {
const res = awaitfetch('/api/products');
if (!res.ok) thrownewError('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();
}, []);
consthandleSearchChange = (e) => {
setSearch(e.target.value);
};
// 按搜索查询过滤产品(不区分大小写)
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(search.toLowerCase())
);
if (loading) return<div>Loading products...</div>;
if (error) return<div>{error}</div>;
return (
<div className="product-list">
<input
type="text"
placeholder="Search products"
value={search}
onChange={handleSearchChange}
/>
<ul>
{filteredProducts.map(prod => (
<li key={prod.id}>{prod.name}</li>
))}
</ul>
</div>
);
}
exportdefaultProductList;
(如果我们要求的话,代码中可能还会有一些解释各部分的注释。)
现在,让我们分析这个结果并考虑提示的改进:
useEffect
中获取的选择。如果我们更喜欢使用自定义钩子或 context,我们应该对此有所提示。所以我们可以改进我们的提示:“实际上,在我们的应用中,我们使用一个自定义钩子 useProducts()
来处理数据获取。请将组件重构为使用 useProducts
钩子,而不是直接调用 fetch
。” AI 随后会进行调整,可能会假设 useProducts
返回 { products, loading, error }
并相应地简化组件。const [sortOrder, setSortOrder] = useState('asc');
// ... 一个用于 sortOrder 的 select 输入框 ...
// 并在渲染前对 filteredProducts 进行排序:
const sortedProducts = [...filteredProducts].sort((a, b) => {
if (sortOrder === 'asc') return a.name.localeCompare(b.name);
else return b.name.localeCompare(a.name);
});
通过这样逐个功能地迭代,我们与 AI 模拟了一个开发周期。这远比试图一次性提示整个复杂组件的所有功能要有效得多。它减少了错误,并允许在需求变得更清晰时进行中途修正。
这个例子表明,用 AI 实现功能完全是关于增量开发和提示优化。一条 Twitter 帖子可能会惊呼某人如何通过不断地向一个大语言模型提示每个部分来构建一个小应用——这本质上就是这种方法:构建、审查、优化、扩展。每个提示就像你开发过程中的一次提交。
实现功能的额外技巧:
/**
* 返回第 n 个斐波那契数。
* @param {number} n - 斐波那契数列中的位置(从 0 开始索引)。
* @returns {number} 第 n 个斐波那契数。
*
* 示例: fibonacci(5) -> 5 (数列: 0,1,1,2,3,5,…)
*/
function fibonacci(n) {
// ... 实现
}
在介绍了调试、重构和新代码生成的提示策略之后,让我们将注意力转向在编码提示工程中的一些常见陷阱和反模式。理解这些将帮助你避免在低效的互动中浪费时间,并在 AI 没有给你想要的东西时迅速调整。
并非所有提示都是平等的。到目前为止,我们已经看到了许多有效提示的例子,但认识到反模式——那些导致 AI 响应不佳的常见错误——同样具有启发性。
以下是一些常见的提示失败案例及其修复方法:
最后,这里有一个在事情出错时重写提示的战术方法:
通过意识到这些反模式及其解决方案,你将能更快地随时调整你的提示。对于开发者来说,提示工程在很大程度上是一个迭代的、反馈驱动的过程(就像任何编程任务一样!)。好消息是,你现在工具箱里已经有很多模式和例子可以借鉴了。
提示工程既是一门艺术,也是一门科学——正如我们所见,它正迅速成为使用 AI 代码助手的开发者必备的技能。通过精心设计清晰、富含上下文的提示,你实际上是在教 AI 你需要什么,就像你引导一个新的人类团队成员或向同事解释一个问题一样。在本文中,我们探讨了如何系统地为调试、重构和功能实现构建提示:
当你将这些技巧融入你的工作流程时,你可能会发现与 AI 的合作变得更加直观。你会培养出一种感觉,知道什么样的措辞能得到最好的结果,以及在模型偏离轨道时如何引导它。请记住,AI 是其训练数据的产物——它看过许多代码和解决问题的例子,但提供方向,指明哪些例子在当下是相关的,这正是你的工作。本质上,你设定上下文,AI 跟进执行。
同样值得注意的是,提示工程是一种不断发展的实践。 开发者社区在不断发现新的技巧——一个巧妙的一行提示或一个结构化模板可能会突然在社交媒体上走红,因为它解锁了人们之前没有意识到的能力。请关注这些讨论(在 Hacker News、Twitter 等平台上),因为它们可以激发你自己的技巧。但也不要害怕自己进行实验。把 AI 当作一个灵活的工具——如果你有一个想法(“如果我让它画一个我的架构的 ASCII 图会怎么样?”),就去试试。你可能会对结果感到惊讶,如果失败了,也没什么损失——你对模型的局限性或需求有了新的了解。
总而言之,提示工程使开发者能够从 AI 助手中获得更多。 这是 frustrating experience(“这个工具没用,它给了我一堆胡说八道”)和 productive one(“这感觉就像和一个为我写样板代码的专家结对编程”)之间的区别。通过应用我们所涵盖的策略手册——从提供详尽的上下文到微调 AI 的风格和思维——你可以将这些专注于代码的 AI 工具变成你开发工作流程的真正延伸。最终结果不仅是你编码更快,而且你常常会在这个过程中学到新的见解和模式(因为 AI 会解释事物或建议替代方案),从而提升你自己的技能水平。
作为最后的总结,请记住提示是一个迭代的对话。用你与另一位工程师沟通时所使用的同样清晰、耐心和彻底的态度来对待它。这样做,你会发现 AI 助手可以显著放大你的能力——帮助你更快地调试、更聪明地重构,并更轻松地实现功能。
祝你提示愉快,编码快乐!
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-06-23
被 AI 气到崩溃?手把手教你写出 “有效指令”
2025-06-21
AI越强,Prompt越没用?恰恰相反,不懂这些你将被淘汰
2025-06-18
Dify高阶技巧:通过元数据实现文档级检索问答
2025-06-17
深度解析 Cursor(逐行解析系统提示词、分享高效制定 Cursor Rules 的技巧...)
2025-06-17
做 Prompt 工程师半年,我被大模型按在地上摩擦后悟了些什么?
2025-06-11
撒娇、威胁对大模型都没用?顶级科研团队2000次实验,14种常用prompt技巧谁是强神谁是狼!撬动大模型能力杠杆,秘诀都在这了
2025-06-10
沃顿商学院重磅Prompt报告:别再对AI"循循善诱"了!我们在错误地使用它!
2025-06-08
一年前的提示词技巧已经落伍了?Anthropic CPO 揭秘7个AI协作新玩法
2025-05-09
2025-06-02
2025-05-25
2025-03-31
2025-04-07
2025-04-15
2025-04-30
2025-04-13
2025-03-31
2025-03-31
2025-06-17
2025-04-21
2025-03-31
2025-03-29
2025-03-17
2025-02-06
2025-01-10
2024-12-25