垃圾代码(junk code)是指对项目没有益处,但增加项目体积、构建时间和团队认知负荷的代码和依赖关系。与永远不会执行的死代码不同,垃圾代码可能可以运行,但效率低下或冗余:重复的库、未使用的导入、注释掉的代码块、过时的 polyfill 和装饰性抽象。根据 CodeScene Code Health Report (2025),移动项目中平均 15% 的依赖关系未被直接使用,仅拖拽传递性包。垃圾代码是项目的额外负担:它使代码库变得更臃肿,但没有变得更强。定期审核依赖关系和删除多余的抽象直接影响构建速度和代码质量。
要点
垃圾代码(junk code)是项目中存在但没有功能价值的代码、配置和依赖关系的统称。垃圾代码不一定是损坏或未使用的 — 问题在于它的存在在没有充分理由的情况下恶化了项目的指标。
垃圾代码分为四类。第一 — 多余的依赖关系:为了一个可以用标准工具实现的功能而连接的库。第二 — 死负荷:注释掉的代码块、没有工单的 TODO、空方法和存根类。第三 — 重复的解决方案:两个做同样事情的库(例如,同一个项目中的 Gson 和 Kotlin Serialization)。第四 — 过度工程:未被使用但为未来维护的架构层。
根据 Stripe Engineering Productivity (2025) 的研究,从典型项目中删除 10% 的垃圾代码可将完整构建时间平均缩短 22%。原因:每个额外的依赖关系都会增加构建图,每个空抽象都需要时间来理解,每个注释掉的代码块都会分散注意力。
与垃圾代码作斗争的主要困难是缺乏立竿见影的后果。带有垃圾代码的项目可以编译和运行。问题是逐渐积累的:构建变慢,传递性依赖关系的数量增加,一年后添加新功能所需的时间比应有的多一倍。
垃圾依赖是指连接到项目但未在代码中直接使用的库和包,或者仅在一个可以用标准 API 更轻松实现的功能中使用。
典型示例:当项目已使用 Kotlin Serialization 时,用于处理 JSON 的库(两个解析器 — 这就是垃圾代码);为单个 StringUtils.isEmpty 方法使用 Apache Commons Lang 库,而该方法可以用 Kotlin 扩展 isNullOrBlank 替代;在十个模块中的一个模块使用的 DI 库,而其他模块通过构造函数手动获取依赖关系。
每个额外的依赖关系不仅仅是二进制文件中的额外代码。它还会增加漏洞的攻击面:根据 GitHub Advisory Database(2025),移动项目中 40% 的关键 CVE 来自开发人员无法控制的传递性依赖关系。依赖关系越少 — 攻击面越小。
// 查看 Gradle 依赖树
./gradlew app:dependencies --configuration releaseRuntimeClasspath
// 查找未使用的依赖(Gradle 插件)
plugins {
id "com.autonomousapps.dependency-analysis" version "2.0.0"
}
// 生成未使用库报告
./gradlew buildHealth
对于 iOS,使用命令 swift package show-dependencies,它显示完整的依赖关系树。Xcode Build Timeline 工具显示每个库为构建增加了多少时间。如果一个库占用了 30% 的编译时间但只在一个屏幕上使用 — 它就是需要删除或替换的候选者。
对于 Node.js(React Native),使用 depcheck — 一个查找 package.json 中未使用依赖关系的工具,以及 npm-check,它还会显示过时的版本。实施规则:每个新的依赖关系都必须经过 code review,并说明为什么不能使用标准工具。
死导入是最常见的垃圾代码类型。它们不影响运行时,但会增加编译时间:编译器处理每个导入,即使它没有被使用。在大型项目中,删除未使用的导入可将构建时间缩短 5–10%。
现代 IDE 会自动以灰色突出显示未使用的导入。在保存文件时配置自动清理:在 IntelliJ IDEA 中 — Optimize Imports on the fly,在 Xcode 中 — Editor > Remove Unused Imports。在 CI 中添加检查:linter 应阻止带有未使用导入的提交。
注释掉的代码是另一种垃圾代码。开发人员注释掉代码块以在重构时保留功能。然而,git 存储了完整的更改历史:任何删除的代码都可以通过一个 git revert 或 git log -S
规则:仓库中没有注释掉的代码。如果代码不需要 — 永久删除它。如果代码需要但暂时禁用 — 使用带有工单和截止期限的功能开关。// TODO: remove after migration 类型的注释 — 不要没有截止期限。设置日期并在日历中设置提醒。
过度工程是创建不能解决当前问题但需要维护的架构层。这是最难处理的垃圾代码类型之一,因为形式上代码是正确的:遵循 SOLID,有测试覆盖,符合架构。问题在于它不需要。
经典示例 — 一个具有单一 invoke 方法的抽象 UseCase 类,它只是调用仓库。如果 UseCase 没有添加逻辑(缓存、重试、转换),只是传递调用 — 这就是一个多余的实体。它增加了项目导航:开发人员打开 UseCase,看到 invoke → repository — 然后关闭。时间浪费,收益为零。
另一个例子 — 过度参数化。一个具有六个类型参数的通用接口,只在一个地方使用。每个类型参数都是认知负荷:阅读代码时需要记住六个类型,而实际上只使用两个。如果抽象没有被复用 — 它就是多余的。
截断标准:如果一个抽象没有在三个不同的上下文中被复用 — 删除它。抽象只有在真正解决重复问题时才是合理的,而不是在预测假设性的未来场景时。YAGNI(You Ain't Gonna Need It)是防止过度工程的最佳原则。
垃圾代码审核需要结合静态分析、依赖关系分析和手动检查。无法完全自动化搜索多余的抽象,但技术性垃圾代码(死导入、未使用的库、注释掉的代码)可以通过工具找到。
| 类别 | 工具 | 检查内容 |
|---|---|---|
| 未使用的依赖 | dependency-analysis(Gradle) | 代码中未使用的库 |
| 未使用的依赖 | depcheck(Node.js) | package.json 中没有导入的包 |
| 未使用的依赖 | swift package --show-dependencies | SwiftPM 依赖树 |
| 死导入 | IDE(Optimize Imports) | 未使用的导入表达式 |
| 注释掉的代码 | grep -r "//" / rg "^\s*//" | 含有代码的注释块 |
| 空方法/类 | SonarQube / CodeClimate | 没有方法体或方法体为空的方法 |
| 重复的库 | Gradle lint(duplicate classes) | 来自不同库的类冲突 |
为了全面审核,每个 sprint 运行一次 buildHealth(Android)或 depcheck(Node.js)。在 CI 中创建一个仪表板,显示每个 sprint 的依赖数量变化趋势。如果数量增加,但功能没有成比例增加 — 团队正在积累垃圾代码。
注意 duplicate classes — 两个库包含同一个类的错误。这不仅是垃圾代码,也是构建冲突的直接来源。在 Gradle 中,这种冲突通过 force 或 exclude 解决,但每次这样的解决都表明其中一个库是多余的。
清理垃圾代码不是一次性行动,而是一个定期流程。没有规则,垃圾代码在两到三个 sprint 内就会回来。最佳实践是分配每个 sprint 的 10–15% 容量用于技术清理,包括垃圾代码审核。
该流程包括四个步骤。第一 — 诊断:运行工具、获取报告、确定优先级。高优先级 — 具有已知 CVE 的依赖和重复的库。中优先级 — 死导入和注释掉的代码。低优先级 — 多余的抽象(需要手动分析)。
第二 — 清理:删除死依赖、将重复的库替换为一个、删除注释掉的代码。每个更改都使用单独的提交,并附带清晰的消息:remove unused dependency: gson(replaced by kotlinx.serialization)、delete commented code in LoginViewModel。
第三 — 验证:构建项目、运行测试、检查 UI。如果删除依赖后测试通过 — 该依赖确实不需要。如果测试失败 — 意味着某个地方留下了静态分析器未能检测到的隐藏引用。
第四 — 预防:更新 code review 清单、在完成定义中添加没有理由就不得添加新依赖的规则、在 CI 中设置自动检查。预防是防止垃圾代码再次积累的唯一方法。
常见问题
技术债务是有意识的折中决策(快速但质量低),计划将来修复。垃圾代码不是有意识的决定,而是积累的废物:多余的依赖、注释掉的代码、没有人计划也没有人想维护的空抽象。
最佳节奏 — 每个 sprint 分配 10% 的时间进行技术清理。这可以在不积累关键数量的情况下控制垃圾代码。如果项目中有很多垃圾代码 — 从一次大型清理 sprint 开始,然后过渡到定期节奏。
测量并展示数字:测量删除 3–5 个多余依赖前后的构建时间。每次构建节省 15–30 秒乘以每天的构建次数,就可以得到团队节省的小时数。数字比抽象的整洁号召更有说服力。
是的,特别是当依赖存在 CVE 时。即使项目稳定,传递性依赖中的漏洞也是安全风险。此外,在更新 SDK 或语言时,旧依赖可能不再兼容,在升级前删除它可以节省数小时的迁移时间。
每个没有工单的 TODO 都是垃圾代码。制定规则:TODO 只能以 // TODO(PROJECT-1234):fix 格式编写,并链接到跟踪器中的任务。定期检查 TODO 并关闭那些已过时的。删除过期的 TODO — 如果问题在半年内没有出现,就不关键。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。