接手旧项目时,最麻烦的通常不是代码不能运行,而是:状态值没有含义、判断层层嵌套,改一处又担心影响其他页面。
AI 很适合帮助我们读懂和整理旧代码,但不能让它直接把一大段代码改掉。
这篇文章用一个订单状态展示函数,演示如何安全地完成一次小步重构。核心原则只有一句:
先确认旧代码做了什么,再决定如何改。
一、先看一段难维护的旧代码
下面的函数根据订单状态,决定页面显示什么文字,以及用户可以执行哪些操作:
javascript
function getOrderDisplay(order) {
let text = '';
let canPay = false;
let canCancel = false;
if (order) {
if (order.deleted === 1) {
text = '订单已删除';
} else if (order.status === 0) {
text = '待支付';
canPay = true;
canCancel = true;
} else if (order.status === 1) {
if (order.shippedAt) {
text = '运输中';
} else {
text = '待发货';
}
canCancel = true;
} else if (order.status === 2) {
text = '已完成';
} else if (order.status === 3) {
text = '已取消';
} else {
text = '状态异常';
}
}
return {
text,
canPay,
canCancel
};
}
代码不算很长,但已经有明显的维护问题:
0、1、2、3是魔法数字。- 状态文字和操作权限混在同一个函数里。
status === 1又依赖shippedAt,规则不直观。- 传入空订单时返回空文本,调用方不容易发现问题。
- 后续增加"退款中""已退款"等状态时,条件会继续膨胀。
这时不要先说"帮我优化这段代码"。先把业务规则找出来。
二、重构前,先锁定旧行为
重构的目标不是让代码看起来更漂亮,而是在不改变正确业务行为的前提下,让代码更容易修改。
先根据旧代码整理出当前行为:
| 条件 | 展示文字 | 可支付 | 可取消 |
|---|---|---|---|
deleted === 1 |
订单已删除 | 否 | 否 |
status === 0 |
待支付 | 是 | 是 |
status === 1 且没有 shippedAt |
待发货 | 否 | 是 |
status === 1 且有 shippedAt |
运输中 | 否 | 是 |
status === 2 |
已完成 | 否 | 否 |
status === 3 |
已取消 | 否 | 否 |
| 其他状态 | 状态异常 | 否 | 否 |
这张表非常重要,它不是 AI 猜出来的,而是从现有代码中整理出来的。
如果你不知道某个规则是否正确,例如"运输中的订单为什么还能取消",不要擅自改掉。先向产品、业务同学或旧代码负责人确认。
三、先让 AI 做分析,不直接改代码
可以把代码和已知背景交给 AI:
text
请分析下面的 JavaScript 函数,暂时不要修改代码。
函数用途:根据订单数据返回页面展示文字、是否可以支付、是否可以取消。
请输出:
1. 该函数当前的所有行为,整理成条件表。
2. 代码中难维护的地方。
3. 可能存在歧义、需要人工确认的业务规则。
4. 一个保持现有行为的最小重构方案。
5. 重构前应该补充哪些测试。
要求:
- 不要自行改变订单状态含义。
- 不要增加退款、支付超时等未提供的功能。
- 明确区分"代码事实"和"你的推测"。
代码:
[粘贴旧代码]
这类 Prompt 的关键是:限制 AI 不要擅自补业务。
AI 可能会指出一个值得确认的问题:status === 1 时,订单已经发货却仍然可以取消,是否符合业务规则?
这个问题不能由 AI 决定。假设我们和业务确认后,当前规则确实如此,那么重构就必须保留它。
四、先补测试,再动旧代码
旧代码如果没有测试,直接重构风险很高。
先把上面的行为表变成测试。这里以 Vitest 为例:
javascript
import { describe, expect, it } from 'vitest';
import { getOrderDisplay } from './order-display.js';
describe('getOrderDisplay', () => {
it('未支付订单可以支付和取消', () => {
expect(getOrderDisplay({ deleted: 0, status: 0 })).toEqual({
text: '待支付',
canPay: true,
canCancel: true
});
});
it('已发货订单显示运输中,仍保留当前的取消规则', () => {
expect(getOrderDisplay({
deleted: 0,
status: 1,
shippedAt: '2026-09-01T10:00:00Z'
})).toEqual({
text: '运输中',
canPay: false,
canCancel: true
});
});
it('未知状态显示状态异常', () => {
expect(getOrderDisplay({ deleted: 0, status: 99 })).toEqual({
text: '状态异常',
canPay: false,
canCancel: false
});
});
});
真实项目中,应覆盖行为表里的每一种情况。这样重构后若意外改变已有行为,测试会尽早提醒我们。
五、让 AI 提出最小重构方案
这一步的重点是"最小"。
不要让 AI 一次把状态管理改成复杂的状态机,也不要为了一个函数引入新的库。
可以这样提问:
text
下面是一段已经有测试保护的订单展示逻辑。
请给出一个最小重构方案,目标是:
1. 消除状态魔法数字。
2. 让每个状态的规则更容易阅读。
3. 保持现有测试行为不变。
4. 不引入新依赖。
5. 不修改调用方接口。
请先解释修改点和风险,再给出完整代码。
一个合适的方案通常是:
- 用常量命名订单状态。
- 用
switch明确分支。 - 为每个状态直接返回结果。
- 保留原函数名称和返回结构。
六、重构后的代码
javascript
const ORDER_STATUS = {
PENDING_PAYMENT: 0,
PENDING_SHIPMENT: 1,
COMPLETED: 2,
CANCELED: 3
};
function getOrderDisplay(order) {
const defaultResult = {
text: '状态异常',
canPay: false,
canCancel: false
};
if (!order) {
return defaultResult;
}
if (order.deleted === 1) {
return {
text: '订单已删除',
canPay: false,
canCancel: false
};
}
switch (order.status) {
case ORDER_STATUS.PENDING_PAYMENT:
return {
text: '待支付',
canPay: true,
canCancel: true
};
case ORDER_STATUS.PENDING_SHIPMENT:
return {
text: order.shippedAt ? '运输中' : '待发货',
canPay: false,
canCancel: true
};
case ORDER_STATUS.COMPLETED:
return {
text: '已完成',
canPay: false,
canCancel: false
};
case ORDER_STATUS.CANCELED:
return {
text: '已取消',
canPay: false,
canCancel: false
};
default:
return defaultResult;
}
}
这次重构没有改变外部接口,调用方仍然使用:
javascript
const display = getOrderDisplay(order);
它主要改善了三点:
- 状态值有了业务名称。
- 每个分支都在一个清晰的位置返回结果。
- 新增状态时,不需要在多层
if中寻找插入点。
七、一个容易忽略的行为变化
旧代码传入 null 时,会返回:
javascript
{
text: '',
canPay: false,
canCancel: false
}
重构后传入 null 时,会返回"状态异常"。
这看起来是更合理的结果,但它已经改变了旧行为。
此时必须停下来做决定:
- 如果调用方依赖空文本,就保持旧行为,并写测试锁定它。
- 如果"状态异常"才是正确产品表现,就把它作为明确的需求修改,补充验收和测试。
这正是 AI 重构最容易踩的坑:它可能顺手"修复"一些看似不合理的逻辑,但这些逻辑可能是旧系统的一部分。
为了完全保持旧行为,可以把空订单单独处理:
javascript
if (!order) {
return {
text: '',
canPay: false,
canCancel: false
};
}
重构不是改得越多越好,而是每个行为变化都要有明确依据。
八、重构后怎么验证
完成代码后,至少做四件事:
- 运行已有单元测试,确认旧行为没有被意外改变。
- 手动验证订单列表和订单详情页,确认展示一致。
- 检查本次 diff,确认没有改到无关文件。
- 让 AI 再做一次审查,重点检查行为变化和遗漏测试。
可以使用下面的审查 Prompt:
text
请审查下面这次旧代码重构的 diff。
重构目标:
- 用具名常量替代订单状态魔法数字。
- 保持原函数名称、返回结构和业务行为。
- 不引入新依赖。
请重点检查:
1. 是否有任何行为变化。
2. 是否遗漏订单状态分支。
3. 空订单和未知状态是否有明确处理。
4. 现有测试是否覆盖了本次改动。
5. 是否存在不必要的复杂化。
请按"问题位置、严重程度、影响、建议、验证方式"输出。
不要直接重写代码。
如果 AI 说"逻辑更简洁了",这不是验收结论。真正要看的,是它是否能指出具体分支、具体影响和验证方法。
九、什么时候不适合直接重构
下面几种情况,不建议直接让 AI 修改:
- 不知道函数被哪些页面或服务调用。
- 没有测试,也无法快速补测试。
- 订单、支付、权限等核心规则尚未确认。
- 旧代码涉及数据库迁移或批量数据修改。
- AI 需要读取真实密钥、用户数据或未经授权的公司代码才能理解问题。
这时 AI 仍然可以帮你做"阅读代码、列问题、设计测试"的工作,但修改动作应该更谨慎,并进行人工评审。
十、旧代码重构清单
- 已经确认函数的用途和调用位置。
- 已经整理旧代码的行为表。
- 已经区分代码事实和业务猜测。
- 已经补充正常、边界和异常测试。
- 已经让 AI 先分析,再提出最小方案。
- 已经一次只修改一个清晰问题。
- 已经确认所有行为变化都有需求依据。
- 已经运行测试并检查 diff。
- 没有将敏感数据直接提交给 AI。
总结
AI 可以大幅降低阅读和整理旧代码的成本,尤其适合:
- 提取复杂条件中的业务规则。
- 找出重复代码和魔法数字。
- 提出多种小步重构方案。
- 补充可能遗漏的测试场景。
- 在提交前审查改动范围。
但 AI 不知道你的历史包袱和真实业务规则。
面对难维护的旧代码,正确顺序应该是:
text
读懂旧代码
↓
整理行为表
↓
补测试锁定行为
↓
AI 提出最小重构方案
↓
小步修改并运行验证
↓
审查 diff 后再提交
让 AI 帮你看清旧代码,不要让它在不了解业务时替你决定旧代码该怎么改。
下一篇文章将介绍:
《真实案例:用 AI 快速定位一次代码问题》
✍坚持原创,求关注,点赞,收藏