
💡 为什么你的 Flutter 应用感觉比它本应更"重"
Flutter 项目增长得很快。一个最初干净、模块化的架构可能很快就会变成一个充满辅助工具(helpers)、实用程序(utilities)和实验性小部件(experimental widgets)的"丛林"。
请关注我的微信公众号:OpenFlutter,感谢
功能被原型化、被放弃、被替换。旧的扩展和数据模型在完成使命很久之后仍滞留在 lib/ 目录中。
不知不觉中,你交付的 APK 包中塞满了未使用的代码------这会使你的构建文件膨胀、让新的队友感到困惑,并拖慢你的持续集成(CI)流程。
这就是 Flutter PruneKit 发挥作用的地方------一个专注的静态分析工具,专门搜寻 你 Dart 代码中的这些"幽灵"。
👻 为什么"死代码"值得你关注
未使用的代码并非无害的杂物。它可能会:
- 增大 应用包体积,拖慢启动性能。
- 增加构建和测试时间,延长反馈周期。
- 迷惑试图理解你的系统的队友。
- 当过时的辅助工具仍然静静地躺在你的源代码树中时,隐藏潜在的错误。
PruneKit 通过为你提供一份可操作的清理报告 来解决这个问题------它拥有 370 多项自动化测试作为支持,并在真实世界的 Flutter 应用上经过了验证。
⚙️ 核心功能
Flutter PruneKit 理解现代 Dart 语义:
- 类型:常规类和抽象类、枚举、混入(mixins)、命名/未命名扩展。
- 函数:顶级函数、实例/静态方法、工厂构造函数、getter/setter、操作符、私有方法。
- Flutter 上下文 :自动保留
initState或dispose等生命周期方法。
该引擎并行遍历你项目的抽象语法树(AST) 。这使得分析运行时间很短------对于中型应用只需几秒------并通过跟踪覆盖链、泛型和跨文件扩展使用,最大程度地减少了误报。
🛠️ 安装 PruneKit
将其作为开发依赖项添加到你的项目:
bash
dart pub add --dev flutter_prunekit
dart pub get
或者全局安装:
bash
dart pub global activate flutter_prunekit
🔍 你的首次扫描
bash
dart run flutter_prunekit
# 或者, 被全局激活:
flutter_prunekit
默认情况下,它会扫描你的 lib/ 目录,并输出按类别分组的未使用的声明。
示例输出:
yaml
═══ Flutter Dead Code Analysis ═══
⚠ Found 5 unused declaration(s):
Classes: 2
Enums: 1
lib/models/old_user.dart:12
OldUser
lib/widgets/legacy_button.dart:8
LegacyButton
lib/utils/deprecated_helper.dart:5
DeprecatedStatus
Top-Level Functions: 1
lib/helpers/formatter.dart:45
formatLegacyData
Instance Methods: 1
UserService (lib/services/user_service.dart:23)
processLegacyUser [instance]
─── Summary ───
Files analyzed: 156
Total declarations: 89
Total methods: 234
Unused: 5
Class usage rate: 96.6%
Method usage rate: 99.1%
Analysis time: 2.3s
💻 生成的代码和工具
PruneKit 默认会跳过 (skips)生成的文件,以避免产生误导性的结果。
如果你需要分析生成代码 (例如来自 Freezed、json_serializable、built_value、Realm 等工具生成的代码),请传递 --include-generated 标志:
bash
dart run flutter_prunekit --include-generated
提示: 最好以两种方式运行 PruneKit------一次不带 生成的文件以减少干扰,另一次带上生成的文件以捕获隐藏的技术债。
⚠️ 已知的边缘情况(和解决方法)
没有静态分析器是完美的。PruneKit 会揭示出一些潜在的"盲点":
- 动态调用(
dynamic invocations): 当一个方法调用无法被静态解析时,该工具会采取保守策略,可能会错误地将目标标记为未使用。你可以添加@keepUnused注解,或者在可能的情况下用具体的类型替换dynamic。 - 反射(
Reflection): 被镜像的类型或运行时查找需要明确的注解才能在代码精简中幸存。 - 条件导入(
Conditional imports): 特定平台的实现应该手动列入白名单。 - 未命名的扩展(
Unnamed extensions): Dart 的分析器目前将任何使用都视为全局引用。请为你的扩展命名以保持分析的准确性。
这些限制在实用的 Flutter 应用中很少见,但了解它们有助于你准确解读报告。
🛠️ 我为什么构建 PruneKit
我构建 Flutter PruneKit 是出于需要。
在处理大型 Flutter 应用时,我不断遇到同样的问题------有些代码看起来很重要,但实际上根本没有被任何地方使用。
辅助工具(Helpers)、混入(mixins)和小部件(widgets)会在多次重构中幸存下来,仅仅因为没有人确定它们是否仍然需要。
现有的工具,如 linter 或 IDE 检查,可以捕获一部分,但无法捕获全部------尤其是在跨越多个文件和抽象层级的情况下。
因此,我开始尝试一种更精确的方法------基于 AST(抽象语法树)的静态分析,这种分析(得益于 AI)真正理解 Dart 的工作方式。
经过数周的迭代、数百次测试和实际生产环境的试验,PruneKit 诞生了。
如果它能帮助其他 Flutter 团队摆脱同样的**"死代码"焦虑**,那么我的使命就完成了。🧹