一、什么是遗留代码
遗留代码(Legacy Code)不等于旧代码。Michael Feathers 在《修改代码的艺术》里给出了一个精准定义:
遗留代码 = 没有测试的代码
更宽泛的定义:难以理解、难以修改、不敢动的代码。
常见特征:
- 函数很长,一个方法做了很多事情
- 命名模糊,看不出变量/函数的用途
- 大量
any类型,类型信息丢失 - 幻数(Magic Number),代码里出现没有解释的数字
- 重复代码,同一段逻辑在多处出现
- 没有错误处理,或者错误处理方式不对
- 没有测试,改了不知道会不会破坏其他功能
二、代码坏味道(Code Smell)完整列表
Martin Fowler 在《重构》里总结了 20+ 种代码坏味道,以下是后端开发最常见的几种:
2.1 函数过长(Long Method)
表现:一个函数超过 30 行,做了多件事情
危害:难以理解,难以测试,改一处容易影响其他逻辑
重构方向:提取方法(Extract Method),把不同职责的逻辑拆成独立的私有方法
// 重构前:processOrder 方法做了 5 件事
async processOrder(data: any) {
// 计算折扣(30 行)
// 验证用户(20 行)
// 创建数据库记录(10 行)
// 发送通知(15 行)
// 更新库存(10 行)
}
// 重构后:每件事是独立的私有方法
async processOrder(data: CreateOrderDto) {
const discount = this.calculateDiscount(data.amount);
const user = await this.validateUser(data.userId);
const order = await this.createOrderRecord(data, discount);
await this.sendNotification(user, order);
await this.updateStock(data.items);
return order;
}
2.2 幻数(Magic Number)
表现:代码里出现没有命名的数字或字符串
危害:6 个月后完全不知道这个数字从哪来的、为什么是这个值
// 重构前
if (data.amount > 10000) {
discount = data.amount * 0.1
} else if (data.amount > 5000) {
discount = data.amount * 0.05
}
// 重构后
const DISCOUNT_RULES = {
HIGH_THRESHOLD: 10000,
MID_THRESHOLD: 5000,
HIGH_RATE: 0.1,
MID_RATE: 0.05,
} as const
if (data.amount > DISCOUNT_RULES.HIGH_THRESHOLD) {
discount = data.amount * DISCOUNT_RULES.HIGH_RATE
}
2.3 重复代码(Duplicated Code)
表现:同一段逻辑在多个地方出现
危害:需要修改时要改多处,容易漏改
// 重构前:两个方法里都有相同的用户验证逻辑
async processOrder(data) {
const user = await this.prisma.user.findUnique({ where: { id: data.userId } });
if (!user) throw new NotFoundException('用户不存在');
if (!user.isActive) throw new ForbiddenException('用户已被禁用');
// ...
}
async cancelOrder(data) {
const user = await this.prisma.user.findUnique({ where: { id: data.userId } });
if (!user) throw new NotFoundException('用户不存在');
if (!user.isActive) throw new ForbiddenException('用户已被禁用');
// ...
}
// 重构后:提取为私有方法
private async validateUser(userId: number): Promise<User> {
const user = await this.prisma.user.findUnique({ where: { id: userId } });
if (!user) throw new NotFoundException('用户不存在');
if (!user.isActive) throw new ForbiddenException('用户已被禁用');
return user;
}
2.4 any 类型滥用
表现:大量使用 any 类型,放弃了 TypeScript 的类型保护
危害:类型错误在运行时才发现,IDE 无法提供代码提示
// 重构前
async create(data: any) {
const result: any = await this.prisma.user.create({ data });
return result;
}
// 重构后
async create(createUserDto: CreateUserDto): Promise<Omit<User, 'password'>> {
const { password, ...rest } = createUserDto;
const hashedPassword = await bcrypt.hash(password, 10);
const user = await this.prisma.user.create({
data: { ...rest, password: hashedPassword },
select: { id: true, username: true, email: true, createdAt: true },
});
return user;
}
2.5 不恰当的异常处理
表现:
- 用
throw new Error(...)代替 NestJS 异常类 - 空的 catch 块(
catch(e) {}) - 捕获了异常但没有处理
// 重构前
if (!user) {
throw new Error('user not found') // 会变成 500,而不是 404
}
// 重构后
if (!user) {
throw new NotFoundException('用户不存在') // 正确返回 404
}
2.6 副作用(Side Effects)
表现:函数修改了传入的参数,调用方不知道参数被改了
// 重构前(直接修改入参,有副作用)
async processOrder(data: any) {
data.discount = this.calculateDiscount(data.amount); // 修改了 data
data.finalAmount = data.amount - data.discount; // 修改了 data
data.status = 'PENDING'; // 修改了 data
return await this.prisma.order.create({ data });
}
// 重构后(用新对象,无副作用)
async processOrder(createOrderDto: CreateOrderDto) {
const discount = this.calculateDiscount(createOrderDto.amount);
const orderData = {
...createOrderDto,
discount,
finalAmount: createOrderDto.amount - discount,
status: OrderStatus.PENDING,
};
return await this.prisma.order.create({ data: orderData });
}
三、安全重构的核心原则
原则一:先有测试,再动代码
没有测试就重构 = 在没有安全网的情况下走钢丝。
重构流程:
1. 为现有代码(有坏味道的代码)写测试
2. 确认测试全绿(验证现有行为)
3. 开始重构
4. 每改一步,运行一次测试
5. 测试保持全绿,重构安全
原则二:小步重构,频繁测试
每次只改一个坏味道,改完就跑测试,而不是一次改很多再测试。
原因:如果一次改了 5 个地方,测试失败了,不知道是哪个改动导致的。
原则三:重构不改业务逻辑
重构的定义:在不改变代码外部行为的前提下,改善代码的内部结构。
如果在重构的同时修改了业务逻辑,出了问题分不清是重构导致的还是逻辑变更导致的。
四、演示用的遗留代码
在项目里新建 src/legacy/order-processor.service.ts,写入以下内容:
import { Injectable } from '@nestjs/common'
import { PrismaService } from '../prisma/prisma.service'
// 这是演示用的遗留代码文件,包含多种典型坏味道
@Injectable()
export class OrderProcessorService {
constructor(private p: PrismaService) {} // 坏味道1:单字母变量名
// 坏味道2:函数过长,做了太多事情(超过 50 行)
// 坏味道3:any 类型
// 坏味道4:没有 JSDoc 注释
async processOrder(data: any) {
// 坏味道5:幻数
if (data.amount > 10000) {
data.discount = data.amount * 0.1
} else if (data.amount > 5000) {
data.discount = data.amount * 0.05
} else if (data.amount > 1000) {
data.discount = data.amount * 0.02
} else {
data.discount = 0
}
// 坏味道6:直接修改入参(副作用)
data.finalAmount = data.amount - data.discount
data.status = 'PENDING'
data.orderNo = 'ORD' + Date.now()
// 坏味道7:数据库操作和业务逻辑混在一起
const user = await this.p.user.findUnique({
where: { id: data.userId },
})
// 坏味道8:用 Error 而不是 NestJS 异常类
if (!user) {
throw new Error('user not found')
}
// 坏味道9:重复代码(cancelOrder 里也有这段)
if (!user.isActive) {
throw new Error('user is inactive')
}
const order = await this.p.order.create({ data })
// 坏味道10:console.log 在生产代码里
console.log('order created', order.id)
return order
}
// 坏味道11:和 processOrder 高度重复的用户验证逻辑
async cancelOrder(data: any) {
const user = await this.p.user.findUnique({
where: { id: data.userId },
})
if (!user) {
throw new Error('user not found')
}
if (!user.isActive) {
throw new Error('user is inactive')
}
const order = await this.p.order.findUnique({
where: { id: data.orderId },
})
if (!order) {
throw new Error('order not found')
}
// 坏味道12:幻字符串
if (order.status !== 'PENDING') {
throw new Error('only pending orders can be cancelled')
}
return await this.p.order.update({
where: { id: data.orderId },
data: { status: 'CANCELLED' },
})
}
}
五、演示操作步骤
第一步:创建遗留代码文件
mkdir -p src/legacy
把上面第四章的代码写入 src/legacy/order-processor.service.ts。
第二步:为遗留代码生成测试(安全网)
在 Cursor Chat 里输入:
@src/legacy/order-processor.service.ts
为 OrderProcessorService 生成单元测试文件 src/legacy/order-processor.service.spec.ts。
目的:在重构前先验证当前行为,作为重构的安全网。
覆盖场景:
- processOrder:正常流程(amount = 6000,用户存在且激活)
- processOrder:用户不存在时抛出 Error('user not found')
- processOrder:用户未激活时抛出 Error('user is inactive')
- cancelOrder:正常取消 PENDING 订单
- cancelOrder:订单不是 PENDING 状态时抛出 Error
注意:Mock 要覆盖 this.p(PrismaService),字段是 user 和 order
运行测试,确认全绿:
npm test -- order-processor.service.spec.ts --verbose
必须在测试全绿之后才能开始重构
第三步:AI 分析坏味道
在 Cursor Chat 里输入:
@src/legacy/order-processor.service.ts
分析这个文件里所有的代码坏味道,按以下格式输出:
### [编号]. [坏味道名称]
- 位置:第几行,哪个方法
- 问题描述:具体是什么问题
- 危害:这个问题会带来什么后果
- 重构方向:应该怎么改
按危害程度从高到低排序
记录 AI 给出的坏味道清单,用于后续逐步重构。
第四步:制定重构计划
在 Chat 里输入:
根据上面的坏味道分析,帮我制定重构计划。
要求:
1. 从风险最低的改动开始(不改逻辑,只改形式的改动放最前面)
2. 每步改动范围要小,改完就运行测试
3. 用步骤编号列出,每步说明:改什么、为什么先改这个
AI 给出的计划通常是以下顺序(从低风险到高风险):
第一步:提取常量(幻数 → 有名字的常量)
第二步:修复异常类型(Error → NestJS 异常类)
第三步:重命名变量(单字母 → 有意义的名字)
第四步:提取重复的用户验证逻辑(→ 私有方法)
第五步:拆分 processOrder 函数(→ 多个小方法)
第六步:修复 any 类型(→ 具体的 TypeScript 类型)
第七步:删除 console.log
第五步:执行重构(逐步进行)
重构第一步:提取常量
在 Chat 里输入:
执行重构第一步:提取 processOrder 方法里的所有幻数为命名常量。
在类定义上方创建常量对象:
const DISCOUNT_RULES = {
HIGH_THRESHOLD: 10000,
MID_THRESHOLD: 5000,
LOW_THRESHOLD: 1000,
HIGH_RATE: 0.1,
MID_RATE: 0.05,
LOW_RATE: 0.02,
} as const;
用这些常量替换方法里的幻数。不要改其他任何东西。
改完立即运行测试:
npm test -- order-processor.service.spec.ts --verbose
确认全绿后继续。
重构第二步:修复异常类型
在 Chat 里输入:
执行重构第二步:把所有的 throw new Error(...) 替换成 NestJS 异常类。
替换规则:
- 'user not found' → NotFoundException('用户不存在')
- 'user is inactive' → ForbiddenException('用户已被禁用')
- 'order not found' → NotFoundException('订单不存在')
- 'only pending orders can be cancelled' → BadRequestException('只有待处理的订单可以取消')
记得在文件顶部 import 这些异常类。不要改其他任何东西。
改完运行测试。
注意:这步改了抛出的异常类型,测试文件里的断言也需要更新(原来断言抛 Error,现在要改成断言抛 NotFoundException 等)。
在 Chat 里说:
测试文件里的断言用的是 Error,但代码已经改成了 NestJS 异常类,
请同步更新测试文件里对应的断言
重新运行测试,确认全绿。
重构第三步:重命名变量
在 Chat 里输入:
执行重构第三步:把构造函数里的 private p: PrismaService 改成 private prisma: PrismaService,
并把文件里所有 this.p. 替换为 this.prisma.。不要改其他任何东西。
运行测试,确认全绿。
重构第四步:提取重复的用户验证逻辑
在 Chat 里输入:
执行重构第四步:把 processOrder 和 cancelOrder 里重复的用户验证逻辑提取成私有方法。
私有方法签名:
private async validateUser(userId: number): Promise<User>
方法逻辑:
1. 查找用户
2. 用户不存在抛 NotFoundException
3. 用户未激活抛 ForbiddenException
4. 返回用户对象
然后在 processOrder 和 cancelOrder 里调用这个方法,删除重复代码。
运行测试,确认全绿。
重构第五步:删除 console.log
在 Chat 里输入:
执行重构第五步:删除所有 console.log 语句。
如果需要日志,注入 NestJS Logger 并替换为 this.logger.log。
不要改其他任何东西。
运行测试,确认全绿。
第六步:对比重构前后
查看 git diff,对比重构前后的差异:
git diff src/legacy/order-processor.service.ts
统计变化:
# 重构前行数
git show HEAD:src/legacy/order-processor.service.ts | wc -l
# 重构后行数
wc -l src/legacy/order-processor.service.ts
重构后的代码应该具备:
- 无幻数,所有数值都有名字
- 无重复代码,用户验证逻辑只在一处
- 无
Error,全部是 NestJS 异常类 - 无
console.log - 变量名有意义
测试依然全绿,行为没有改变。
六、重构常见问题
| 问题 | 原因 | 处理方式 |
|---|---|---|
| 重构后测试失败 | 不小心改了业务逻辑 | 用 git diff 找出改了什么,回退那部分改动 |
| 测试本身需要更新 | 异常类型或返回值结构变了 | 同步更新测试文件的断言,这是正常的 |
| AI 一次改了太多地方 | Prompt 没有写"不要改其他任何东西" | 每次重构 Prompt 结尾加"不要改其他任何东西" |
| 不知道从哪里开始 | 没有制定重构计划 | 先让 AI 分析坏味道,再制定计划,按计划执行 |
Spec Coding 实战补充:09 AI 辅助重构遗留代码
来源:
Spec Coding实战/09 AI 辅助重构遗留代码.md,已合并到本章节。
1. 遗留代码的真实面目
"遗留代码"不一定是很久以前写的代码。上个月写的代码,如果没有测试、没有文档、耦合严重,它就已经是遗留代码了。
Michael Feathers 在《修改代码的艺术》里给遗留代码的定义很直接:没有测试的代码。
遗留代码的典型特征:
- 一个函数 200 行,干了十件事
- 数据库查询和业务逻辑混在一起
- 魔法数字满天飞(
if (status === 2),2 是什么没人知道) - 错误处理要么没有,要么是
catch (e) { console.log(e) } - 函数名和它做的事情不一致
- 全局状态,函数互相依赖
AI 在重构遗留代码这件事上非常有用,但有一个前提:你要先让 AI 理解代码,再让它提建议,最后才动手改。 不能直接说"帮我重构这个文件"就完事了。
2. 第一步:理解遗留代码
在改之前,先让 AI 读懂它。
读取 @src/legacy/order.service.ts,告诉我:
1. 这个文件总共做了哪些事情(功能清单)
2. 主要的数据流是什么(数据从哪来,怎么处理,返回什么)
3. 有哪些外部依赖(数据库、第三方服务、全局变量等)
4. 有没有明显的副作用(发邮件、写文件、修改全局状态等)
这一步不是为了让 AI 去改,而是为了建立"共同理解"。如果 AI 对代码的描述和你的预期不符,说明代码本身写得就很模糊,需要先搞清楚它实际在做什么。
3. 第二步:识别坏味道
继续分析 @src/legacy/order.service.ts,识别以下代码坏味道:
1. 过长函数(超过 30 行的函数)
2. 过大的类(承担了超过一个职责的 Service)
3. 重复代码(相似逻辑出现了多次)
4. 魔法数字/魔法字符串(硬编码的数字、状态值)
5. 深层嵌套(超过 3 层的 if/for 嵌套)
6. 注释掉的死代码
7. 不恰当的命名(变量名 a、b、temp 等)
8. 没有错误处理的异步操作
按照严重程度排序,优先列出影响最大的问题。
典型的遗留代码示例
// ❌ 重构前:典型的遗留代码
export class OrderService {
constructor(
private db: any,
private mailer: any
) {}
async processOrder(data: any) {
// 校验
if (!data.userId) throw new Error('no user')
if (!data.items || data.items.length === 0) throw new Error('no items')
let total = 0
for (let i = 0; i < data.items.length; i++) {
const item = await this.db.query(
`SELECT * FROM products WHERE id = ${data.items[i].productId}`
)
if (!item) throw new Error('product not found')
if (item.stock < data.items[i].quantity) throw new Error('no stock')
total += item.price * data.items[i].quantity
}
// 打折
if (total > 1000) {
total = total * 0.9
} else if (total > 500) {
total = total * 0.95
}
// 创建订单
const orderId = Math.random().toString(36).substr(2, 9)
await this.db.query(
`INSERT INTO orders (id, user_id, total, status) VALUES ('${orderId}', '${data.userId}', ${total}, 1)`
)
for (let i = 0; i < data.items.length; i++) {
await this.db.query(
`INSERT INTO order_items (order_id, product_id, quantity) VALUES ('${orderId}', '${data.items[i].productId}', ${data.items[i].quantity})`
)
await this.db.query(
`UPDATE products SET stock = stock - ${data.items[i].quantity} WHERE id = '${data.items[i].productId}'`
)
}
// 发邮件
const user = await this.db.query(
`SELECT * FROM users WHERE id = '${data.userId}'`
)
await this.mailer.send(
user.email,
'Order Confirmed',
`Your order ${orderId} total: ${total}`
)
return { orderId, total }
}
}
这段代码有什么问题:
| 问题类型 | 具体表现 |
|---|---|
| SQL 注入 | 直接拼接用户输入到 SQL 字符串 |
| N+1 查询 | 循环里每次都查数据库 |
| 无事务保护 | 插入订单成功但扣库存失败,数据不一致 |
| 魔法数字 | status = 1是什么状态?折扣 0.9/0.95 是什么规则? |
| 职责混乱 | 一个方法干了:校验、计价、创建订单、扣库存、发邮件 |
| 随机 ID | Math.random()生成的 ID,可能重复 |
| 类型不安全 | 参数是 any,返回也不声明类型 |
4. 第三步:制定重构计划
重构不是一次性的,要分步走。让 AI 帮你制定计划:
基于上面识别的问题,帮我制定一个安全的重构计划。
原则:
1. 每一步都保持代码可运行
2. 优先处理安全风险(SQL 注入)
3. 其次处理数据一致性风险(无事务)
4. 最后做代码结构优化
给出具体的步骤顺序,每步的目标是什么,改完后如何验证没有引入新问题。
典型的重构计划:
Step 1: 消除 SQL 注入(用参数化查询或 ORM 替换拼接 SQL)
验证:接口功能不变,无 SQL 注入漏洞
Step 2: 引入事务(用数据库事务包裹插入订单+扣库存操作)
验证:任一步骤失败时整体回滚,不产生脏数据
Step 3: 消灭魔法数字(提取常量和枚举)
验证:代码逻辑不变,status/折扣规则有明确命名
Step 4: 拆分函数(每个函数只做一件事)
验证:单元测试覆盖每个子函数
Step 5: 加类型声明(消灭 any)
验证:TypeScript 编译通过,无类型错误
5. 第四步:AI 辅助执行重构
重构示例:拆分过长函数
@src/legacy/order.service.ts 的 processOrder 方法承担了太多职责。
帮我按以下方式拆分,不改变业务逻辑,只做结构重组:
1. validateOrderInput(data):校验输入参数
2. calculateOrderItems(items):查询商品信息,返回商品详情+价格小计
(同时返回 insufficientStock 的商品列表)
3. calculateDiscount(subtotal):根据总价计算折扣,返回最终金额
4. createOrderWithItems(userId, items, total):在事务中创建订单+扣库存
5. sendOrderConfirmation(userId, orderId, total):发送确认邮件(异步)
6. processOrder(data):调用上面各步骤,组织完整流程
每个方法要有明确的参数类型和返回类型,不能用 any。
重构后的代码
// ✅ 重构后:清晰的 NestJS Service
import {
Injectable,
BadRequestException,
ConflictException,
} from '@nestjs/common'
import { PrismaService } from '../../prisma/prisma.service'
import { MailService } from '../mail/mail.service'
import { CreateOrderDto, OrderItemDto } from './dto/create-order.dto'
import { BusinessException } from '../../common/exceptions/business.exception'
import { ErrorCode } from '../../common/constants/error-code'
// 折扣规则常量
const DISCOUNT_RULES = [
{ threshold: 1000, rate: 0.9 },
{ threshold: 500, rate: 0.95 },
] as const
// 订单状态枚举
enum OrderStatus {
PENDING = 'PENDING',
PAID = 'PAID',
CANCELLED = 'CANCELLED',
}
interface OrderItemDetail {
productId: string
productName: string
price: number
quantity: number
subtotal: number
}
@Injectable()
export class OrderService {
constructor(
private readonly prisma: PrismaService,
private readonly mailService: MailService
) {}
async createOrder(
dto: CreateOrderDto
): Promise<{ orderId: string; total: number }> {
const itemDetails = await this.resolveOrderItems(dto.items)
const subtotal = itemDetails.reduce(
(sum, item) => sum + item.subtotal,
0
)
const total = this.applyDiscount(subtotal)
const orderId = await this.persistOrder(dto.userId, itemDetails, total)
// 异步发送邮件,不阻塞响应
this.mailService
.sendOrderConfirmation(dto.userId, orderId, total)
.catch((err) => {
// 邮件失败不影响订单创建,只记录日志
this.logger.error(
{ orderId, error: err.message },
'Failed to send order confirmation'
)
})
return { orderId, total }
}
private async resolveOrderItems(
items: OrderItemDto[]
): Promise<OrderItemDetail[]> {
const productIds = items.map((item) => item.productId)
const products = await this.prisma.product.findMany({
where: { id: { in: productIds }, isDeleted: false },
})
const productMap = new Map(products.map((p) => [p.id, p]))
return items.map((item) => {
const product = productMap.get(item.productId)
if (!product) {
throw new BusinessException(ErrorCode.PRODUCT_NOT_FOUND)
}
if (product.stock < item.quantity) {
throw new BusinessException(ErrorCode.INSUFFICIENT_STOCK)
}
return {
productId: item.productId,
productName: product.name,
price: product.price,
quantity: item.quantity,
subtotal: product.price * item.quantity,
}
})
}
private applyDiscount(subtotal: number): number {
const rule = DISCOUNT_RULES.find((r) => subtotal >= r.threshold)
return rule ? subtotal * rule.rate : subtotal
}
private async persistOrder(
userId: string,
items: OrderItemDetail[],
total: number
): Promise<string> {
return this.prisma.$transaction(async (tx) => {
const order = await tx.order.create({
data: {
userId,
total,
status: OrderStatus.PENDING,
items: {
create: items.map((item) => ({
productId: item.productId,
quantity: item.quantity,
price: item.price,
})),
},
},
})
// 批量扣减库存
await Promise.all(
items.map((item) =>
tx.product.update({
where: { id: item.productId },
data: { stock: { decrement: item.quantity } },
})
)
)
return order.id
})
}
}
6. 重构前后对比
| 维度 | 重构前 | 重构后 |
|---|---|---|
| 函数长度 | 50+ 行,一个函数 | 5 个方法,每个 10-20 行 |
| SQL 注入 | 存在(字符串拼接) | 消除(Prisma 参数化) |
| 数据一致性 | 无事务保护 | 事务包裹订单+库存操作 |
| N+1 查询 | 循环查数据库 | 一次批量查询 |
| 类型安全 | any参数 |
完整 DTO + 返回类型 |
| 可测试性 | 极难 Mock | 每个私有方法可独立测试 |
| 可读性 | 需要逐行读懂 | 函数名即文档 |
7. 安全演进:保障回归不出问题
重构遗留代码最大的风险是:改着改着,原来能跑的功能不能跑了。
防止回归的策略
策略一:先加测试,再重构
在重构 processOrder 之前,先帮我为现有的代码写集成测试,
覆盖正常下单流程,用内存数据库模拟,这样重构后可以跑测试验证行为没变。
策略二:小步提交
每完成一个重构步骤就提交一次:
git commit -m "refactor: extract resolveOrderItems from processOrder"
git commit -m "refactor: wrap order creation in transaction"
git commit -m "refactor: replace magic numbers with constants"
这样如果某步出了问题,可以精准回滚,不影响其他改动。
策略三:让 AI 检查重构前后的语义等价性
对比 @src/legacy/order.service.ts(重构前)和
@src/modules/order/order.service.ts(重构后),
确认以下方面的语义是否完全一致:
1. 输入校验的条件
2. 折扣计算规则
3. 数据库操作的内容(哪些表、哪些字段)
4. 邮件发送的时机和参数
找出任何可能的行为差异。
8. 小结
AI 辅助重构遗留代码的正确节奏:
理解代码 → 识别坏味道 → 制定计划 → 小步执行 → 测试验证
不要跳步。特别是"理解代码"这一步,很多开发者会跳过,直接让 AI 重构,结果 AI 改了但逻辑不对,因为连 AI 都没弄清楚原来的代码在干什么。
重构的目的不是让代码"看起来更好",而是让它更容易被修改、更容易被理解、更难产生 bug。AI 是这个过程的加速器,但方向还是你来把握。