🍊 UglyOrange
🛠️ 技术

AI 编程助手提示词工程指南

frigidpluto Views: ...
给程序员的提示词工程指南

这是由 Google 工程负责人 Addy Osmani 分享的提示词工程指南,非常值得一读。核心思想是:

你提供的信息越多,输出就越好。

这篇指南从以下几个方面介绍了提示词工程:

  • 有效提示词的基础原则
  • 调试代码的提示词模式
  • 重构和优化的提示词模式
  • 实现新功能的提示词模式
  • 功能实现示例:使用 AI 助手构建 React 组件

有效提示词的基础原则

1. 提供丰富的上下文

你必需假设 AI 对你的项目一无所知,包括编程语言、框架、库以及具体的函数或代码片段。如果运行时出现错误,你应该提供确切的错误消息并描述代码应该做什么。

我有一个使用 Express 和 Mongoose 的 Node.js 函数,应该通过 ID 获取用户,但它抛了个 TypeError。这是代码和错误...

2. 明确目标或问题

模糊的查询会导致模糊的答案,不要问”为什么我的代码不工作?“,而是明确指出你需要什么。

这个 JavaScript 函数返回 undefined 而不是预期结果。根据下面的代码,你能帮我找出原因并修复它吗?

调试的一个提示词公式是:

它应该做 [预期行为],但在给定 [示例输入] 时却做了 [当前行为]。bug 在哪里?

3. 分解复杂任务

不要在一个提示词中处理整个大问题,一口吃不成胖子,把工作分成小块并迭代处理。

首先,为产品列表页面生成一个 React 组件骨架。接下来,我们将添加状态管理。然后,我们将集成 API 调用。

4. 包含输入/输出示例

如果能用例子说明你想要什么,就提供一条示例。

给定数组 [3,1,4],这个函数应该返回 [1,3,4]。

5. 利用角色扮演

让 AI 扮演特定角色可以影响回答的风格和深度。

扮演一个高级 React 开发者,审查我的代码中潜在的 bug。

6. 迭代和优化对话

提示词工程是一个交互过程。开发者通常需要审查 AI 的第一次回答,然后提出后续问题或进行修正。

Question 1: xxx
Answer 1: xxx
Question 2: xxx
Answer 2: xxx
...

7. 保持代码清晰和一致

编写干净、结构良好的代码和注释,即使在使用 AI 之前。有意义的函数和变量名称、一致的格式和文档字符串不仅使你的代码让其他人更容易理解,也为 AI 提供了更强的线索。

调试代码的提示词模式

调试是 AI 助手的拿手项,问题越清晰、越具体,AI 定位 bug 并给出解决方案的准确性就越高。

1. 清晰描述问题和症状

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

2. 对复杂 bug 使用逐步或逐行方法

逐行浏览这个函数并跟踪每一步的 total 值。它没有正确累加 - 逻辑在哪里出错了?

3. 尽可能提供最小可复现示例

这里有一个简化示例仍然触发错误[包含代码片段]。为什么会出现这个错误?

4. 提出有针对性的问题和后续问题

这个解释有道理。你能告诉我如何修复代码吗?请提供修正后的代码。

调试示例:糟糕 vs 改进的提示词

比如说我们有一个简单的 Node.js 函数,用于将用户对象列表转换为按用户 ID 查找的映射。但是这段代码有 bug,它抛出了一个错误:

// Buggy function: converts array of users to a map by ID
function mapUsersById(users) {
  const userMap = {};
  for (let i = 0; i <= users.length; i++) {
    // Note: using <= will go out of bounds on the last iteration
    const user = users[i];
    userMap[user.id] = user;
  }
  return userMap;
}

// Example usage:
const result = mapUsersById([{ id: 1, name: "Alice" }]);

糟糕的提示词

为什么我的 mapUsersById 函数不工作?

AI 对糟糕提示词的回应:

你确定函数被正确的用户数组调用了吗?也许问题是 users 为空或不是数组,导致循环无法正确执行。也可能是在 userMap 上设置属性时出错。确保每个用户都有 id 属性。如果 users 是 undefined 或 null,那也会导致它不工作。

分析: 这个答案相当模糊。提示词除了函数名之外没有给出任何细节,所以 AI 只能猜测一般原因。它没有识别出实际的 bug。

改进的提示词

我有一个 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;
}

它应该返回 { "1": {id: 1, name: "Alice"} }。bug 是什么,我该如何修复它?

AI 对改进提示词的回应:

错误 Cannot read property ‘id’ of undefined 表明在某个时候,user 是 undefined,然后你试图访问 user.id。查看你的循环,问题出在循环边界上。你使用的是 i <= users.length。如果 users.length 是 1(数组中有一个用户),循环会在 i = 0i = 1 时运行。当 i 是 1 时,users[1] 是 undefined(因为数组中唯一的索引是 0)。因此在那次迭代中 user 变成 undefined,user.id 抛出 TypeError。修复方法是将循环条件改为 i < users.length

分析: 这个回应直接指出了 bug(在循环中使用 <=)并解释了为什么它会导致 user 为 undefined。然后提供了正确的代码解决方案。

当下流行技术栈的调试场景

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"

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

状态架构管理

糟糕的提示词

为我的 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 和 Server Components
- TypeScript strict mode
- 服务端数据获取用于 SEO
- 客户端交互用于购物车/用户操作
- 状态应在导航间持久化

我应该使用:
1. 每个域的 Zustand stores(cart、auth、notifications)
2. React Query/TanStack Query 用于服务器状态 + Zustand 用于客户端状态
3. 带有切片的单个 Zustand store

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

重构和优化的提示词模式

1. 明确重构目标

重构以下函数以提高可读性和可维护性(减少重复,使用更清晰的变量名)。

2. 提供必要的代码上下文

我有一个作为类编写的 React 组件。请将其重构为使用 Hooks 的函数组件。

3. 鼓励代码变更的解释

请建议代码的重构版本,并解释你做的改进。

4. 使用角色扮演设定高标准

扮演一个经验丰富的 TypeScript 专家,重构这段代码以符合最佳实践和现代标准。

重构示例:糟糕 vs 改进的提示词

假设我们有一个函数,要进行两次数据库调用并进行一些处理。代码能跑,但不够优雅:代码重复、难以阅读。我们想重构,以提高清晰度和效率。

// Original function: Fetches two lists and processes them (needs refactoring)
async function getCombinedData(apiClient) {
  // Fetch list of users
  const usersResponse = await apiClient.fetch("/users");
  if (!usersResponse.ok) {
    throw new Error("Failed to fetch users");
  }
  const users = await usersResponse.json();
  // Fetch list of orders
  const ordersResponse = await apiClient.fetch("/orders");
  if (!ordersResponse.ok) {
    throw new Error("Failed to fetch orders");
  }
  const orders = await ordersResponse.json();
  // Combine data (match user to orders)
  const result = [];
  for (let user of users) {
    const userOrders = orders.filter((o) => o.userId === user.id);
    result.push({ user, orders: userOrders });
  }
  return result;
}

糟糕的提示词

重构上面的 getCombinedData 函数。

改进的提示词

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

实现新功能的提示词模式

1. 从高级指令开始,然后深入细节

概述一个计划,在我的 React 应用中添加一个按名称过滤产品列表的搜索功能。产品是从 API 获取的。

2. 提供相关上下文或参考代码

这是一个现有的 UserList 组件(代码...)。现在创建一个类似的 ProductList 组件,但包含一个搜索栏。

3. 使用注释和 TODO 作为内联提示

// TODO: 验证请求负载(确保提供名称和电子邮件)然后开始下一行

4. 提供预期输入/输出或使用示例

实现一个 JavaScript 函数 formatPrice(amount),它接受一个数字(如 2.5)并返回格式化为 USD 的字符串(如 $2.50)。例如,formatPrice(2.5) 应该返回 '$2.50'。

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

假设我们有一个 React 应用程序,我们想添加一个新组件:带有搜索过滤器和排序下拉菜单的产品列表。

初始提示词(功能请求):

创建一个名为 ProductList 的 React 函数组件,显示产品列表并包含一个文本输入来按名称过滤产品。组件应该:
- 从 /api/products 获取产品数组(假设它返回 {id, name, ...} 的 JSON 数组)
- 将它们存储在状态中
- 允许用户在搜索框中输入以按名称过滤显示的产品(不区分大小写匹配)
- 在简单的 <ul> 中显示过滤后的列表,包含产品名称
- 包含基本错误处理(例如,如果 API 调用失败则显示错误消息)
- 获取时的加载状态

AI 可能会产生类似这样的组件:

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);
  };

  // Filter products by search query (case-insensitive)
  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>
  );
}

export default ProductList;

然后你可以迭代改进:

很好,现在添加一个下拉菜单来按名称排序产品(A-Z 或 Z-A)。下拉菜单应该让用户选择升序或降序,列表应该在过滤的基础上相应排序。

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

反模式 1:提示词模糊

  • 问题: “它不工作,请修复它” 或 “写一些做 X 的东西”
  • 解决方案: 添加上下文和具体细节

反模式 2:提示词过载

  • 问题: “生成一个完整的 Node.js 应用,包含认证、React 前端和部署脚本”
  • 解决方案: 拆分任务,一次做一件事。欲速则不达。

反模式 3:缺少问题

  • 问题: 只是转发大量代码而没有明确提出要求
  • 解决方案: 始终包括明确的请求,如”识别上述代码中的任何 bug”

反模式 4:标准模糊

  • 问题: “让这个函数更快”
  • 解决方案: 量化改进:“优化这个函数在线性时间内运行(当前版本是二次的)“

反模式 5:忽略 AI 的澄清

  • 问题: 忽略 AI 的问题或假设
  • 解决方案: 回答 AI 的问题或完善你的提示词

反模式 6:风格不一致

  • 问题: 在指令中混合不同格式
  • 解决方案: 保持一致的风格

反模式 7:引用模糊

  • 问题: “上面的代码”
  • 解决方案: 明确引用代码或重新引用

高级提示词技巧

1. 文档驱动开发

/**
 * 返回第 n 个斐波那契数。
 * @param {number} n - 斐波那契序列中的位置(从 0 开始索引)。
 * @returns {number} 第 n 个斐波那契数。
 *
 * 示例:fibonacci(5) -> 5(序列:0,1,1,2,3,5,…)
 */
function fibonacci(n) {
  // ... 实现
}

2. 边缘情况处理

这个功能应该考虑哪些边缘情况(你能在代码中处理它们吗)?

3. 让 AI 创建测试用例

你能提供几个可能破坏这个函数的测试用例(输入)吗?

4. 代码审查角色扮演

扮演代码审查员。这是一个没有按预期工作的代码片段。审查它并指出任何可能导致问题的错误或不良实践:[代码]

结论

提示词工程既是一门艺术也是一门科学。编写清晰、上下文丰富的提示词,则能让 AI 最大程度地发挥作用。