收益:
- 能够建立一个测试安全网,在重构之前捕获当前行为
- 能够要求 AI 进行小型、一步式、行为保留的转换并验证每一步
- 能够在业务环境中识别技术债务并确定其优先级
重构是在不改变代码外部行为的情况下改进代码的内部结构:使其更可读、更简单、更可维护。另一方面,技术债务是为了快速解决方案而做出的设计妥协,并随着时间的推移“连带利息”地偿还——你今天削减的每一个角落都会在明天以减速或错误的形式回来。人工智能是一个强大的助手,可以加速重复性和机械性的重构任务;但重构有一条黄金法则,仅靠人工智能无法保证这一点:行为不得改变。
在本单元中,我们学习如何使用人工智能进行安全重构:小而可逆的步骤、通过测试进行保护、检测代码异味以及确定技术债务的优先级。关键点在于:证明行为被保留的是通过的测试,而不是人工智能的话。
重构的黄金法则:行为保持不变
重构之所以危险,是因为在说“我正在进步”的同时不知不觉地改变了行为。简化条件时丢弃边缘情况、转换循环时破坏顺序、拆分函数时遗漏副作用——所有这些都会产生“看起来干净”但损坏的代码。
这就是为什么测试是重构的先决条件:在更改之前,您必须进行捕获现有行为的测试。这些测试是一个“安全网”;如果您在重构过程中不小心破坏了某些东西,它们会破坏并警告您。如果没有测试,请首先编写修复现有行为的测试(正如我们在第 5 单元中学到的那样)——这就是人工智能的起步点。
注意:没有测试网的人工智能辅助重构是最隐蔽的错误来源之一。说“我保留了这种行为”很容易;证据是更改前后通过了相同的测试。
一步一步:安全重构流程
- 设立安全网。让测试捕获您将重构的代码的当前行为;如果没有,请先把它们写下来(并看着它们完成)。
- 说出气味。你正在改进什么?为什么? “这个函数做了 3 件事”,“相同的逻辑在 4 个地方重复”,“名称具有误导性”。
- 要求采取小的、一步一步的步骤。要求人工智能进行一次转换(例如,“将此函数分成两半”),而不是重写整个文件。
- 运行测试。每走一步之后。如果是绿色,则继续,如果是红色,则收回。
- 阅读差异。逐行确认更改确实是行为保留的;当说人工智能“只是结构”时可能存在逻辑错误。
- 合并成小块。大型一次性重构 PR 既存在风险又无法审查。
三个迷你箱
案例 1 — 220 行函数安全拆分。一个团队拥有 220 行订单处理功能。前 14 个测试(在 AI 的帮助下)编写并捕获了当前行为,它们全部通过了。然后由AI将功能逐步划分为5个更小的功能;每个步骤后都运行测试。两项测试一步被破坏——人工智能在一种边缘情况下错过了返回。测试立即发现了这个问题并修复了它。如果没有网络,错误可能会一直蔓延到生产环境。
案例 2——没有测试网的灾难。另一位开发人员“清理”了一个没有经过人工智能测试的日期计算模块。代码看起来更好,但它错误地计算了闰年;两周后,该错误因客户投诉而出现。损失远远超过重构节省的时间。教训:不进行测试的重构是一场赌博。
案例 3 — 技术债务优先级。一个团队给 AI 积压了 30 个左右的“可改进”点,并在“变更频率 × 风险 × 努力”轴上对每个点进行评分。在结果表中,很少被触及的丑陋模块实际上是低优先级,而经常更改的中等复杂性模块是高优先级。团队将精力用在了正确的地方。
四个可复制模板
代码气味检测和优先级:
在此代码中列出重构候选者的“味道”:长函数、重复(DRYViolation)、误导性名称、深层嵌套条件、隐藏副作用、幻数。对于每个:位置、问题原因、建议的小步骤、估计风险(低/中/高)。暂时不要更改代码,只需计划即可。{{code}}
一步式、行为保留的转变:
只需这样做:{{单次转换,例如将此函数分为 3 个较小的命名函数}}。更改可见行为、签名和返回值。用 1 句话写下为什么您更改的所有内容都保留了该行为。{{code}}
重构前的安全网(特征测试):
编写测试来捕获该函数的当前行为(正确与否);目标是捕捉重构过程中行为是否发生变化。包括典型+边缘条目。根据函数的当前输出编写期望。{{function}}
技术债务记录(积压)生成:
将以下气味列表倒入优先级表中:物质、受影响的区域、变化频率(我的知识:{{...}})、风险、估计工作量、建议的优先级。将高影响+低努力的放在顶部。 {{气味列表}}
弱提示/强提示
弱:“清理这段代码并使其变得更好。”
Strong:“将这个 90 行函数拆分为 3 个具有单一职责的较小函数,而不更改其外部行为和签名。将副作用(数据库写入)保持在当前顺序。我进行了测试,行为应该保持不变。给出差异并用一句话解释为什么每个拆分都是行为保留的。[代码]”
强大版本;它需要一个特定的转换,明确地施加行为和签名约束,并要求理由。诸如“做得更好”之类的模糊要求会导致不受控制和危险的变化。
重构类型
人工智能可靠性
先决条件
重命名
高
范围是否正确?
功能划分
中高
测试网是必须的
分享重复
中等
行为差异可能被隐藏
算法/结构改变
低
广泛的测试+人工验证
架构重组
低
以人为主导,人工智能支持
管理技术债务,而不是重置它
技术债务并不都是坏事。有时有意识地借用(以满足交付)是正确的决定。目标不是消除债务,而是使其可见且易于管理。人工智能能够快速检测债务并确定其优先级,但决定“哪些债务应该偿还,哪些债务应该放弃”需要业务背景:这个模块多久更改一次,影响多少人,风险有多大?这个决定是由了解代码库和产品的团队做出的; AI只是澄清了选项。
提示:将重构 PR 与涉及行为改变的 PR 分开。能够说“这个 PR 只是一次重构,行为是一样的”可以让调查变得更容易,并让您在出现问题时快速缩小原因范围。
常见错误
- 无需测试网的重构。您无法证明该行为被保留。
- 它的意思是“清除整个文件”。较大的、不受控制的更改隐藏了错误并且无法检查。
- 接受 Diff 而不阅读它。当人工智能说“只是结构”时,它可能已经忽略了一些逻辑。
- 将重构与行为改变混淆。在同一个 PR 中同时执行这两项操作会使根本原因跟踪变得不可能。
- 试图修复每一种气味。很少更改的丑陋代码通常优先级较低;将精力分配到经常变化的地方。
综上所述
重构的唯一规则是行为保持不变,而测试就是证明。人工智能在检测代码异味、一步转换和优先处理技术债务方面非常强大;但您必须设置安全网,运行测试并在每个步骤后读取差异。采取小的、可逆的步骤;区分重构和行为改变;并让了解业务背景的团队决定支付哪些债务。
应用任务
从代码库中选择一个对您来说看起来很长或很复杂的函数。首先打印测试,使用“安全网”模板捕获其当前行为,并查看它们是否全部通过。然后使用“一步、行为保留转换”模式以单一方式(例如分成两半)重构函数,并再次运行测试。如果测试失败,找出原因;如果它根本没有中断,请逐行读取差异以确认该行为确实被保留。
清单
- [ ] 我知道重构不应该改变行为,并且有测试可以证明这一点。
- [ ] 我正在建立一个安全网,在重构之前捕获当前行为。
- [ ] 我想要人工智能带来的小规模、一步式的转变,而不是一次性的大转变。
- [ ] 在每个步骤之后,我都会运行测试并读取差异。
- [ ] 我将重构公关与行为改变公关分开。
- [ ] 我会根据业务环境优先考虑技术债务,而不是盲目地试图将其归零。