


















在第11章,我们为项目建立了一道坚固的“测试电网”。这道电网,能极大地保证我们代码库的“正确性”。只要电网不断电,我们就能确保系统的核心行为,不会在AI的修改下发生偏离。
然而,“正确”的代码,并不等同于“健康”的代码。
一个软件系统,就像一个生命有机体。除了需要有强健的骨骼(架构)和可靠的行为(测试)之外,它还需要一个高效的“新陈代谢”系统。一个健康的有机体,会不断地将老旧、无用、甚至有害的细胞,清除出体外,为新的、有活力的细胞,腾出空间和资源。
如果一个有机体,只进不出,不断地累积代谢废物,那么即使它眼下还能正常活动,也终将因为内部的“拥堵”和“中毒”,而变得臃肿、迟缓,最终走向衰败。
在AI编程时代,我们享受了前所未有的“创造”速度,代码的“生长”速度被极大地加快了。但与此同时,如果我们没有一个与之匹配的、同样高效的“清理”机制,那么代码库中“代谢废物”——即技术债务——的累积速度,也将是史无前例的。
本章,我们将学习如何成为一名软件世界的“清道夫”和“园艺师”。我们要建立一套定期的“垃圾回收”机制,主动地、系统性地去识别和清除那些由AI(以及我们自己)在快速迭代中,不经意间制造出来的“代码垃圾”。我们的目标,是让我们的系统,在一次次的版本迭代中,不仅功能越来越强大,代码本身也越来越纯粹、精炼,最终实现一种令人向往的“逆生长”状态。
在与AI协作一段时间后,你会敏锐地发现一个现象:AI生成的代码里,似乎特别容易出现“死代码”——那些被定义了,但从未被任何地方调用过的函数、变量、类或导入。
此外,AI也特别喜欢留下一些逻辑上看似合理,但实际上在当前业务流程中,永远不会被执行到的“冗余逻辑分支”。
这种现象,并非偶然,它源于AI的两个核心天性:“概率联想”和“缺乏全局意识”。
AI写代码,并不像人类那样,是基于一个清晰的、从A到B的“意图驱动”逻辑。它的核心,是一种“模式补全”的机制。
当你让AI写一个处理用户上传图片的函数时,它的神经网络会被“图片上传”这个关键词激活。然后,它会从它的训练数据中,调取所有与“图片上传”这个模式相关的、最常见的代码片段。
isValidFileType()的辅助函数。isWithinSizeLimit()的辅助函数。compressImage()、createThumbnail()、addWatermark()这些函数,也都一并生成出来。现在,问题来了:在你的具体业务需求里,你可能只需要“文件类型验证”和“大小限制”。你根本不需要压缩、缩略图和水印功能。
但AI并不知道这一点。它只是在“概率”的驱动下,为你补全了一个它认为“最完整”、“最常见”的“图片上传处理”代码模式。于是,compressImage()、createThumbnail()、addWatermark()这三个函数,就成了“死代码”。它们被定义了,语法完全正确,甚至可能还有对应的单元测试,但它们从未在你的业务主流程中,被调用过一次。
我们在第五章讲过,AI的“认知”是受限于其“上下文窗口”的。即使有超长的上下文,它对代码的理解,依然是“扁平化”的,缺乏人类那种对项目整体架构的、层次化的“心智模型”。
这种“局部视野”,导致AI在进行修改时,特别容易制造出“孤儿代码”。
【一个典型的场景】
V1_ReportGenerator.js模块,它被DashboardPage.js和AdminPanel.js两个地方调用。V2_ReportGenerator.js,并让DashboardPage.js改用这个新模块。”V2_ReportGenerator.js,并修改了DashboardPage.js的import语句,使其指向了新模块。DashboardPage.js。它不知道,在项目的另一个遥远的角落,还有一个AdminPanel.js,仍然在引用那个旧的V1_ReportGenerator.js。AdminPanel.js。在这次重构中,AI发现AdminPanel.js里生成报表的功能,和DashboardPage.js里的很像,于是它(正确地)决定,也让AdminPanel.js改用V2_ReportGenerator.js。V1_ReportGenerator.js这个模块,已经不再被项目里的任何一个地方所引用了。它变成了一个彻头彻尾的“孤岛”。但没有任何人(包括AI)会意识到这一点。这个包含了数百行旧逻辑的文件,将像一个“幽灵”一样,永远地留存在你的代码库中,占用空间,并在未来的开发者阅读代码时,制造巨大的困惑。由AI制造的“死代码”和“冗余逻辑”,通常具有以下几个特点:
这种类型的技术债务,极其隐蔽,难以通过常规的Code Review和自动化测试被发现。测试只能保证“被调用的代码”是正确的,但它无法告诉你“哪些代码从未被调用”。
因此,我们必须建立一套专门的、定期的“垃圾回收”机制,来主动地、系统性地清扫这些由AI高速创造力所伴生的“代谢废物”。
对抗“代码熵增”,不能靠一时的心血来潮。它必须被流程化、制度化,成为你和你的团队,开发周期中一个不可或缺的“仪式”。
这个“仪式”,可以设定在每个“迭代”开始前,或者每个“主要版本”发布后。它的目标,不是去开发新功能,而是专门停下来,对现有的代码库,进行一次彻底的“大扫除”。
这个大扫除,主要聚焦于三类“垃圾”。
这类垃圾,指的是那些在代码结构上存在的、完全无用的部分。清理它们,需要借助静态代码分析工具。
ts-prune或ESLint的no-unused-vars规则(但后者功能较弱)。Webpack的Tree Shaking在构建时能移除死代码,但我们希望在“编码时”就发现它们。vulture是一个专门用于发现死代码的优秀工具。go vet自带了一些检查,社区也有deadcode等工具。ts-prune或vulture等工具,并将它们的输出,作为构建产物。depcheck是一个必不可少的工具。它会扫描你的代码,然后告诉你package.json里的哪些依赖,从未在代码中被import或require过。npx depcheck。depcheck的报告,对于确认不再需要的依赖,果断地从package.json中移除,并重新运行npm install或yarn install。node_modules的体积,还能降低项目的安全风险(更少的依赖=更小的攻击面)。这类垃圾,无法被工具100%自动发现,它需要结合工具和人类的“领域知识”进行判断。
if (condition)块,但由于系统其他部分的演进,这个condition在逻辑上,已经永远不可能为true了。if语句的分支,在经过了你全面的自动化测试之后,依然显示为“未覆盖”,这本身就是一个强烈的“危险信号”。if条件成立?如果不存在,请解释为什么,并帮我安全地移除这个分支和它内部的代码。”'LEGACY_USER'的用户类型。但在三个月前,我们已经完成了数据迁移,系统中已不再存在这种用户类型。因此,这个分支是安全的死代码。”WARN日志事件(比如,event: "FALLBACK_TO_GENERIC_RECOMMENDATIONS")。[... a ...],并判断在当前系统架构下,这个条件是否还可能发生。如果不可能,请帮我移除这段降级代码,并将原来的try-catch块,简化为直接调用。”这类垃圾,不影响程序的运行,但会极大地增加人类(和AI)理解代码的“认知成本”。
if/else逻辑,就变成了技术债务。'ENABLE_NEW_FEATURE_X'现在已经100%开启,并且是永久性的。请扫描整个代码库,移除所有与这个开关相关的if/else判断,并只保留if分支内的逻辑。”通过建立这样一套覆盖了“结构”、“逻辑”和“认知”三个维度的定期清理机制,你就拥有了一个强大的“代码新陈代谢”系统。这个系统,将确保你的项目,不会在AI的“催化”下,过早地衰老和僵化。
“逆生长”,听起来是一个违反自然规律的、充满诗意的理想。但在软件的世界里,它并非不可能。
一个常规的软件项目,其生命周期曲线,通常是这样的:
这个过程,就是软件的“熵增定律”的体现。
而我们建立的“定期垃圾回收”机制,其终极目标,就是要对抗熵增。它与我们日常的“功能开发”工作,共同构成了项目演进的“阴”和“阳”两面:
当“生长”和“修剪”达到一种动态平衡时,奇迹就会发生。
每一次迭代,我们都:
结果就是,项目的总代码行数(LOC, Lines of Code)可能增长缓慢,甚至在某些迭代中是下降的!但系统的业务价值,却在持续、稳健地提升。
系统的“复杂度”被控制在了一个相对平稳的水平,而不是无限地指数增长。代码库,像一个被精心照料的盆景,而不是一个肆意疯长的热带雨林。它在不断地新陈代陈,变得越来越“精炼”,越来越“纯粹”。
这就是“逆生长”。
AI在这个过程中的角色: AI,既是那个可能加速“熵增”的“催化剂”,也是我们执行“熵减”的、最强大的“手术刀”。
一个成熟的AI协作团队,其迭代节奏,不应该是“开发-开发-开发”,而应该是“清理-开发-清理-开发”。将“垃圾回收”制度化,把它放在和“功能开发”同等重要的战略位置上,是确保你的项目能够穿越时间周期,实现长期价值和可持续发展的唯一道路。
请将这份清单,作为你每个迭代周期“大扫除”仪式的行动指南。你可以把它做成一个模板,在每次迭代规划会议上,逐项检查。
| 分类 | # | 检查点 | 如何检查(工具/方法) | AI协作指令 |
|---|---|---|---|---|
| 结构性 | 1 | 未使用的导出 (Dead Exports) | ts-prune | "Run ts-prune and help me analyze its report. For each reported unused export, confirm if it's safe to remove." |
| 2 | 未使用的依赖 (Unused Dependencies) | depcheck | "Run depcheck and remove all identified unused dependencies from package.json." | |
| 3 | 空文件/空目录 (Empty Files/Dirs) | find . -type f -empty | "Find and list all empty files or directories in the src folder for deletion." | |
| 逻辑性 | 4 | 无法触及的分支 (Unreachable Branches) | 审查代码覆盖率报告中的非100%分支 | "This if branch is never covered by our tests. Analyze the condition and confirm if it's unreachable in our current logic. If so, refactor the code to remove it." |
| 5 | 永不触发的降级 (Obsolete Fallbacks) | 搜索生产环境日志,寻找长期未出现的降级事件 | "This fallback logic hasn't been triggered for a month. Is its triggering condition still possible? If not, please simplify the try-catch block." | |
| 6 | 重复/相似的逻辑 (Duplicate Logic) | jscpd 或 pmd (CPD) 等代码重复检测工具 | "Our CPD tool found these two functions are 90% similar. Please refactor them by extracting the common logic into a shared utility function." | |
| 认知性 | 7 | 过期的注释 (Outdated Comments) | 人工审查 | "The logic of this function has changed. Please review and update its docstring/comment to accurately reflect the new behavior." |
| 8 | 废弃的功能开关 (Stale Feature Flags) | 审查功能开关注册表 | "The feature flag '...' is now obsolete. Please scan the codebase and remove all related logic, keeping only the 'enabled' path." | |
| 9 | 模糊/待办的注释 (Vague/TODO Comments) | 搜索代码库中的 // TODO:, // FIXME:, // HACK: | "List all TODO comments in the codebase. Let's review them one by one. Can we resolve this one now? If so, implement the required change." | |
| 10 | 被注释掉的代码 (Commented-out Code) | 使用正则表达式搜索被注释掉的大块代码 | "This block of code has been commented out for a long time. It should have been managed by Git. Please remove it. If we need it, we can find it in the Git history." |
将这份清单,融入你的团队文化。让“保持代码的清洁”,和“交付新功能”一样,成为一种能带来荣誉感和满足感的、值得骄傲的工程实践。
当你做到这一点时,你就真正地、完全地驾驭了AI。你不仅利用了它的“光”——那无与伦比的创造速度;也控制了它的“影”——那同样惊人的、制造混乱的潜力。你成为了一个真正意义上的、面向未来的“软件大师”。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。