浏览 AI使用教程
重构前怎样固定现有行为,避免顺手改变业务逻辑
重构的前提是知道旧代码现在做什么。先用特征测试固定关键输入输出,再拆结构;业务规则要不要改,应当作为另一项明确决定。
沿完整工作流推进
前一阶段的可检查结果,是下一阶段的输入;中间证据不足时停在当前阶段。
边重构边修业务逻辑,最后很难分清是哪一步改坏了
准备重构折扣计算,代码没有测试;函数混合会员等级、优惠券和日期规则。开发者一边拆函数一边修“看起来不合理”的结果,业务行为悄悄改变。
这是可执行的教学函数,不是 12 个生产订单的实际运行报告。
- 已知异常也有执行中的断言
- 结构重构未混入业务修复
- 未覆盖行为明确列出
先确认这次任务的起点、边界和交付
| 需要确认 | 本例内容 |
|---|---|
| 现有材料 | Node.js 20+;price.mjs 和 price.test.mjs。冻结普通价、会员折扣及已知负数输入行为,不涉及数据库或外部副作用。 |
| 不能越过的边界 | 先用特征测试固定当前可观察行为;已知异常单独记录,不在重构中顺手修;前后输出逐项比较。 |
| 要交付的结果 | 特征测试与前后差异报告 |
从“界定重构目标”走到“比较前后证据”
| 当前阶段 | 实际处理 |
|---|---|
| 界定重构目标 | 写要改善的结构、可维护性或性能问题,以及明确不改变的外部行为。 |
| 捕获当前行为 | 为关键输入、边界、错误和副作用添加特征测试,哪怕当前结果不理想也先记录。 |
| 建立安全小步 | 一次只移动、重命名或抽取一种结构,保持每步可编译、可测试和可审查。 |
| 分开业务修复 | 发现真实 Bug 时单独记录和提交,先确认期望行为与负责人,不藏在重构中。 |
| 比较前后证据 | 运行相同测试、接口样例和性能基线,审查 diff 是否只涉及目标范围。 |
把负数现状也锁住,不能跳过
在空目录保存以下两个文件,先运行 node --test。这里允许负数是故意保留的旧行为,不是业务建议。
price.mjs
重构前
export function total(cents, member) {
if (member) return Math.round(cents * 0.9);
return cents;
}price.test.mjs
同一组保护测试
import test from 'node:test';
import assert from 'node:assert/strict';
import { total } from './price.mjs';
test('普通价', () => assert.equal(total(100, false), 100));
test('会员舍入', () => assert.equal(total(105, true), 95));
test('已知旧行为 ISSUE-NEGATIVE:负数仍参与折扣', () => {
assert.equal(total(-100, true), -90);
});price.mjs
重构后替换为以下内容,再运行 node --test
const memberPrice = cents => Math.round(cents * 0.9);
export function total(cents, member) {
return member ? memberPrice(cents) : cents;
}预期两版都是 3 条通过。若把负数测试改为 skip,重构引入 0、NaN 或异常都可能漏掉。这个小样没有覆盖类型转换、超大整数和业务税费,不能作为真实价格引擎的完整测试集。
同一异常的两种任务
| 任务 | 输入 -100,true 的断言 | 合并依据 |
|---|---|---|
| 本次结构重构 | 保持 -90,断言实际执行 | 前后可观察结果一致 |
| 后续业务修复 | 业务确认后改为拒绝负数 | 新契约、迁移影响与独立修复记录 |
完成后的特征测试与前后差异报告
预期:重构前后同一组 3 条断言通过,包含当前负数返回 -90 的已知异常。若业务决定以后拒绝负数,在独立修复中修改契约和测试,不能在结构重构时 skip。
为什么“前后结果一致,仍可能遗漏重要行为”还不能交付
前后结果一致,仍可能遗漏重要行为
- 原因
- 样本只来自正常订单
- 怎么改
- 补生产边界、错误和历史回归案例,并记录覆盖不到的外部副作用
