Skip to content

重构与解释技巧

让 AI 帮你解释代码和重构代码,是 Vibe Coding 里很实用的一步。但这两个任务很容易被问坏:你问“解释一下这段代码”,它可能逐行翻译;你说“帮我重构”,它可能顺手改掉接口、目录和测试。

比较稳的做法是先让 AI 解释“意图和风险”,再让它按你指定的目标重构。不要一上来就让它大改。

解释代码时,不要只要逐行说明

逐行解释适合学习语法,但对理解项目帮助有限。你真正需要知道的是:这段代码为什么存在、输入输出是什么、依赖了哪些外部状态、改它会影响哪里。

比如你接手一个订单计算函数,可以这样问:

text
@src/modules/order/calculateTotal.ts

请帮我解释这个文件。
我不需要逐行翻译,请重点说明:
- 这个函数在业务流程里负责什么
- 输入数据有哪些假设
- 哪些分支会影响最终金额
- 如果我要加优惠券逻辑,最可能改哪里

这种问法会把 AI 从“代码讲解器”拉回“项目理解助手”。如果它开始泛泛总结,你可以继续追问:

text
请结合具体函数名和字段名说明,不要只说“处理订单数据”。

解释代码时还有一个小技巧:让 AI 先画调用关系,再解释细节。

text
先列出这个文件被谁调用、它又调用了哪些函数。
然后再解释主流程。

如果项目里有测试,顺手把测试文件也给它。测试往往比实现更能说明真实意图。

重构前先限定目标

“帮我重构一下”太宽了。AI 会自己决定什么叫重构,结果可能是拆函数、改命名、换库、调整目录一起上。你可能只想让代码可读一点,它却把行为也改了。

重构提示词里至少说清楚四件事:

  • 当前问题是什么
  • 这次重构的目标是什么
  • 哪些行为必须保持不变
  • 哪些文件或接口不能动

比如:

text
@src/modules/order/createOrder.ts

这个函数能跑,但现在太难读。

这次重构目标:
- 把参数校验、库存检查、订单创建拆成更清楚的小函数
- 保持 createOrder 的函数签名不变
- 不改变数据库表结构
- 不改路由层和前端调用

请先给重构计划,不要直接修改。

先要计划,是为了防止 AI 把“可读性重构”做成“架构迁移”。计划里如果出现你没要求的动作,比如“新增 repository 层”“改 API 返回格式”,要先删掉再让它执行。

保留行为比改漂亮更重要

重构不是重写。尤其是正在跑的产品代码,最重要的是行为不变。

你可以明确要求 AI 用这些方式约束自己:

text
重构时请遵守:
- 外部调用方式不变
- 错误信息不变
- 返回字段不变
- 如果需要改测试,只能补充覆盖,不要降低原有断言

如果原本没有测试,也可以让 AI 先补“锁行为”的测试,再重构:

text
请先根据当前实现补 3 个测试,用来固定现有行为。
测试通过后,再重构实现代码。

这一步看起来慢,但它能帮你判断 AI 有没有偷偷改业务逻辑。对于金额计算、权限判断、状态流转这类代码,我会优先这样做。

一个完整示例对话

假设你有一个“能跑但难读”的函数:

ts
export async function createOrder(input, user) {
  if (!user) throw new Error("unauthorized");
  if (!input.items || input.items.length === 0) throw new Error("empty order");

  let total = 0;
  for (const item of input.items) {
    const product = await db.product.findUnique({ where: { id: item.productId } });
    if (!product) throw new Error("product not found");
    if (product.stock < item.quantity) throw new Error("stock not enough");
    total += product.price * item.quantity;
  }

  const order = await db.order.create({
    data: {
      userId: user.id,
      total,
      status: "pending",
      items: {
        create: input.items.map((item) => ({
          productId: item.productId,
          quantity: item.quantity,
        })),
      },
    },
  });

  return order;
}

第一轮先解释:

text
@src/modules/order/createOrder.ts

请解释 createOrder 的业务流程。
不要逐行翻译,重点说:
- 它做了哪些校验
- 哪些地方访问数据库
- 哪些错误会抛给调用方
- 哪些行为在重构时必须保持不变

等 AI 解释完,再给重构目标:

text
现在请重构 createOrder。

目标:
- 拆出 validateOrderInput、loadAndValidateProducts、calculateTotal 三个辅助函数
- 保持 createOrder(input, user) 的签名不变
- 保持现有错误信息不变
- 不改数据库调用方式
- 不新增依赖

请只修改 @src/modules/order/createOrder.ts。

如果它提出要顺手改返回结构,你要立刻收窄:

text
不要改返回结构,也不要新增 DTO。
这次只做函数拆分和命名整理。

最后再让它解释 diff:

text
请检查这次 diff:
- 行为是否保持不变
- 有没有新增未要求的抽象
- 哪些地方需要我手动重点 review

这一轮对话的关键不是提示词写得多,而是每一步都只推进一个目标。解释先服务于理解,重构只服务于指定问题,检查只服务于确认风险。

什么时候不要让 AI 直接重构

如果你还不理解这段代码在系统里的角色,不要直接让 AI 改。先解释,再找调用方,再决定是否动手。

下面几类代码尤其要谨慎:

  • 支付、退款、金额计算
  • 鉴权、权限、租户隔离
  • 数据迁移和批处理脚本
  • 线上兼容逻辑

这些地方不是不能用 AI,而是不能只靠“看起来更清爽”来判断重构成功。你需要测试、日志或明确的输入输出样例来兜住行为。

下一篇 给 AI 提供项目上下文 会继续讲:当项目变大时,怎么让 AI 更快理解你的目录、约定和边界。

重构与解释技巧

让 AI 解释代码和让 AI 重构代码,是两类看起来相似、实际要求不同的任务。解释代码时,你要让它帮你建立理解;重构代码时,你要让它在不破坏行为的前提下改变结构。

这两件事都不能只说“解释一下”或“优化一下”。前者容易得到逐行翻译,后者容易得到一堆自作主张的改动。

让 AI 解释代码,不要只要翻译

你看到一段陌生代码时,第一反应可能是:

text
解释一下这段代码。

AI 会照着代码从上到下讲一遍,但你读完还是不一定知道它为什么这么写。更好的问法是让它围绕你的目的解释:

text
@src/cache/sessionStore.ts

我第一次看这个文件。
请帮我解释:
- 这个模块负责什么
- 外部通常怎么调用它
- 哪些状态会被持久化
- 如果我要改登录过期时间,应该看哪里

不要逐行翻译,先讲整体流程。

解释代码时,最有价值的不是“第 12 行定义了一个变量”,而是它在系统里的位置。你可以要求 AI 画出调用关系、数据流,或者指出改动入口。

如果代码很长,可以先让它回答两个问题:

text
先不要重构。
请只回答:
1. 这个文件对外暴露了哪些能力?
2. 哪几段代码最容易影响用户可见行为?

这能避免它一上来就建议改代码。

重构前先说清楚目标

重构不是“把代码变好看”。你需要告诉 AI,当前代码哪里让你不舒服,以及这次重构要解决什么。

常见目标可以很具体:

  • 减少重复逻辑
  • 拆出纯函数,方便测试
  • 把 UI 和数据请求分开
  • 降低单个函数的长度
  • 保持行为不变,只改善命名和结构

如果目标没说清楚,AI 会自己决定什么叫“更好”。它可能换库、改组件层级、调整接口返回格式,最后代码看起来变整齐了,但风险更高。

一个更可控的提示词是:

text
@src/components/CheckoutForm.tsx

请重构这个组件。

当前问题:
- 表单校验、价格计算、提交请求都写在同一个组件里
- handleSubmit 太长,很难测试

重构目标:
- 把价格计算抽成纯函数
- 把请求提交保留在当前组件里
- UI 展示不要变化
- props 和对外行为保持兼容

请先给出修改计划,等我确认后再改。

“先给计划,再改代码”很适合风险高的重构。你可以在计划阶段发现它是否准备越界。

要明确保留什么不动

AI 重构时最容易踩的坑,是把你不想动的东西一起动了。不要只写“不要大改”,这句话太模糊。

可以直接列出边界:

text
不要改:
- 路由路径
- API 请求参数
- 组件 props
- CSS class 名
- 已有测试文件的测试意图

如果你正在维护一个线上项目,兼容性边界尤其重要。比如组件 props 已经被多个页面使用,重构内部实现可以,改 props 就会牵一发而动全身。

一个完整示例对话

下面是一个“能跑但难读”的函数。你可以把它交给 AI,但不要只说“帮我优化”。

ts
async function submitOrder(cart, user, coupon) {
  let total = 0
  for (const item of cart.items) {
    total += item.price * item.count
  }
  if (coupon && coupon.type === "percent") {
    total = total * (1 - coupon.value / 100)
  }
  if (coupon && coupon.type === "fixed") {
    total = total - coupon.value
  }
  if (total < 0) total = 0
  if (!user.address) {
    throw new Error("address required")
  }
  const order = await db.order.create({
    data: {
      userId: user.id,
      total,
      address: user.address,
      items: cart.items,
    },
  })
  await email.send(user.email, "order created")
  return order
}

可以这样对话:

text
请帮我重构这段 submitOrder 代码。

目标:
- 行为保持不变
- 把金额计算抽成一个纯函数,方便单元测试
- submitOrder 里保留数据库写入和发邮件流程
- 不要引入新库

请先解释这段代码现在做了什么,再给出重构后的代码。

如果 AI 给出的版本顺手改了优惠券逻辑,你可以继续追问:

text
你刚才把 fixed coupon 的处理顺序改了。
这次要求行为完全一致,请恢复原来的计算顺序,只做函数拆分。

重构对话通常需要一两轮。第一轮看它是否理解行为,第二轮再让它收窄改动。不要急着接受一个“看起来更优雅”的结果,先确认输入输出和边界有没有变。

解释和重构可以分开做

如果你对代码还没理解,先让 AI 解释,不要马上让它改。等你知道哪些行为不能动,再开重构任务。

在大项目里,我更建议这样走:

text
第一步:解释这个模块的职责和调用关系,不改代码。
第二步:指出最适合重构的两个位置,说明原因。
第三步:只重构其中一个位置,并保持测试通过。

把理解、判断、修改分开,Cursor 的结果会更像一个谨慎的同事,而不是一个拿到文件就开始整理的人。

面向开发者的 AI 实战路线——Vibe Coding 与 AI 应用开发