如何大规模评估AI生成的代码

每个开发者最近都经历过这样的时刻。你让 AI 写一个函数,它看起来是对的,你发布了它,三天后生产环境出了你完全没想到的问题。代码不完全错——但它也不

我在这个等式的另一端花了职业生涯中很大一部分时间。大规模审查 AI 生成的代码。不是瞥一眼,而是深入评估——测试它、破坏它、理解模型为什么做出了那些选择,并找出如何让它做得更好。

以下是我真正学到的东西。

1、大多数人首先搞错的地方

当人们考虑审查 AI 生成的代码时,他们想到的是语法。它能运行吗?它会抛出错误吗?这些只是入场券。一个调优良好的 LLM 几乎每次都能生成语法正确的代码。

真正的问题更深。它们存在于:

  • 模型没有考虑的边缘情况
  • 小规模下看起来不错但负载下崩溃的性能
  • 微妙错误的安全假设
  • 技术上正确但在架构上是倒退的逻辑

模型不笨。它在从数百万个示例中进行模式匹配。但它不理解你的系统。它不知道你的数据库有 5000 万行。它不知道这个端点在高峰时每秒被击中 10,000 次。你知道这些。这就是你审查 AI 代码时需要弥合的差距。

2、我的实际审查框架

在审查了大量 AI 生成的代码后,我形成了一个一致的心智框架。我每次都会按以下层次进行。

第 1 层:它真的解决了问题吗?

这听起来很明显,但出奇地容易错过。LLM 擅长解决所述的问题,但所述的问题往往与实际问题略有不同。

如果提示词说"写一个获取所有用户的函数",模型可能会写:

async function getAllUsers() {
  return await User.find({});
}

这行得通。但在真实系统中,你几乎从不想在无分页、无过滤、无限制的情况下获取所有用户。模型解决了字面问题而忽略了实际问题。

我总是问:如果这个现在就在生产环境运行,实际上会发生什么?

第 2 层:错误处理

这是 AI 生成的代码 consistently 不足的地方。模型倾向于漂亮地编写快乐路径,将错误处理视为事后补充。

看看这种模式——它不断出现:

async function getUserData(userId) {
  const user = await User.findById(userId);
  return user.profile; // 💥 如果 user 是 null 怎么办?
}

模型知道这种模式存在。它见过数千次。但它经常跳过 null 检查,因为在训练示例中,数据总是存在的。

你想看到的是:

async function getUserData(userId) {
  if (!userId) throw new Error('userId is required');
  
  const user = await User.findById(userId);
  if (!user) throw new Error(`User not found: ${userId}`);
  
  return user.profile;
}

我每次都会标记缺失的 null 检查、缺失的 try/catch 块和被吞掉的错误(空的 catch 块悄悄吃掉异常)。

第 3 层:性能假设

AI 生成的代码几乎总是为单个请求、单个用户、小数据集编写的。它很少考虑规模。

我关注常见模式:

N+1 查询——模型在循环内查询:

// 模型写的
const orders = await Order.find({ userId });
for (const order of orders) {
  order.product = await Product.findById(order.productId); // N 次查询!
}
// 应该是
const orders = await Order.find({ userId }).populate('productId');

无分页——从集合中获取无限记录。

异步循环中的同步操作——在 forEach 中使用 await,这不会按预期工作。

缺失索引——模型生成一个将进行全集合扫描的查询,因为它不知道你的数据库模式。

第 4 层:安全

这是最让我紧张的。AI 生成的代码可能引入在快速审查中容易遗漏的微妙安全问题。

我特别关注:

输入清理——用户输入是否直接用于查询或文件路径?

// 危险 - 模型经常生成这个
const filePath = `./uploads/${req.params.filename}`;
fs.readFile(filePath, ...);
// 安全版本
const filename = path.basename(req.params.filename); // 去除路径遍历
const filePath = path.join('./uploads', filename);

暴露的敏感数据——模型有时返回整个数据库对象,包括密码、token 或内部 ID 等字段。

缺失的身份验证检查——模型编写了逻辑但忘记了中间件。

第 5 层:它适合代码库吗?

这是审查中最人性化的层次。代码可能在技术上正确、安全且高性能——但它是否符合它要进入的代码库的模式?

  • 它是否使用了与应用其余部分相同的错误处理模式?
  • 它是否遵循命名约定?
  • 它是否使用了已经存在的共享工具,还是重新实现了它们?

AI 没有关于你代码库的上下文,除非你明确给它。在"正确的代码"和"属于这里的代码"之间的差距,每次都是一个人为判断。

3、告诉你模型正在挣扎的模式

在审查了足够的 AI 生成代码后,你开始识别出迹象。那些表明模型不确定或在其能力边缘工作的模式。

过度注释——当模型在每一行都写注释时,通常意味着它生成的解释多于逻辑。注释分散了对实际问题的注意力。

不一致的变量命名——在一个函数内,变量名风格发生变化。userData 变成 user_data 变成 data。模型在拼接不同训练示例中的模式。

占位符逻辑——在看起来完整的代码中出现 // TODO: handle this case。模型识别了一个空白但没有填补它。

过度防御性检查——有时模型会连续五次检查 if (array && array.length > 0)。它很谨慎,但冗余表明它对流程不完全自信。

4、是什么让好的 AI 生成代码真正好

当模型做对时,代码通常有一种真正令人印象深刻的清晰度。我审查过的最好的 AI 生成代码往往是:

  • 干净可读——结构良好、合理的变量名、逻辑流畅
  • 模块化——分解为小的、可组合的函数,而不是一个巨大的代码块
  • 一致——始终使用相同的模式,而不是混合方法

模型读过大量写得好的代码。当提示词清晰、问题定义明确时,它可以生成在结构上确实比一般开发者在截止日期压力下写得更好的代码。

技能在于知道什么时候信任它,什么时候推回去。

5、审查 AI 生成代码的实用建议

1. 始终运行它,不要只是阅读它。 AI 代码经常看起来正确但在边缘输入上崩溃。用空值、null、非常大的数字、意外的类型来测试它。

2. 审查提示词,而不仅仅是输出。 模糊的提示词产生模糊的代码。如果输出是错的,修复通常在提示词中,而不是在修补生成的代码中。

3. 检查假设。 每一段 AI 代码都带有关于输入、数据大小和环境的隐含假设。让这些假设变得明确。

4. 不要因为它看起来精致就橡皮图章式通过。 这是陷阱。格式良好、注释良好的代码仍然可能微妙地错误。呈现不是质量信号。

5. 将 AI 输出作为初稿,而不是最终产品。 模型在快速达到 70% 方面确实有用。达到 100% 仍然是人类的工作。

6、结束语

大规模使用 AI 生成代码让我成为了一个更好的代码审查者。它迫使我变得更加系统化,更明确地表达好的代码实际上是什么样子,并更意识到"技术上正确"和"生产就绪"之间的差距。

模型正在快速变好。但判断层——理解上下文、预见失败模式、做出需要了解你的系统的决策的能力——仍然很大程度上是人类的。

至少目前如此。


原文链接:How I Evaluate LLM Code Quality: Reviewing AI-Generated Code at Scale

汇智网翻译整理,转载请标明出处