关注公众号

AI干活 / 免费教程

Codex 实战2026-07-0260 分钟

函数太长先别急着拆:先让 Codex 锁住现有行为

最让人犹豫的重构,往往不是你看不懂代码,而是你看懂之后更不敢动。一个函数三百行,里面混着参数归一、权限判断、金额计算、状态转换、兼容旧数据、日志上报和错误提示。你知道它应该被拆开,也知道继续堆下去会越来越难维护。可问题是:现有测试很薄,历史 bug 又不少,函数里还有几段看起来怪但可能是线上补丁...

Codex 实战小步修改与安全编辑AI 工作流可复制模板

适合人群

想清理代码但怕改坏功能的工程师

先解决什么

一个函数过长难维护,但没有足够测试。

学完结果

行为锁定清单和重构步骤。

你会学到什么

让 Codex 提取当前输入输出、补小测试或快照,再分步重构。

准备材料:目标函数、调用样例、历史 bug、现有测试命令。

交付物:行为锁定清单和重构步骤。

边界:区别于功能开发,强调重构前的保护。

教程定位

这篇教程解决什么问题

最让人犹豫的重构,往往不是你看不懂代码,而是你看懂之后更不敢动。一个函数三百行,里面混着参数归一、权限判断、金额计算、状态转换、兼容旧数据、日志上报和错误提示。你知道它应该被拆开,也知道继续堆下去会越来越难维护。可问题是:现有测试很薄,历史 bug 又不少,函数里还有几段看起来怪但可能是线上补丁的分支。这个时候如果直接让 Codex “帮我重构一下”,它可能会把代码拆得很漂亮,也可能顺手改变了某个边界行为。

这篇教程讲一个更稳的做法:在重构之前,先让 Codex 给当前函数加一层“行为锁”。行为锁不是大而全的测试体系,也不是要求你把遗留代码一次性补到完美覆盖率。它是一组足够小、足够贴近当前输入输出的保护动作:列出函数的主要输入形态、输出结果、副作用、错误分支、历史 bug 场景和调用方依赖;把其中最关键的行为变成小测试、快照或记录型断言;再让 Codex 只在这些锁定行为保持不变的前提下分步提取函数。

本文的例子是一个安全虚构的后端函数 `buildInvoicePreview`。它负责根据订单、客户配置、优惠信息和开票规则生成发票预览。函数已经变得很长,维护者想把金额计算、发票抬头、税率选择和异常提示拆出来。但项目只有两条粗略测试,最近还修过一个“折扣后金额为 0 时不应生成负税额”的 bug。你的任务不是让 Codex 开发新功能,而是让它先提取当前行为,再补少量防回归测试,最后给出可分步执行的重构计划。

读完这篇,你会得到一套可以复制的流程:先喂给 Codex 目标函数、调用样例、历史 bug 和现有测试命令;让它输出行为锁定清单;挑出必须固化的行为;让它补小测试或快照;再按“只提取、不改行为”的原则重构。最终产物是一份行为锁定清单和一份重构步骤,而不是一个看起来整洁但无法证明没改坏的 diff。

使用场景

什么情况下最适合用这一套

你是一个想清理代码但怕改坏功能的工程师。你手上有一个函数,名字可能叫 `buildInvoicePreview`、`normalizeCheckoutPayload`、`resolveUserAccess` 或 `prepareSettlementRows`。它一开始只是处理一个简单业务点,后来需求越补越多,变成了所有人都绕着走的“大函数”。每次新需求来了,大家都在末尾加几行判断;每次线上出 bug,又在中间塞一个特殊分支。几年下来,函数还在跑,但已经没人愿意完整解释它。

你打开函数,能看到很多重构冲动:

如果这是新功能代码,你可以先写设计,再让 Codex 按模块实现。但重构旧函数不一样。旧函数最重要的约束是:用户已经在依赖它今天的行为。哪怕某个行为看起来不优雅,只要它是生产事实,重构阶段就不能悄悄改掉。比如金额为 0 时仍要返回一条预览行;企业客户没有税号时要给出可展示错误,而不是抛异常;老订单缺少 `discountLines` 时要当成空数组,而不是让页面报错。

这就是“行为锁”的用处。你先不问 Codex “怎么拆得优雅”,而是让它回答“现在到底表现成什么样”。它要从函数代码、调用方、测试、历史 bug 描述和样例输入里整理一张清单:

有了这张清单,再让 Codex 补最小测试或快照。不是为了追求覆盖率数字,而是为了在重构时有几条会响的线。等这些线挂上去,才进入拆函数。每一步重构都应该能回答:“我只是移动或提取代码,行为锁里的输入输出没有变。”

  1. 参数预处理和主逻辑混在一起,应该提成 `normalizeInput`。
  2. 金额计算和文案生成耦合在同一个循环里,应该拆开。
  3. 历史兼容分支没有注释,看起来像死代码。
  4. 错误返回既有 `throw`,也有 `{ ok: false }`,还夹着日志上报。
  5. 测试只覆盖了最普通的一条路径,边界行为全靠线上经验。
  • 哪些输入形态必须保留支持。
  • 哪些输出字段必须保持名称、顺序、默认值和空值处理。
  • 哪些错误要抛出,哪些错误要返回给调用方展示。
  • 哪些副作用存在,比如日志、埋点、缓存写入或审计记录。
  • 哪些历史 bug 场景不能回退。
  • 哪些行为不确定,需要人工确认后才能锁定。

材料准备

开始前先把材料和边界备齐

开始前准备四类材料。重构前的材料不需要像新需求 PRD 那么完整,但必须贴近当前代码。你给 Codex 的不是“理想行为”,而是“现有行为和已知风险”。

第一类是目标函数。最好给完整函数,至少给函数签名、主要分支、返回结构、错误处理和它依赖的辅助函数。不要只贴几行你觉得难看的代码。大函数的问题常常藏在远处:前面归一化了字段,后面才依赖这个归一结果;中间捕获了异常,末尾又根据错误类型改提示;看起来无用的排序,其实保护了前端快照。

第二类是调用样例。调用方能告诉 Codex 这个函数的真实契约。比如它是在接口 handler 里被 `await` 调用,还是在前端渲染前同步调用;调用方是否依赖返回数组顺序;错误是被全局中间件捕获,还是由页面直接展示;某些参数是否永远来自已经校验过的 DTO。没有调用样例,Codex 容易把函数当成孤立工具函数来整理。

第三类是历史 bug 和边界事故。哪怕只有一句话也有价值,例如“去年修过一次折扣后金额为 0 导致税额为负”“企业客户缺税号时页面要展示错误,不要 500”“旧订单没有 `coupon` 字段,曾经导致发票预览空白”。这些信息能帮助 Codex 分清哪些怪分支可能是业务补丁,不能在重构时当作冗余删除。

第四类是现有测试命令和测试位置。你要告诉 Codex 现在能跑什么:单元测试命令、相关测试文件、是否有快照测试、是否能只跑某个文件、是否有类型检查。重构前补行为锁时,测试要尽量贴近已有框架。项目用 Vitest 就用 Vitest,项目用 Jest 就用 Jest;没有快照习惯就不要硬引入新工具,可以先用普通断言或小的 golden fixture。

还要提前说清楚本轮边界:

适用边界也要放在心里。这个方法适合“想保持行为不变的结构性整理”,不适合趁重构重写业务规则。如果你已经决定改变产品行为,比如改计费规则、改权限模型、改错误提示策略,那它就不再是纯重构,需要按功能开发走需求确认和验收。行为锁保护的是“不要无意改变”,不是替你批准“有意改变”。

  • 先输出行为锁定清单,不直接重构。
  • 只锁定当前行为,不借机设计新行为。
  • 小测试优先覆盖历史 bug、关键输出和调用方依赖。
  • 如果现有行为看起来不合理,先标为“当前行为待确认”,不要直接改。
  • 重构步骤必须小,每一步都能运行同一组行为锁测试。
  • 不新增真实用户数据、真实订单号、真实税号、真实密钥或生产日志。

实操流程

按这套步骤把工作跑起来

第一步,让 Codex 只读目标函数并提取现有行为。提示词里明确写“不要重构、不要改代码”。要求它把行为按输入、输出、错误、副作用、历史 bug 和不确定项分组。输出要具体到字段和条件,比如“当 `discountTotal` 大于 `subtotal` 时,当前函数把应税金额压到 0”,而不是泛泛写“处理折扣”。

第二步,让 Codex 标注证据来源。行为锁不是猜测清单,每一条都应该来自代码、调用方、现有测试或历史 bug 材料。可以要求它在清单里加一列“证据”。例如“代码分支:`if (!customer.taxId && customer.type === 'company')`”“调用方:`InvoicePreviewPanel` 直接展示 `errorMessage`”“历史 bug:金额为 0 时税额不能为负”。证据不够的行为要进入待确认项。

第三步,人工挑选必须锁住的行为。不是清单里每一条都要立刻写测试。大函数可能有几十个分支,你可以先选最有价值的 6 到 10 条:主路径输出、历史 bug、空值兼容、错误策略、顺序依赖和一个复杂折扣场景。行为锁的第一版要小而硬,能为重构提供底线。

第四步,让 Codex 设计最小测试或快照。测试形式跟着项目走。纯函数适合单元测试;输出对象很大但稳定时适合小快照;涉及日志或审计时可以 mock 副作用;调用链太重时可以先测试提取前的顶层函数。不要让 Codex 为了测试方便改业务代码,也不要为了快照方便把大量不稳定字段锁死。时间戳、随机 ID、排序不稳定的数组要先归一或只断言关键字段。

第五步,先补行为锁测试,再跑一次确认它们能通过。测试补完后,重构还没开始。这个阶段的检查很关键:新测试应该在当前代码上通过。如果测试一上来就失败,有两种可能:要么 Codex 理解错了当前行为,要么你发现了真实 bug。前者要修测试理解;后者要停下来决定是否另开修复任务。不要把“修 bug”和“重构”混在同一轮里。

第六步,让 Codex 输出分步重构计划。计划必须基于已经锁住的行为,而不是基于“看起来可以怎么设计”。每一步只能做一种结构整理,例如提取输入归一函数、提取金额计算函数、提取错误消息构造、提取排序规则、删除确认无调用的临时变量。每一步都要写明:改动目的、预期不变的行为锁、需要运行的测试命令、如果测试失败先回退哪一步。

第七步,按步骤重构,每一步都运行行为锁测试。不要让 Codex 一次性完成所有提取。大函数重构最怕 diff 看起来合理,但失败时不知道哪一步改坏了。你可以让它先只提取 `normalizeInvoiceInput`,保持返回结构不变;测试通过后再提取 `calculateTaxableAmount`;再提取 `buildPreviewWarnings`。每一步都应该能单独评审。

第八步,要求 Codex 做重构后对照。让它列出“行为锁清单是否仍满足”“实际改动是否只涉及结构”“有没有改动输出字段、错误类型、排序、副作用”“有没有新增或删除业务分支”。这个对照比一句“测试通过”更有信息量,尤其适合写进 PR 描述。

第九步,人工决定是否清理行为锁。第一轮行为锁通常不应该重构完就删。历史 bug 测试和关键输出测试应该留下来,成为长期回归保护;过于临时的快照可以在确认稳定后缩小或改成更明确的断言。不要把所有锁都当成一次性脚手架,否则下一次重构又会回到裸奔状态。

第十步,把发现的非重构问题单独记录。重构过程中你可能发现某个现有行为很奇怪,比如企业客户缺税号时返回 200,但页面显示错误;或者旧订单缺字段时静默跳过某项金额。这些可能值得改,但不应该混进“保持行为不变”的重构 PR。把它们写成后续任务:现有行为、风险、建议改法、需要谁确认。

输入示例

可以直接参考的输入材料

下面是一份可以交给 Codex 的安全虚构材料。它模拟一个真实工程师会准备的上下文,但不包含真实订单、真实客户、真实税号或真实金额流水。

这份输入有两个关键点。第一,它给的不是理想设计,而是现有行为证据。第二,它把历史 bug 写进材料,让 Codex 知道哪些分支很可能是保护线。你可以少给代码片段,但不能少给调用方式和已知事故;否则 Codex 会更像在做代码美化,而不是受保护的重构。

输入样例示例 1可复制后按自己的场景替换。
任务背景:
我想重构 buildInvoicePreview 这个函数。它现在大约 320 行,负责根据订单、客户配置和优惠信息生成发票预览。函数太长,后续很难维护。但现有测试很少,我不想在重构时改坏行为。

目标:
请先不要重构。先帮我提取当前行为,形成行为锁定清单;再建议需要补哪些小测试或快照;最后给出分步重构计划。

目标函数:
文件:server/invoice/buildInvoicePreview.ts
函数:buildInvoicePreview(order, customer, options)

函数片段:
- 会把缺失的 order.discountLines 当成空数组。
- 会根据 customer.type === "company" 判断是否要求 taxId。
- 当 subtotal - discountTotal 小于 0 时,会把 taxableAmount 压到 0。
- 当 taxableAmount 为 0 时,taxAmount 返回 0。
- 返回 previewLines,warnings,errorMessage,canIssue。
- 对历史订单 source === "legacy_import" 会跳过 couponCode 校验。
- 会调用 logger.warn 记录缺少税号的企业客户。

调用样例:
1. API handler:
   const preview = await buildInvoicePreview(order, customer, { locale: "zh-CN" });
   res.json(preview);

2. 前端 InvoicePreviewPanel:
   - 直接展示 preview.errorMessage。
   - 按 preview.previewLines 的顺序渲染。
   - 当 preview.canIssue === false 时禁用“确认开票”按钮。

历史 bug:
1. 2026-05 修过一次:折扣后金额为 0 时不应出现负税额。
2. 2026-05 修过一次:企业客户缺 taxId 时页面要展示错误,不要接口 500。
3. 2026-06 修过一次:旧订单没有 discountLines 字段时发票预览不能空白。

现有测试:
- server/invoice/buildInvoicePreview.test.ts 只有普通个人客户一条用例。
- 可运行命令:pnpm test server/invoice/buildInvoicePreview.test.ts
- 类型检查:pnpm typecheck

限制:
- 先不要改业务行为。
- 不要引入新测试框架。
- 不要修改调用方。
- 如果发现现有行为不合理,请列入“待确认”,不要直接修。

提示词

可复制使用的提示词

下面这段提示词可以直接复制使用。建议分两轮:第一轮只产出行为锁和重构计划;第二轮在你确认后才允许补测试和重构。

当你确认第一轮行为锁后,可以用第二轮提示词让 Codex 开始动手:

重构阶段再用第三轮提示词:

这三轮提示词的重点是把 Codex 的速度拆开使用。先让它读和归纳,再让它补保护,再让它小步改代码。每一轮都有清楚边界,重构就不容易滑成“顺手重写”。

可复制提示词示例 1可复制后按自己的场景替换。
你现在是“重构前行为锁定助手”,不是功能开发助手。

我有一个过长函数想重构,但现有测试不够。我担心 Codex 直接重构会改变线上行为。请先只读材料,不要改代码。

目标函数:
[粘贴函数路径、函数名、函数签名、关键代码片段或完整函数]

调用样例:
[粘贴 1-3 个真实调用方的脱敏片段,说明调用方如何使用返回值、错误和副作用]

历史 bug 或线上事故:
[粘贴历史 bug、边界事故、曾经修过的特殊分支]

现有测试和命令:
[粘贴相关测试文件、可运行测试命令、类型检查或 lint 命令]

请输出一份“行为锁定清单和重构步骤”,要求:
1. 先列出现有行为,按输入兼容、输出结构、错误处理、副作用、历史 bug、防回归场景、不确定项分组。
2. 每条行为都标注证据来源:代码、调用方、现有测试、历史 bug,或需要人工确认。
3. 从清单里挑出第一轮最该锁住的 6-10 条行为,并说明为什么优先。
4. 建议最小测试或快照,不引入新测试框架,不为了测试方便改业务代码。
5. 区分“应该长期保留的回归测试”和“只适合临时辅助重构的快照”。
6. 给出分步重构计划,每一步只做结构调整,不改变行为。
7. 每一步都写明要运行哪些测试,以及测试失败时应该先检查什么。
8. 如果现有行为看起来不合理,请放进“待确认行为”,不要把它当作本轮修复。

输出格式:
- 行为锁定清单
- 第一轮锁定优先级
- 建议补充的测试或快照
- 分步重构计划
- 待人工确认的问题
- 后续实施提示词
可复制提示词示例 2可复制后按自己的场景替换。
现在只执行第一轮行为锁,不做重构。

请在现有测试框架内,为刚才确认的行为锁补最小测试或快照。限制:
1. 不改 buildInvoicePreview 的业务实现。
2. 不改调用方。
3. 不引入新测试框架。
4. 测试数据必须脱敏,使用 DEMO_ORDER、DEMO_CUSTOMER 这类安全样例。
5. 补完后运行 pnpm test server/invoice/buildInvoicePreview.test.ts。
6. 如果新测试不能在当前代码上通过,先停止并说明是测试理解错误,还是发现了现有 bug。
可复制提示词示例 3可复制后按自己的场景替换。
现在开始受保护重构。只执行重构计划第 1 步:[粘贴第一步名称]。

要求:
1. 只做结构提取,不改变输入输出、错误处理、副作用和排序。
2. 不顺手修待确认行为。
3. 不修改与本步骤无关的文件。
4. 完成后运行行为锁测试和现有测试命令。
5. 输出行为锁对照:哪些锁被测试覆盖,哪些仍需人工复核。

输出样例

AI 应该输出到什么程度

下面是一份使用前面材料后,Codex 可能输出的行为锁定初稿。它不是完整测试代码,而是重构前应该先确认的保护清单。

这份输出有几个好信号。它没有急着拆代码,而是先把“什么不能变”列出来;它把历史 bug 转成防回归测试;它把不确定的兼容分支放到人工确认,而不是顺手删除;它的重构计划每一步都绑定了一组行为锁测试。这样的输出才适合进入下一轮实现。

AI 输出样例示例 1可复制后按自己的场景替换。
行为锁定清单

一、输入兼容
1. order.discountLines 缺失时,当前函数按空数组处理。
   证据:函数片段说明;历史 bug 2026-06。
   锁定建议:补一个旧订单样例,断言 previewLines 仍能生成,且不抛异常。

2. source === "legacy_import" 的订单跳过 couponCode 校验。
   证据:函数片段说明。
   锁定建议:先标为待确认。该行为可能是历史兼容规则,但需要确认是否仍然有效。

二、输出结构
3. 返回对象包含 previewLines、warnings、errorMessage、canIssue。
   证据:函数片段说明;前端 InvoicePreviewPanel 直接使用这些字段。
   锁定建议:补主路径测试,断言字段存在,previewLines 顺序稳定。

4. previewLines 的顺序会被前端直接渲染。
   证据:调用样例 2。
   锁定建议:不要在重构中改排序。测试里至少锁住折扣行和税额行的相对顺序。

三、错误处理
5. 企业客户缺 taxId 时,函数返回可展示 errorMessage,并让 canIssue 为 false,不应抛出导致接口 500 的异常。
   证据:历史 bug 2026-05;前端直接展示 errorMessage。
   锁定建议:补企业客户缺 taxId 测试,并 mock logger.warn。

四、金额边界
6. subtotal - discountTotal 小于 0 时,taxableAmount 被压到 0。
   证据:函数片段说明。
   锁定建议:补折扣大于小计的测试。

7. taxableAmount 为 0 时,taxAmount 返回 0,不出现负税额。
   证据:历史 bug 2026-05。
   锁定建议:这是第一轮必须锁住的历史 bug 回归测试。

五、副作用
8. 企业客户缺 taxId 时调用 logger.warn。
   证据:函数片段说明。
   锁定建议:如果项目已有 logger mock 模式,可断言调用一次;否则先人工复核,不为了这条引入复杂 mock。

第一轮优先锁定
- 旧订单缺 discountLines 不抛异常。
- 折扣后 taxableAmount 不小于 0。
- taxableAmount 为 0 时 taxAmount 为 0。
- 企业客户缺 taxId 返回 errorMessage,不抛异常。
- canIssue 在缺 taxId 时为 false。
- previewLines 至少保持关键行顺序。
- 返回对象保留 previewLines、warnings、errorMessage、canIssue 字段。

建议补充的测试或快照
1. 单元测试:legacy order without discountLines
   目的:锁住旧数据兼容。
   断言:函数不抛异常;previewLines 非空;warnings 不包含系统异常。

2. 单元测试:discount exceeds subtotal
   目的:锁住金额下限。
   断言:taxableAmount 展示为 0;taxAmount 为 0;没有负数金额。

3. 单元测试:company customer missing taxId
   目的:锁住错误策略。
   断言:canIssue 为 false;errorMessage 可展示;函数不抛异常。

4. 小快照:normal company preview shape
   目的:锁住返回字段和关键行顺序。
   注意:不要锁时间戳、随机 ID 或格式不稳定字段。

分步重构计划
1. 提取 normalizeInvoiceInput
   范围:只移动 order.discountLines 默认值、options 默认值和输入归一逻辑。
   行为锁:旧订单缺 discountLines 测试必须通过。
   命令:pnpm test server/invoice/buildInvoicePreview.test.ts。

2. 提取 calculateTaxableAmount
   范围:只移动 subtotal、discountTotal、taxableAmount 和 taxAmount 计算。
   行为锁:折扣大于小计、taxAmount 为 0 的测试必须通过。
   命令:pnpm test server/invoice/buildInvoicePreview.test.ts。

3. 提取 buildInvoiceIssueState
   范围:只移动 canIssue、errorMessage、warnings 和缺 taxId 判断。
   行为锁:企业客户缺 taxId 测试必须通过。
   命令:pnpm test server/invoice/buildInvoicePreview.test.ts。

4. 提取 buildPreviewLines
   范围:只移动 previewLines 组装和排序,不改字段名和顺序。
   行为锁:主路径小快照和关键行顺序测试必须通过。
   命令:pnpm test server/invoice/buildInvoicePreview.test.ts。

待人工确认的问题
1. legacy_import 跳过 couponCode 校验是否仍是有效业务规则。
2. logger.warn 是否必须长期保留,还是只是历史排查残留。
3. previewLines 中金额字段是否允许从字符串改为数字;本轮默认不改。

人工验收

人要怎么检查和改到可用

人工复核的第一件事,是确认 Codex 提取的行为到底是不是当前行为。它可能把注释当成事实,也可能把历史 bug 描述误解成现在的期望。你要打开目标函数和调用方,对照行为锁清单逐条看证据。凡是没有证据的条目,不要写成测试里的硬断言,先标成“待确认”。

第二,检查它有没有把“理想行为”混进“现有行为”。重构前的行为锁应该描述现在代码怎么表现,而不是你希望它将来怎么表现。比如你认为企业客户缺税号应该返回 400,但当前调用方依赖 `errorMessage` 和 `canIssue: false` 展示页面错误,那么重构阶段就要先锁住当前策略。要改错误策略,可以另开功能或 bugfix。

第三,检查测试是否太脆。行为锁不是把整个大对象一股脑快照下来。大快照短期看省事,长期会让每次小改都变成快照审查。更好的方式是锁关键字段、关键顺序、关键金额和关键错误。对时间戳、随机 ID、日志格式、对象属性自然顺序这类不稳定内容,要么归一化,要么不纳入第一轮锁定。

第四,检查测试数据是否安全。不要从生产日志里复制真实订单、客户名、税号、邮箱、手机号、地址或发票抬头。行为锁只需要结构和边界,不需要真实身份信息。用 `DEMO_ORDER_001`、`DEMO_CUSTOMER_COMPANY`、`TAX_ID_DEMO` 这类占位数据就够。

第五,检查重构步骤是否仍然太大。如果第一步同时提取输入归一、金额计算和错误处理,那失败时很难定位。你可以把它拆成更窄的步骤:先只提取无业务判断的默认值处理;测试通过后再提金额计算;再提错误状态。每一步越小,Codex 越不容易在移动代码时顺手修逻辑。

第六,检查实际 diff 是否符合“只重构”。如果 Codex 在重构里改了错误文案、改了金额舍入、改了返回字段名、改了调用方或删了历史兼容分支,就要让它说明证据。没有明确需求和确认的行为变化,应从本轮撤出。重构 PR 的价值在于结构变清楚,行为保持可证明。

第七,决定哪些锁要长期留下。历史 bug、防空值兼容、错误策略和调用方依赖通常应该保留在测试里。临时快照如果只是为了辅助拆函数,可以在重构后缩小成更明确的断言。最终留下的测试应该服务未来维护,而不是成为一堆没人敢更新的冻住文件。

适用边界再强调一次:本文方法适合遗留函数拆分、长函数提取、复杂条件整理、数据转换函数重构、错误处理归拢等“不打算改变行为”的场景。如果你要改业务规则、替换底层算法、迁移数据模型、调整权限策略或改变接口契约,就不能只靠行为锁。那时需要需求确认、迁移计划、兼容策略和发布回滚方案。

失败反例

这些失败反例要提前避开

【反例 1:直接说“帮我把这个函数重构得优雅一点”】

这个提示词看起来很自然,但它没有告诉 Codex 什么不能变。Codex 可能会提取函数、改名、合并分支、统一错误返回、删除看似重复的兼容逻辑。最终代码确实更短,但折扣为 0、旧订单缺字段、企业客户缺税号这些线上边界可能被悄悄改掉。

更好的做法是先问:“请提取当前输入输出和历史 bug 行为,列出重构前必须锁住的行为,不要改代码。”等行为锁确认后,再让它分步重构。

【反例 2:把快照当成万能保险】

有些人会让 Codex 给整个返回对象补一个巨大快照,然后开始拆函数。问题是大快照可能锁住大量无关细节,比如属性顺序、格式化空格、临时字段和不稳定时间。它既不能告诉你真正重要的行为是什么,也会让后续每次调整都陷入快照噪音。

更好的做法是把快照用在稳定的输出形状上,同时用明确断言锁历史 bug和关键字段。比如断言 `taxAmount` 不为负、`canIssue` 为 false、`errorMessage` 存在、关键预览行顺序不变。

【反例 3:一边补测试一边改行为】

你发现企业客户缺税号时当前函数返回 200 和错误消息,觉得不合理,于是让 Codex 顺手改成抛异常,再补测试。这已经不是重构,而是行为变更。它可能需要前端、接口调用方、错误处理中间件和产品文案一起调整。混在重构里,评审者很难判断到底是结构整理还是业务修改。

更好的做法是先锁住当前行为,把“不合理但真实存在”的地方列为待确认。确认要改后,另开小 bugfix 或功能改动,再用新的验收标准处理。

【反例 4:只锁主路径,不锁历史 bug】

普通用例最容易写,也最容易给人安全感。但长函数真正容易被重构改坏的,常常是那些历史 bug 场景:旧数据缺字段、金额边界、特殊客户类型、兼容导入来源、错误展示策略。如果行为锁只覆盖“正常订单正常客户”,重构仍然可能把最脆弱的分支拆坏。

更好的做法是从历史 bug 里挑第一批测试。哪怕只补三条,也要优先覆盖曾经真实坏过的边界。

【反例 5:把重构计划写成“拆成几个工具函数”就开干】

“拆成金额计算、校验、格式化三个函数”听起来合理,但还不够。它没有说明每一步保护哪些行为、跑哪些测试、失败时怎么定位。Codex 很可能一次性完成全部提取,最后测试失败时你只能在一大坨 diff 里找原因。

更好的做法是把每一步和行为锁绑定:提取输入归一,只跑旧订单兼容测试;提取金额计算,只跑金额边界测试;提取错误状态,只跑缺税号测试。结构越清楚,回退越容易。

主题边界

它和相邻主题的区别

这篇文章不是“让 Codex 开发一个新功能”。新功能的重点是目标行为、验收标准和上线顺序;本文的重点是现有行为、历史边界和防止无意改变。你不是让 Codex 发明更好的业务规则,而是让它先证明旧规则没有被重构碰坏。

它也不同于“大功能拆成小 PR”。大功能拆分关注数据库、接口、前端、权限、配置和发布的依赖顺序;本文关注一个已有函数内部如何在行为不变的前提下拆结构。这里的交付物不是功能实施清单,而是行为锁定清单和受保护重构步骤。

它还不同于“小 bug 修复范围确认”。小 bug 的重点是找到最小修复点和不改清单;本文默认你不是在修一个已知错误,而是在清理一段仍然工作的复杂代码。重点不是“改哪里最少能修掉问题”,而是“重构前哪些输入输出必须先被固定”。

它也不同于“给一个字段加展示”。单字段改动通常围绕接口字段、类型、映射和 UI 展示链路;本文围绕旧函数的输入兼容、输出结构、错误策略、副作用和历史 bug。两者都强调小步,但本文更强调“先保护,再整理”。

最后,本文也不是测试教程大全。它不会教你从零建立完整测试体系,也不追求覆盖率指标。它只解决一个窄问题:当你要用 Codex 重构一个长函数,而测试又不足时,怎样先把当前行为锁住,让每一步整理都有底线。对怕改坏功能的工程师来说,这个底线往往比漂亮抽象更重要。

可直接套用的流程

1. 先写清楚任务目标:这次要让 AI 帮你完成什么工作,而不是泛泛地问一个问题。

2. 再给资料边界:哪些背景、数据、约束、口径必须被使用,哪些内容不能编。

3. 最后规定输出格式:用清单、表格、方案、话术还是复盘报告,并保留人工检查。

继续看相关教程

同类教程