向 AI 编码工具提问,有点像与一个极其刻板、但有时知识渊博的合作者沟通。为了得到有用的结果,你需要清晰地设定场景,并引导 AI 明白你想要什么以及你希望它如何做。
以下是贯穿本手册所有示例的基础原则:
•提供丰富的上下文。始终假设 AI 对你的项目一无所知,除了你提供的信息。包括相关细节,如编程语言、框架和库,以及具体的函数或代码片段。如果出现错误,请提供确切的错误信息,并描述代码应该做什么。明确性和上下文是区分模糊建议和精确、可操作解决方案的关键。在实践中,这意味着你的提示可能包含一个简短的背景介绍,例如:“我有一个使用 Express 和 Mongoose 的 Node.js 函数,它应该通过 ID 获取用户,但抛出了一个 TypeError。这是代码和错误信息……”。你提供的背景越多,AI 需要猜测的就越少。
•明确你的目标或问题。模糊的查询导致模糊的答案。与其问“我的代码为什么不工作?”,不如精确指出你需要什么洞见。例如:“这个 JavaScript 函数返回了 undefined,而不是预期的结果。根据下面的代码,你能帮忙找出原因并修复吗?”这样的提问更有可能得到有用的回答。一个调试的提示公式是:“它预期会[预期行为],但在给定[示例输入]时,它却[当前行为]。错误在哪里?”。同样,如果你想要优化,请要求特定类型的优化(例如,“如何提高这个排序函数处理一万个项目时的运行时性能?”)。明确性引导 AI 的注意力。
•分解复杂任务。在实现新功能或处理多步骤问题时,不要把整个问题塞进一个巨大的提示里。将工作分成更小的部分并进行迭代通常更有效。例如,“首先,为产品列表页面生成一个 React 组件的骨架。接下来,我们添加状态管理。然后,我们再集成 API 调用。”每个提示都建立在前一个的基础上。通常不建议一次性要求完成一个大型功能;相反,从一个高层目标开始,然后迭代地要求每个部分。这种方法不仅使 AI 的回应保持专注和可管理,也模仿了人类逐步构建解决方案的方式。
•包含输入/输出示例或预期行为。如果你能用一个例子来说明你想要什么,就这么做。例如,“给定数组 [3,1,4],这个函数应该返回 [1,3,4]。”在提示中提供具体例子有助于 AI 理解你的意图并减少歧义。这类似于给一个初级开发者一个快速的测试用例——它澄清了需求。在提示工程术语中,这有时被称为“少样本提示(few-shot prompting)”,即你向 AI 展示一个模式让它遵循。即使只有一个正确行为的例子,也能显著地引导模型的响应。
•迭代并优化对话。提示工程是一个互动过程,而非一蹴而就。开发者通常需要审查 AI 的第一个回答,然后提出后续问题或进行修正。如果解决方案不完全正确,你可能会说,“那个解决方案使用了递归,但我更喜欢迭代的方法——你能不用递归再试一次吗?”或者,“很好,现在你能改进一下变量名并添加注释吗?”AI 会记住聊天会话的上下文,所以你可以逐步引导它达到期望的结果。关键在于将 AI 视为一个你可以指导的伙伴——追求进步而非第一次就完美。
•保持代码的清晰和一致性。这最后一个原则有点间接,但对于处理你代码上下文的工具来说非常重要。即使在 AI 介入之前,也要编写清晰、结构良好的代码和注释。有意义的函数和变量名、一致的格式以及文档字符串不仅使你的代码对人类来说更容易理解,也为 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 响应的差异:
调试示例:糟糕的提示 vs. 改进的提示
假设我们有一个简单的 Node.js 函数,旨在将用户对象列表转换为按用户 ID 查找的映射表。然而,它抛出了一个错误。这是有问题的代码:
这段代码本应遍历users数组并构建一个以用户 ID 为键的对象。然而,存在一个错误:循环使用了i <= users.length而不是i < users.length。这会导致在最后一次迭代中users[i]变为undefined,很可能导致运行时错误(例如,尝试访问undefined的id属性)。让我们看看 AI 助手对关于这个问题的两个不同提示会如何回应。
分析:这个回答相当模糊。提示除了函数名外没有提供任何细节,所以 AI 只能猜测一些常见原因(“也许数组是空的或不是数组”,“确保每个用户都有一个 id”)。它没有识别出真正的错误。当你提问不够具体时,你就会得到这种回应——模型只能复述一些通用的调试技巧。作为开发者,这没什么帮助,因为它没有解决真正的错误。提示中缺乏上下文和明确性导致了泛泛的回答(基本上相当于 AI 版的“你试过重启吗?”)。这与社区的建议一致,即模糊的提示会导致无关或泛泛的答案。
✅ 改进的提示:“我有一个 JavaScript 函数mapUsersById,它应该将一个用户对象数组转换为一个以用户 ID 为键的 map(对象)。然而,当我运行它时,它抛出了一个错误。例如,当我传入[ {id: 1, name: "Alice"} ]时,我得到TypeError: Cannot read property 'id' of undefined。这是函数代码:
分析:这个回应直接指出了错误(在循环中使用了<=)并解释了为什么这会导致user变为undefined。然后,它提供了正确的代码解决方案(i < users.length)并确认了预期的结果。改进的提示之所以成功,是因为它遵循了我们的原则:它指明了语言(JavaScript),描述了函数的目的,包含了确切的错误信息和示例输入,甚至提供了相关的代码片段。注意 AI 如何利用错误信息作为线索,将注意力集中在循环边界上——一个有针对性的提示让 AI 能够进行真正的问题解决,有效地模拟了人类调试者的思考过程:“undefined可能来自哪里?很可能是循环索引的问题”。这是一个具体的例子,展示了详细提示的好处。
额外的调试策略:除了识别明显的错误,你还可以使用提示工程来进行更深层次的调试辅助:
•询问潜在原因。如果你真的束手无策,可以稍微扩大问题范围:“在这段代码中,出现TypeError: cannot read property 'foo' of undefined的可能原因有哪些?”并附上代码。模型可能会列出几种情况(例如,对象未初始化、竞态条件、变量作用域错误等)。这可以为你提供一些你未曾考虑过的调查角度。这就像和同事进行头脑风暴。
•“向小黄鸭提问”——即,向 AI 解释你的代码。这听起来可能有些反直觉(为什么要向助手解释?),但写解释的过程可以澄清你自己的理解,然后你可以让 AI 来验证或批评它。例如:“我来解释一下这个函数在做什么:[你的解释]。基于此,我的推理正确吗?它是否揭示了错误所在?”AI 可能会在你的解释中发现一个指向实际错误的缺陷。这项技术将 AI 作为一个不仅会倾听还会回应的积极的小黄鸭来利用。
•让 AI 创建测试用例。你可以问:“你能提供几个可能让这个函数崩溃的测试用例(输入)吗?”助手可能会想出你没想到的边缘情况(空数组、极大数字、null值等)。这对于调试和为未来的健壮性生成测试都很有用。
•角色扮演代码审查员。作为直接“调试这个”提示的替代方案,你可以说:“扮演一个代码审查员。这是一段没有按预期工作的代码片段。请审查它并指出任何可能导致问题的错误或不良实践:[代码]”。这将 AI 置于一种批判模式。许多开发者发现,将请求表述为代码审查会得到非常详尽的分析,因为模型会评论代码的每个部分(并且通常在这样做时,它会发现错误)。事实上,一个提示工程技巧是明确要求 AI 表现得像一个细致的审查员。这不仅能发现手头的错误,还能发现其他问题(例如,可能缺少空值检查),这可能很有用。
总之,在使用 AI 助手进行调试时,细节和方向是你的朋友。提供场景、症状,然后提出有针对性的问题。正如我们上面看到的,一个含糊的“它不工作,帮帮我!”提示和一个精准的调试提示之间的差别是天壤之别。接下来,我们将转向另一个主要用例:重构和改进现有代码。
重构和优化的提示模式
重构代码——在不改变其功能的前提下,使其更清晰、更快或更符合语言习惯——是 AI 助手可以大放异彩的领域。它们接受了大量代码的训练,其中包括许多结构良好、经过优化的解决方案的例子。然而,要有效地利用这些知识,你的提示必须阐明“更好”对你的情况意味着什么。以下是如何为重构任务提出提示:
•如果相关,请包含版本或环境细节。例如,“这是一个 Node.js v14 的代码库”或“我们正在使用 ES6 模块”。这会影响 AI 是否使用某些语法(比如 import/export vs. require),这是正确重构的一部分。如果你想确保它不会引入不兼容的东西,请提及你的限制。
3. 鼓励在提供代码的同时附上解释。从 AI 主导的重构中学习(并验证其正确性)的一个好方法是要求对更改进行解释。例如:“请建议一个重构版本的代码,并解释你所做的改进。”这甚至被内置到了我们引用的提示模板中:“……建议重构后的代码并附上对你更改的解释。”。当 AI 提供解释时,你可以评估它是否理解了代码并达到了你的目标。解释可能会说:“我将两个相似的循环合并为一个以减少重复,并使用字典进行更快的查找,”等等。如果解释中有什么听起来不对劲,那就是仔细检查代码的警示信号。简而言之,利用 AI 的解释能力作为一种保障——这就像让 AI 对自己的重构进行代码审查。
4. 使用角色扮演来设定高标准。如前所述,要求 AI 扮演代码审查员或高级工程师的角色可能非常有效。对于重构,你可能会说:“扮演一位经验丰富的 TypeScript 专家,重构这段代码以符合最佳实践和现代标准。”这通常不仅会产生表面的更改,还会带来更有洞察力的改进,因为 AI 会努力达到“专家”的身份。一个来自提示指南的流行例子是让 AI 扮演导师的角色:“像一位经验丰富的 Python 开发者指导初级开发者一样。提供解释并编写文档字符串。重写代码以优化它。”。在这种情况下,结果是 AI 使用了更高效的数据结构(用集合去重)并为一个原本使用循环的函数提供了一行解决方案。角色扮演不仅帮助它重构,还帮助它解释了为什么新方法更好(在这种情况下,使用集合是众所周知的去重优化)。
分析:这实际上是一个相当不错的重构,实现了几个改进:并行获取、为提高效率按用户分组订单、使用.map进行更清晰的合并并默认为空数组。AI 也解释了它做了什么。那么我们为什么将这个提示标记为不具体或“糟糕”呢?在这种情况下,AI 碰巧做出了好的选择,但它假设了很多,因为提示是开放式的。它假设性能应该被提高(并行请求,这是一个聪明的改变,但如果在这里并行不安全呢?),并且它将错误处理改为一个通用的消息“Failed to fetch data”,这在合并错误时失去了具体是哪个调用失败的信息。
使用一个泛泛的“重构这个”提示的风险在于,AI 可能会做出以意想不到的方式改变行为的更改。在这里,合并错误处理可能是可接受的,也可能不是,这取决于需求。助手还做了一个结构性改变(使用字典来分组订单)——这很好,可能更好,但如果我们有特定的限制(比如想要流式处理结果或保持低内存使用),我们没有告知它。所以虽然结果看起来不错,但 AI 对“重构”的自我解释与我们想要的相符,这有些幸运。这说明一个不具体的提示有时在简单的案例中可以得到好的结果,但对于复杂的代码,它可能会走向一个不希望的方向或错过你关心的某些改进。
分析:这个回应直接解决了所有指定的目标。代码被重构得更清晰、更快,并且按要求保留了单独的错误消息。AI 的解释确认了我们列出的每一点,这表明它仔细遵循了提示的指示。这是一个很好的结果,因为我们作为提示者,定义了在这种情况下“重构”意味着什么。通过这样做,我们引导 AI 产生了一个符合我们需求的解决方案,而且几乎不需要来回沟通。如果 AI 忽略了其中一点(比如说它仍然合并了错误处理),我们可以很容易地再次提示:“看起来不错,但请确保用户和订单的错误消息保持不同。”——然而,在这种情况下,这没有必要,因为我们的提示很详尽。
•询问替代方法:也许 AI 的第一次重构有效,但你对不同的角度感到好奇。你可以问,“你能用另一种方式重构它吗,也许使用函数式编程风格(例如,使用数组方法而不是循环)?”或者“用递归代替迭代方法怎么样,只是为了比较一下?”这样,你就可以评估不同的解决方案。这就像和同事一起头脑风暴多个重构选项。
•结合重构与解释来学习模式:我们已经提到了这一点,但值得强调——把 AI 当作一个学习工具。如果它用一种巧妙的方式重构了代码,研究输出和解释。你可能会发现一个你以前没用过的新 API 或技术(比如使用reduce来构建一个映射表)。这是要求解释的一个原因:它将一个答案变成了一个迷你教程,加深了你对最佳实践的理解。
•验证和测试:在任何 AI 生成的重构之后,一定要运行你的测试或用示例输入尝试代码。AI 可能会无意中引入细微的错误,特别是如果提示没有指定一个重要的约束。例如,在我们的重构中,如果原始代码有意为日志记录而分离获取错误,但我们没有提到日志记录,那么合并后的错误可能就不那么有用了。我们的工作是在审查中发现这一点。AI 也可以通过编写测试来提供帮助——你可以问*“为重构后的函数生成几个单元测试”*,以确保它在预期输入上的行为与之前相同。
至此,我们已经涵盖了调试和重构——改进现有代码。合乎逻辑的下一步是使用 AI 辅助实现新功能或生成新代码。我们将探讨如何有效地为那种场景提出提示。
AI 代码助手最令人兴奋的用途之一是帮助你从头开始编写新代码或将新功能集成到现有代码库中。这可以是从为 React 组件生成样板代码到在 Express 应用中编写新的 API 端点。这里的挑战通常是这些任务是开放式的——实现一个功能有很多种方法。用于代码生成的提示工程旨在引导 AI 生成符合你需求和风格的代码。以下是实现这一目标的一些策略:
1. 从高层指令开始,然后逐步深入。首先用通俗的语言概述你想要构建什么,可能将其分解成更小的任务(类似于我们之前关于分解复杂任务的建议)。例如,假设你想为一个现有的 Web 应用添加一个搜索栏功能。你可能首先提示:“概述一个计划,在我的 React 应用中添加一个搜索功能,该功能可以按名称过滤产品列表。产品是从一个 API 获取的。”
AI 可能会给你一个分步计划:“1. 添加一个用于搜索查询的输入字段。2. 添加状态来保存查询。3. 根据查询过滤产品列表。4. 确保搜索不区分大小写,等等。” 一旦你有了这个计划(你可以在 AI 的帮助下对其进行完善),你就可以用集中的提示来处理每个要点。
2. 提供相关上下文或参考代码。如果你要向现有项目添加功能,向 AI 展示该项目中类似功能是如何完成的会非常有帮助。例如,如果你已经有一个与你想要的组件相似的组件,你可以说:“这是一个现有的 UserList 组件(代码…)。现在创建一个类似的 ProductList 组件,但要包括一个搜索栏。”
AI 将会看到这些模式(也许你使用了某些库或风格约定)并应用它们。在你的提示中打开相关文件或引用它们,可以提供上下文,从而产生更具项目针对性和一致性的代码建议。另一个技巧是:如果你的项目使用特定的编码风格或架构(比如使用 Redux 进行状态管理或某个 CSS 框架),请提及它。“我们使用 Redux 进行状态管理——将搜索状态集成到 Redux store 中。”
一个训练有素的模型随后会生成与 Redux 模式等一致的代码。从本质上讲,你是在向 AI 传授你项目的环境,以便它能量身定制输出。一些助手甚至可以利用你的整个仓库作为上下文来借鉴;如果使用这些工具,请确保将其指向你仓库中类似的模块或文档。
• 如果开始一个新项目但你有偏好的方法,你也可以提及:“我想用函数式编程风格来实现这个(没有外部状态,使用数组方法)。”或者,“确保遵循 MVC 模式,将逻辑放在控制器中,而不是视图中。”这些都是高级工程师可能会提醒初级工程师的细节,而在这里你就是告诉 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 库画一个冰淇淋甜筒,但它一直给出不相关的输出,直到他们用更多具体细节和上下文来完善提示。教训是,不要一次尝试就放弃。找出提示中缺少或被误解了什么,并加以澄清。这就是提示工程的精髓——每一次微调都能引导模型更接近你的设想。
让我们用一个示例场景来说明功能实现的提示:
功能实现示例:在 AI 辅助下构建 React 组件
假设我们有一个 React 应用,我们想添加一个新组件:一个带有搜索过滤器和排序下拉菜单的产品列表。这是一个 Web 应用中相当典型的功能。为简便起见,我们将重点放在这个组件内的搜索功能的提示上。
通过这样逐个功能地迭代,我们与 AI 模拟了一个开发周期。这远比试图一次性提示整个复杂组件的所有功能要有效得多。它减少了错误,并允许在需求变得更清晰时进行中途修正。
• 如果 AI 犯了一个细微的错误(比如它忘记让搜索过滤器不区分大小写),我们只需指出:“让搜索不区分大小写。”它会调整过滤器以使用小写比较(在我们的伪输出中它已经这样做了,但如果没有,它会修复它)。
这个例子表明,用 AI 实现功能完全是关于增量开发和提示优化。一条 Twitter 帖子可能会惊呼某人如何通过不断地向一个大语言模型提示每个部分来构建一个小应用——这本质上就是这种方法:构建、审查、优化、扩展。每个提示就像你开发过程中的一次提交。
实现功能的额外技巧:
•让 AI 搭建骨架,然后你填充具体内容:有时让 AI 生成一个粗略的结构,然后你再进行微调会很有用。例如,“生成一个 Node.js Express 用户注册路由的骨架,包括验证和错误处理。”它可能会产生一个带有占位符的通用路由。然后你可以填写特定于你应用的实际验证规则或数据库调用。AI 为你省去了编写样板代码的麻烦,而你则处理敏感的自定义逻辑。
•要求处理边缘情况:在生成功能时,你可以提示 AI 思考边缘情况:“对于这个功能,我们应该考虑哪些边缘情况(你能在代码中处理它们吗)?”例如,在搜索示例中,一个边缘情况可能是“如果用户输入时产品还没有加载怎么办?”(尽管我们的代码通过加载状态处理了这一点)或者“如果两个产品有相同的名称怎么办”(不是大问题,但也许可以提一下)。AI 可能会提到诸如空结果处理、非常大的列表(可能需要对搜索输入进行防抖处理)等。这是一种利用 AI 在常见陷阱上的训练经验的方法。
•文档驱动开发:一些人采取了一种巧妙的方法,即先编写文档字符串或用法示例,然后让 AI 实现函数以匹配。例如:
• 如果你编写了上面的注释和函数签名,一个大语言模型可能会正确地填充实现,因为注释确切地描述了要做什么,甚至给出了一个例子。这种技术确保你先用文字澄清功能(这通常是一个好习惯),然后 AI 以此为规范来编写代码。
在介绍了调试、重构和新代码生成的提示策略之后,让我们将注意力转向在编码提示工程中的一些常见陷阱和反模式。理解这些将帮助你避免在低效的互动中浪费时间,并在 AI 没有给你想要的东西时迅速调整。
常见的提示反模式及其避免方法
并非所有提示都是平等的。到目前为止,我们已经看到了许多有效提示的例子,但认识到反模式——那些导致 AI 响应不佳的常见错误——同样具有启发性。
以下是一些常见的提示失败案例及其修复方法:
•反模式:模糊的提示。这是典型的*“它不工作,请修复它”或“写一个做 X 的东西”而没有足够细节的例子。我们之前看到了一个例子,当问题“为什么我的函数不工作?”得到一个无用的回答时。模糊的提示迫使 AI 猜测上下文,通常导致泛泛的建议或不相关的代码。解决方法很简单:添加上下文和具体细节。如果你发现自己提出的问题得到的回答像是一个魔力八号球的响应(“你试过检查 X 吗?”),停下来,用更多细节(错误消息、代码摘录、预期与实际结果等)重新组织你的查询。一个好的做法是读一下你的提示,然后问自己,“这个问题能适用于几十种不同的场景吗?”* 如果是,那它就太模糊了。让它变得具体到只能适用于你的场景。
•反模式:缺失问题。有时用户会提供大量信息,但从未清楚地提出问题或指明他们需要什么。例如,扔下一大段代码片段,只说“这是我的代码”。这会迷惑 AI——它不知道你想要什么。始终包含一个明确的要求,例如*“识别上述代码中的任何错误”、“解释这段代码是做什么的”或“完成代码中的 TODO。”* 一个提示应该有一个目的。如果你只提供文本而没有问题或指令,AI 可能会做出错误的假设(比如总结代码而不是修复它等)。确保 AI 知道你为什么给它看了一些代码。即使是简单的补充,如*“这段代码有什么问题?”或“请继续实现这个函数。”*也能给它方向。
•反模式:模糊的成功标准。这是一个微妙的问题——有时你可能会要求优化或改进,但你没有定义成功是什么样子。例如,“让这个函数更快。”快是以什么标准衡量?如果 AI 不知道你的性能限制,它可能会对一些无关紧要的东西进行微优化,或者使用一种理论上更快但实际上可以忽略不计的方法。或者*“让这段代码更清晰”*——“更清晰”是主观的。我们通过明确说明目标来处理这个问题,比如“减少重复”或“改进变量名”等。解决方法是:量化或限定改进。例如,“优化这个函数,使其以线性时间运行(当前版本是二次的)”或“重构这个函数以移除全局变量并改用类”。基本上,要明确你通过重构或功能解决的是什么问题。如果你让它太开放,AI 可能会解决一个与你关心的问题不同的问题。
•反模式:忽略 AI 的澄清或输出。有时 AI 可能会以一个澄清问题或一个假设来回应。例如:“你使用的是 React 类组件还是函数式组件?”或“我假设输入是一个字符串——请确认。”如果你忽略这些,只是重申你的请求,你就错过了一个改进提示的机会。AI 在示意它需要更多信息。一定要回答它的问题或完善你的提示以包含这些细节。此外,如果 AI 的输出明显错误(比如它误解了问题),不要只是逐字重试相同的提示。花点时间调整你的措辞。也许你的提示中有个模糊的短语或遗漏了什么必要的东西。把它当作一次对话——如果一个人类误解了,你会用不同的方式解释;对 AI 也一样。
•反模式:风格多变或不一致。如果你一直改变提问方式或在一次请求中混合不同的格式,模型可能会感到困惑。例如,在指令中在第一人称和第三人称之间切换,或者以一种令人困惑的方式混合伪代码和实际代码。尝试在单个提示中保持一致的风格。如果你提供示例,确保它们被清晰地划分(使用 Markdown 的三反引号表示代码,引号表示输入/输出示例等)。一致性有助于模型正确解析你的意图。另外,如果你有偏好的风格(比如,ES6 vs ES5 语法),要始终如一地提及它,否则模型可能会在一个提示中建议一种方式,在另一个提示中建议另一种。
提示工程既是一门艺术,也是一门科学——正如我们所见,它正迅速成为使用 AI 代码助手的开发者必备的技能。通过精心设计清晰、富含上下文的提示,你实际上是在教AI 你需要什么,就像你引导一个新的人类团队成员或向同事解释一个问题一样。在本文中,我们探讨了如何系统地为调试、重构和功能实现构建提示:
• 我们学会了向 AI 提供与向同事求助时相同的信息:代码应该做什么、它如何行为异常、相关的代码片段等等——从而获得更有针对性的帮助。
• 我们看到了与 AI 迭代的威力,无论是逐行遍历函数逻辑,还是通过多个提示优化解决方案(比如将递归方案改为迭代方案,然后改进变量名)。耐心和迭代将 AI 从一个一次性的代码生成器转变为真正的结对程序员。
• 我们利用角色扮演和身份设定来提升响应的水平——将 AI 视为代码审查员、导师或某个技术栈的专家。这通常会产生更严谨、解释更丰富的输出,不仅解决了问题,还在过程中教育了我们。
• 对于重构和优化,我们强调了定义“好”的标准(无论是更快、更清晰、更符合语言习惯等),而 AI 也表明,在引导下,它可以应用已知的最佳实践(如并行化调用、消除重复、正确处理错误)。这就像拥有了无数代码审查员的集体智慧——但你必须提出正确的问题才能挖掘出来。
• 我们还演示了在 AI 辅助下逐步构建新功能,表明即使是复杂的任务也可以分解并一次一个提示地解决。如果提示得当,AI 可以搭建样板代码、建议实现方案,甚至高亮边缘情况——扮演一个随时待命、知识渊博的共同开发者。
• 在此过程中,我们识别了要避免的陷阱:保持提示既不太模糊也不太超载,始终明确我们的意图和约束,并在 AI 输出不准确时随时准备调整。我们引用了糟糕提示的具体例子,并看到了微小的改变(如包含错误消息或预期输出)如何能极大地改善结果。
当你将这些技巧融入你的工作流程时,你可能会发现与 AI 的合作变得更加直观。你会培养出一种感觉,知道什么样的措辞能得到最好的结果,以及在模型偏离轨道时如何引导它。请记住,AI 是其训练数据的产物——它看过许多代码和解决问题的例子,但提供方向,指明哪些例子在当下是相关的,这正是你的工作。本质上,你设定上下文,AI 跟进执行。
同样值得注意的是,提示工程是一种不断发展的实践。开发者社区在不断发现新的技巧——一个巧妙的一行提示或一个结构化模板可能会突然在社交媒体上走红,因为它解锁了人们之前没有意识到的能力。请关注这些讨论(在 Hacker News、Twitter 等平台上),因为它们可以激发你自己的技巧。但也不要害怕自己进行实验。把 AI 当作一个灵活的工具——如果你有一个想法(“如果我让它画一个我的架构的 ASCII 图会怎么样?”),就去试试。你可能会对结果感到惊讶,如果失败了,也没什么损失——你对模型的局限性或需求有了新的了解。
总而言之,提示工程使开发者能够从 AI 助手中获得更多。这是 frustrating experience(“这个工具没用,它给了我一堆胡说八道”)和 productive one(“这感觉就像和一个为我写样板代码的专家结对编程”)之间的区别。通过应用我们所涵盖的策略手册——从提供详尽的上下文到微调 AI 的风格和思维——你可以将这些专注于代码的 AI 工具变成你开发工作流程的真正延伸。最终结果不仅是你编码更快,而且你常常会在这个过程中学到新的见解和模式(因为 AI 会解释事物或建议替代方案),从而提升你自己的技能水平。
作为最后的总结,请记住提示是一个迭代的对话。用你与另一位工程师沟通时所使用的同样清晰、耐心和彻底的态度来对待它。这样做,你会发现 AI 助手可以显著放大你的能力——帮助你更快地调试、更聪明地重构,并更轻松地实现功能。