那些让 Flutter 工程“烂”得明明白白的事,我们都遇到过:模块拆分越做越细,可单个文件几百行谁都不敢动;改了一个按钮,连带四个页面一起回归;再往下走,代码里抄来抄去的片段连原作者都认不出来。而当你真正要跨端落地——把一套 Flutter 工程带到鸿蒙侧——这些问题就不再只是“看着难受”,而是实打实的研发风险被放大了。团队里没人敢拍胸脯说“这次改动没问题”,因为没人能证明它没问题。
这个场景正好是我最近在推进的工作:给一套 Flutter 三方库做鸿蒙化适配,同时把静态代码治理能力完整落到端侧工程化质量巡检里。核心工具是 dart_code_metrics,目标是把复杂度分析、代码重复率检测、自动化规则修复这三板斧全部跑起来,并且能在日常开发流程里自动执行、自动卡点。这篇文章就是这次适配过程的全记录,从思路到步骤到踩坑,尽量写成一份可以直接照抄的作业。适合正在做鸿蒙化 Flutter 工程改造、或者打算把代码质量工具链引入日常实践的团队参考。
1. 项目整体设计与核心思路拆解
1.1 静态代码治理到底在治什么
先说个基本认知。静态代码治理的核心不是“抓 bug”,而是把代码的坏味道提前暴露出来。它不运行程序、不触发业务逻辑,而是通过扫描代码结构,找出那些容易引发后患的地方。dart_code_metrics 做得比较全的点在于,它不只是给你一个“这里的代码很烂”的结论,而是把“烂”这件事拆成了三个可量化的维度。
第一个维度是复杂度。一个函数如果有几十个分支、十几个 if 嵌套、靠 return 串起来的逻辑链,那它未来的命运基本只有两个:要么没人敢碰,要么谁碰谁出事。第二个维度是重复率。代码复制粘贴这种事,短期省了时间,长期是灾难,改了 A 处忘了 B 处,bug 就藏在这种不对称里。第三个维度是规则合规性。项目里约定的命名规范、参数顺序、废弃 API 使用,这些靠人 review 是看不完的,机器可以盯得死死的。
所以这套工具的定位,我个人的理解是:它是把“代码规范”这件事从口头约定沉淀成机器可执行规则的第一步。它替代不了代码 review,但它能把 review 的精力从“你这里缩进不对”“你这个变量名起得有问题”这种琐碎中解放出来,让人去关注真正的架构问题。
1.2 鸿蒙化适配的真正难点不在工具本身
很多人听到“鸿蒙化适配”,第一反应是工具能不能在鸿蒙上跑。这里必须澄清一下:dart_code_metrics 是纯 Dart 实现的命令行工具,理论上只要是 Dart 环境它就能跑。鸿蒙化 Flutter 工程的底层运行时再特殊,它最终的源码还是 Dart 和 Flutter 这一套,分析器解析的也是同一个语言规范。所以工具层面的兼容问题,远没有大家想象的那么严重。
真正的难点在三个地方。第一,鸿蒙化工程往往是从已有 Android/iOS 工程迁移过来的,代码现状和标准 Flutter 工程不同,有大量条件编译、平台通道、自定义构建脚本,扫描的时候很容易误报或者漏报。第二,CI/CD 流水线和端侧工具链是分开搭建的,Android 侧的报告管道、质量门槛、问题卡点在鸿蒙侧未必有对应实现,你需要在鸿蒙的构建流程里重新接一遍。第三,团队的执行惯性还没形成,规则配得太松没意义,太严格开发同学直接不推代码,这个度很难拿捏。
我在这次适配里的做法是:不追求一次把所有规则全开,先把“复杂度、重复率、自动修复”这三个最直观、最能体现价值的能力落地,让工具先在团队里形成正向反馈,再逐步加码。说白了,质量工具要能被人用起来,必须让人觉得它是在帮忙,而不是找茬。
1.3 端侧工程化质量巡检的真实闭环
还有一个容易忽略的点,就是“巡检”两个字背后的工程化链路。静态分析工具本身只是一条命令,真正有价值的是把它纳入日常开发流程,形成一个端侧工程质量检查的闭环。
我在项目里设计的链路大致是这样的:开发提交代码 -> 触发 CI 流水线 -> 流水线里跑 dart_code_metrics 扫描 -> 产生结构化报告 -> 对照质量基线判定是否通过 -> 通过则正常合入,不通过则自动在 MR 界面留评论、让作者回去改。这样一个闭环下来,工具就不只是偶尔跑一下的“仪式感”,而是每一行代码提交都必须过的硬门槛。
考虑到鸿蒙化工程的构建链路和 Android 不太一样,我后面还会专门讲到 CI 集成时需要注意的脚本挂载和产物路径问题。这里先建立一个认知:工具落地,比工具本身更值钱的是流程设计。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工具选型与适配方案设计
2.1 为什么选中 dart_code_metrics 而不是其他方案
在考虑鸿蒙化场景时,我其实先做过一轮工具对比。市面上的静态分析工具可以分两类:一类是通用的多语言工具,比如 SonarQube、Coverity;另一类是 Dart/Flutter 生态的原生工具,比如 flutter analyze、dart_code_metrics、以及后来的 custom_lint。
通用工具的优势是覆盖面广,能和公司已有的质量平台打通,但劣势也很明显:对 Dart 语言特性的理解不够深,很多 Flutter 特有的模式——比如 BuildContext、StatefulWidget 生命周期、异步流——在通用工具眼里就是普通代码,分析不出语义层面的问题。而且通用工具往往需要一个服务端承载,适配鸿蒙化这种定制化程度很高的工程,不管是规则配置还是报告对接都费劲。
dart_code_metrics 的优势在于,它是直接把“Dart 代码里最常见的坏味道”作为分析对象的。它有专门针对长参数列表、嵌套过深、复杂度超标这类问题的度量指标,而且这些指标可以直接导出成机器可读的 JSON 格式,方便后续对接自定义的巡检系统。这一点在后面做端侧质量关卡的时候帮了大忙。
我当时还考虑过 custom_lint —— 这个库的设计更现代,专门支持开发者编写自定义 lint 规则,而且能通过 IDE 插件实时反馈。但团队现状是,我们更需要一个能直接在 CI 里批量跑、能产出统一报告、规则开箱即用的工具,dart_code_metrics 更符合我们的诉求。这里没有谁绝对更好,只是选型要看你的落地场景。
2.2 适配方案的整体架构与分层
这次适配我没有直接把工具源码拉下来改,而是采用了“分层适配”的思路,尽可能让工具本身保持黑盒状态。
底层是 dart_code_metrics 的命令行服务和它依赖的 analyzer 引擎,这一层不动,用源码依赖加本地缓存的方式固定版本。中间层是配置文件层,包含 metrics 配置、规则开关、度量阈值、忽略文件清单,这一层是鸿蒙化适配的主战场,因为鸿蒙工程会有很多特有的目录命名和构建产物,需要在这层做精细化排除。上层是流水线集成层,包含扫描脚本、报告生成脚本、质量基线判定逻辑,这层要做得足够薄,只做数据搬运和结果判断,方便在 Android、iOS、鸿蒙三端之间复用同一个报告格式。
这套分层的最大好处是,工具升级的时候,不需要改动上层的流水线逻辑;鸿蒙工程结构调整的时候,也不需要碰工具本体,改配置和脚本就行。我在实际操作中遇到过好几次版本兼容性问题,因为把配置和代码解耦了,最后都能通过调整中间层快速绕过,不至于回滚整个适配方案。
2.3 鸿蒙工程结构调整带来的扫描边界问题
说到鸿蒙工程的目录结构,这是我在适配中踩得最凶的一个坑。一个典型的鸿蒙化 Flutter 工程,往往会有这样一些目录特征:根目录下会有 ohos 目录承载原生侧代码;Flutter 模块可能被组织成独立的 har 包或者直接嵌入主工程的 module;为了兼容多端构建,还会有大量被 gitignore 过滤掉的生成目录。
如果不做任何处理直接运行 dart_code_metrics,它会把 ohos 目录里那些不属于 Dart 生态的文件也算进去,或者把 build、.dart_tool 等生成目录里的临时拷贝也扫了,结果就是报告里出现一堆根本不是人写的“代码”,噪音远比信号多。
解决这个问题,核心是维护好一份精确的 ignore 清单。我会在后面的配置章节里给出完整的 exclude 示例。这里先强调一个原则:静态分析的扫描边界一定要跟工程的实际源码边界严格对齐,多扫一个目录不叫严谨,叫浪费机器算力和污染报告数据。
3. 环境准备与集成配置实操
3.1 环境准备清单
先把环境装好。因为 dart_code_metrics 是命令行工具,它本身不依赖 Flutter SDK 的图形界面,只要有 Dart 环境就能跑。但考虑到要分析 Flutter 代码,通常还是会依赖 Flutter SDK 自带的 Dart。所以环境准备其实很朴素:
- 安装 Flutter SDK,版本建议 3.x 以上,太老的版本 analyzer 内核不一致,容易触发兼容问题;
- 确认 Dart SDK 版本和 Flutter 绑定,命令行里
dart --version看得到; - 在项目里初始化 pubspec.yaml,如果没有对应的依赖管理环境,先跑
flutter pub get把基础依赖拉通; - 如果是鸿蒙化工程,还需要确认 ohos 目录不影响 pub 的解析路径。这一步通常没太多问题,但确实遇到过由于仓库里有多个 pubspec.yaml 导致依赖解析混乱的情况。
这次适配我用的环境组合是:Flutter 3.16 系列 + dart_code_metrics 5.x,过程和结论都以这套组合为准。如果你用的是更新或者更旧的版本,每个版本的具体命令参数可能会有一点点出入,但整体思路完全通用。
3.2 在 pubspec.yaml 中挂载依赖
依赖挂载有两条路。一条是直接在项目的 dev_dependencies 里写入:
yaml复制dev_dependencies:
dart_code_metrics: ^5.3.0
跑完 flutter pub get 之后,命令 dart run dart_code_metrics 就能直接用了。另一条是如果你不想把工具变成项目的直接依赖,而是想让它作为一个独立的分析服务运行,可以把工具装在一个统一的工具仓库里,通过绝对路径或者软链接指向目标工程。
我在鸿蒙化适配中优先采用了第二种方式。原因是:鸿蒙化工程往往有多个 Flutter 模块,如果每个模块都挂一份工具依赖,依赖版本容易漂移,升级的时候要同步改好几个 pubspec.yaml。用一个独立的工具容器统一管理版本,再通过参数指定要扫描的工程路径,运维成本会低很多。
当然,独立容器的方式也有代价:命令参数的写法要更完整,不能依赖当前目录的隐式配置。这算是技术取舍,对团队后期维护更友好,值得多写几行命令。
3.3 最小可用的默认配置集
无论哪种挂载方式,配置都要靠 analysis_options.yaml 来承载。dart_code_metrics 的约定是先通过 analyzer 段的插件引入,再在 dart_code_metrics 段里做具体配置。我给出一个最小可用的默认集合:
yaml复制analyzer:
plugins:
- dart_code_metrics
dart_code_metrics:
metrics:
cyclomatic-complexity: 20
maximum-nesting-level: 5
source-lines-of-code: 100
number-of-parameters: 8
number-of-methods: 25
weight-of-class: 0.33
metrics-exclude:
- test/**
- integration_test/**
rules:
- avoid-long-functions
- avoid-nested-conditions
- avoid-redundant-arguments
rules-exclude:
- test/**
anti-patterns:
- long-method
- long-parameter-list
exclude:
- build/**
- .dart_tool/**
- ohos/**
- ios/**
- android/**
这个配置看起来不复杂,但每个字段背后都有讲究:
metrics 里定义的是数值型阈值,cyclomatic-complexity 是圈复杂度上限,我团队定的 20,也就是说一个方法的独立路径数超过 20 就告警。maximum-nesting-level 是最大嵌套深度,超过 5 层基本就是“金字塔代码”了。source-lines-of-code 是单文件代码行数,100 行是比较宽松的标准,如果团队已经养成了小文件习惯,可以压到 60。
rules 是具体的行为规则,比如 avoid-long-functions 禁用长函数,avoid-nested-conditions 禁止过深嵌套。anti-patterns 那三项是结构层面的坏味道检测,和 rules 有重叠但也有区别。rules 更像“行为规范”,anti-patterns 更像“结构模式识别”,两者都开才能覆盖得更全面。
exclude 是最关键的字段,尤其对鸿蒙化工程,ohos、ios、android 这种原生侧目录必须排除,因为里面可能有从其他仓库拷贝过去的 Dart 文件,扫了不仅慢,而且会污染真实统计数字。
这里我补充一个原则:默认配置宁可先松后紧。第一次接入时,把阈值调得宽松一点,让报告先出来,团队熟悉流程后再逐步收紧。直接上最严格配置的结果就是——开发者被一屏又一屏的告警吓到,工具直接变成“没人看的地板文件”。
3.4 分析命令与报告落地
配置写好后,跑一次扫描的命令大概长这样:
bash复制dart run dart_code_metrics analyze . --reporter=json > metrics_report.json
这里我推荐 json 输出而不是默认的文本输出。理由是:文本输出适合人肉看,但流水线里需要机器读,JSON 才是工程化消费的首选。拿到 metrics_report.json 之后,你可以做两件事:把它喂给质量平台展示趋势;或者直接在当前流水线里解析关键字段,判断本次提交是否突破质量基线。
另外还有一个很容易踩的坑:如果项目里同时存在多个 pubspec.yaml,dart run 的时候可能会选错工作目录,导致报告内容为空或者报“无法解析依赖”。我建议在命令里显式指定分析根目录,不要依赖当前工作目录的隐式行为,命令风格统一成:
bash复制dart run dart_code_metrics analyze lib --root=. --reporter=json
这样即使流水线在鸿蒙工程的 ohos 目录下启动,也能正确回到 lib 目录执行分析。
4. 复杂度分析与重复率检测的落地细节
4.1 圈复杂度的计算逻辑和实际含义
圈复杂度这个东西,很多人只记住了“数字越小越好”,但没搞懂数字是怎么算出来的,所以看到报告上标红的函数时完全不知道该从哪里下手优化。简单来说,一个函数的圈复杂度等于它的独立路径数量:顺序执行是一条路径,遇到 if、for、while、case 这些分支点,每多一个判断,独立路径就多一条。代码里有一个大 if-elseif-else 链,每个分支都要算一次。
dart_code_metrics 在统计时除了基础的分支逻辑,还会把 ?.、??= 这类 Dart 特有的空安全语法也纳入决策点计算。这是它比通用工具更适合 Flutter 项目的地方,因为 Dart 3 之后空安全语法使用频率极高,如果不计入复杂度,那一个大量使用 ?. 的函数在语义上已经很难读懂,但数字上却显示很健康,就失真了。
实际处理时我的建议是:复杂度超标的函数,不要试图“优化”它,而是要“拆”。把一个 40 复杂度的函数拆成 3 个 15 复杂度的函数,对可维护性的提升是实打实的。拆的时候记住一个原则:每个子函数只做一件事,函数名把这件事说清楚,宁可多写几个函数,不要让读者在一个屏幕里读不完一段逻辑。
4.2 从报告数据到质量门槛
有了复杂度报告之后,怎么演变成“质量门槛”,这是端侧工程化巡检的核心环节。我通常的做法是:在 CI 脚本里解析 metrics_report.json,把每个文件的最大复杂度、总代码行数、重复代码块数量这几个字段抽出来,和预设基线做比较。
举个例子,假设基线是“圈复杂度大于 20 的文件数量不得超过 5 个”,那 CI 的判定逻辑就是:本 MR 新增或修改的文件里,如果出现一个复杂度超标的文件,流水线直接失败;如果只是既有存量文件超标,则只记录不阻断,留到技术债清理专项里统一处理。
这样设计的好处是,存量问题不会被无限放大变成团队的日常负担,增量问题却被严格拦截,长期下去代码健康度只会越来越好。对于从 Android/iOS 迁移过来的鸿蒙化工程,这个策略尤其重要,因为你不太可能要求团队在迁移的同时把所有历史代码都重构一遍,但你可以要求他们保证“新写的代码不许脏”。
4.3 代码重复率检测的阈值标定
代码重复率是另一个容易“精神内耗”的指标。dart_code_metrics 会对代码做相似度检测,找出连续相同或者近似的代码块。这里有一个很关键的参数需要标定:最小的重复行数。如果你把重复阈值设成 5 行,报告里会充满各种琐碎的重复;如果设成 50 行,那真正的“大段复制”才能被暴露出来。
我的习惯是从 20 行起步,因为 20 行以上的重复,基本意味着某一个逻辑块被整体复制了一份。对于鸿蒙化工程来说,这种大段重复往往出现在“为了兼容多端而做的条件编译副本”里——由于平台差异,开发者直接把同一套逻辑复制一份改了改调用方式,这反而是重复检测最有价值的发现。
发现重复之后怎么处理?不要盲目抽公共方法。有时候两个重复块只是因为碰巧长得像,各自的业务上下文完全不同,强行抽取反而让代码更抽象、更难懂。正确做法是:先读两块代码的差异点,判断它们是不是同一个业务语义的两种变体。如果是,抽公共函数加上差异参数;如果不是,保留现状,在工具配置里加入 --ignore 路径或者标记为“合理重复”。
4.4 我踩过的重复率误报深坑
这里分享一个让我印象深刻的坑。有一个鸿蒙侧的页面适配逻辑,我把整个 Flutter 模块和原生交互代码放在同一个仓库里管理,结果 dart_code_metrics 把两个不同 plugin 文件里对原生事件的处理代码识别成了重复块。其实那两份代码只是调用了同一个事件分发对象,结构类似,但业务完全不同,属于“形似而神不似”的误报。
解决方式是在配置里按文件维度添加排除注释,或者换用更严格的相似度阈值。工具提供了在源码里通过注释控制开关的能力,我用了之后误报率才算降下来。这也是我强烈建议——统计指标要谨慎落地,不要拿到报告就直接开始“暴力认同”。机器给的是线索,最终判断权在人。
5. 自动化规则修复的工程化实践
5.1 能自动修的规则有哪些边界
自动化修复,是 dart_code_metrics 里比较有吸引力但也最容易被误解的功能。它能帮你自动修的,不是“重构逻辑”,而是“改写表象”。什么意思?比如一个函数参数数量超标,它不是帮你拆函数,而是帮你把超长参数列表的换行格式整理好。再比如某条命名规则要求变量必须用 lowerCamelCase,你可以跑一把修复命令,让所有不合规的标识符一次性改正。
这种“表层修复”的价值是被很多人低估的。真实场景里,一个工程里可能散落着上百处命名不规范、几十处 import 顺序错误,让开发者一个个手动改根本不现实,自动化修复能把这部分成本直接降为零。
但你必须清醒地认识到边界:任何需要业务理解的修复,都不会出现在自动修复清单里。圈复杂度高它不会帮你拆函数,长方法它不会帮你提取公共逻辑,重复代码它也不会帮你消除。这些仍然是人的工作。
5.2 实战修复流程与质量保障
我在鸿蒙化工程里执行修复的流程是,先在本地分支上把修复跑一遍,但注意不要直接让工具处理全仓库。正确姿势是,用 git diff 把本次改动涉及的代码固定住,只让工具修这些文件,避免“修复”出意想不到的大范围漂移。
bash复制dart run dart_code_metrics analyze lib/pages/xxx_page.dart --fix
--fix 参数是自动修复的开关,配合文件路径,把修复范围锁死。修复完成后立刻执行两件事:一个是 flutter analyze 确认没有新增编译错误;另一个是跑一遍与该文件相关的单元测试,因为极少数情况下自动修复会让本来是语义故意的代码变得“不那么故意”。
从实际操作来看,自动修复最大的价值是保证“代码风格的一致性”。团队里每个成员的手感不一样,有人喜欢通过空行分段,有人喜欢一句话一行,这些微小的差异过去只能靠 review 时人肉纠正,现在机器直接帮你统一了。这比它能省 5 分钟还是 10 分钟更值得看重。
5.3 修复动作纳入流水线的策略
自动化修复不一定非要交给开发者在本地跑,我见过更高效的玩法是把“自动修复”能力也集成进流水线。但这里必须留一个口子:流水线里的自动修复,应该只修安全规则,不修可能影响行为的规则。
具体来说,我将规则分成三类。第一类是纯格式类,比如 import 顺序、尾部逗号、换行风格,这类可以全自动跑,机器改了绝对无害。第二类是命名类,例如必须用 lowerCamelCase,这类基本安全,但要对注释和字符串内容做保护。第三类是结构类,比如废弃旧 API 替换、参数数量优化,这类需要人工确认,不能自动。
我用一个简单的规则评级表来管理这个策略:
| 规则类型 | 典型示例 | 是否自动修复 | 风险等级 |
|---|---|---|---|
| 格式类 | import 顺序、空行、换行 | 是 | 无风险 |
| 命名类 | 标识符风格统一 | 是 | 低风险 |
| 结构类 | 参数列表简化、API 替换 | 否 | 中高风险 |
在流水线上我只开放前两类,结构类的修复建议在本地 IDE 辅助完成。这套做法最大程度保留了机器效率,又降低了意外事故的概率。
5.4 自动化修复对鸿蒙工程的额外收益
鸿蒙化工程因为经常是从多套旧仓库合并过来的,代码风格不统一的问题会被放大。一个典型现象是:同一个事件处理方法,在 Android 侧工程里叫 handleEvent,在鸿蒙侧副本里叫 doEvent,功能一模一样,命名却完全不同。这种偏差不会产生 bug,但会给后续跨端维护的同事制造大量不必要的心理负担。
自动规则修复在这个场景下的价值不仅仅是“统一命名”,更是为跨端代码对比、结构化 diff 提供一个更干净的基础。当两端的代码风格趋于一致,工具才能更准确地发现真正的逻辑差异。这个收益是我适配初期没有预料到的,但它反而成为团队持续使用工具的最大动力之一。
6. 常见问题排查与经验速查
6.1 高频问题与解决方案速查表
适配过程中我把遇到的问题都做了记录,整理成一张速查表,遇到类似问题可以直接对照排查:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
命令执行报错,提示找不到 dart_code_metrics |
依赖未正确安装,pub 缓存路径异常 | 重新执行 flutter pub get,确认 pubspec.yaml 中依赖书写在 dev_dependencies |
| 扫描结果中 ohos 目录的 Dart 文件数量异常 | 配置的 exclude 没写全,或路径分段符不匹配项目结构 | 在 exclude 中使用 ohos/** 通配符,并确认配置层级位置 |
| 分析精度比预期低,许多明显的坏味道没被发现 | 规则集没有完全启用,或者配置里开了过多的 exclude | 对照官方规则文档逐项激活,先开全量规则再按需关闭 |
| 报告文件生成了但内容为空 | 扫描根目录指定错误,解析到了不包含 Dart 源码的目录 | 在命令中显式添加 --root= 参数指向项目根目录 |
| 修复命令提示“没有可修复项” | 当前文件对应的规则可能不属于自动修复范围 | 检查规则是否标注为 fixable,换用可 fix 的规则验证 |
| 和 flutter analyze 结果冲突 | 两套工具使用的 analyzer 版本内核不一致 | 锁定 Flutter SDK 版本,避免跨版本混用 |
这些问题的共同特点是,大多数不是工具坏了,而是使用姿势不对。工具链的调试最忌讳堆时间,一定要先确认环境和配置,再看堆栈和日志。
6.2 适配过程中三个最容易忽视的隐患
第一个隐患是“忽略文件写太狠”。有些开发者为了省事,直接把整个模块目录加进 exclude,结果该模块的代码质量完全脱离监控。时间一长,这个模块就成了团队的“法外之地”,架构腐烂的速度比别处快得多。我的建议是:宁可接受一部分误报,也要保证覆盖面,真要排除某个目录,必须在配置注释里写明理由和责任人。
第二个隐患是“只管扫描不管处置”。我从一开始对接流水线时就明确了原则:扫描报告出来之后必须要有下游动作,要么阻断合入,要么生成技术债工单,要么自动指派给提交者。没有处置路径的扫描结果,最终只会变成一封没人读的定时邮件,这比不扫还消耗团队信任。
第三个隐患是“质量基线长期不更新”。代码库是在演进的,团队能力也在提高,半年前定的复杂度阈值,现在看来可能已经松得离谱。我建议每季度回顾一次报告数据分布,把基线上调或下调到合理区间,让质量门槛始终匹配当下团队的实际情况。基线不是一成不变的条款,而是有生命力的管理工具。
6.3 一个实战场景的复盘
最后复盘一个让我印象很深的场景。某个大型页面模块从 Android 侧迁移到鸿蒙侧,原计划是直接搬运大部分逻辑,只改少量平台通道调用。迁移完成后,团队按惯例跑了一次全量扫描,结果那个模块的重复率飙红了,而且复杂度超标的文件数一下子多了好几个。
刚开始大家觉得是误报,因为代码逻辑明明是从旧工程里原样搬来的,“原来能跑,搬到鸿蒙怎么就不行了”。后来仔细查了 diff,发现迁移过程中为了适配鸿蒙的页面生命周期,开发者对部分方法做了插入式修改——原来的函数体中间被硬塞了十几个平台分支判断。代码行数没变多少,但每个函数里都长了一截“带刺”的逻辑,复杂度就这么被顶上去了。
那次之后我们做了一个决定:鸿蒙化迁移的代码也必须走同样的质量门槛,不允许“因为历史包袱就豁免”。结果后面几个模块的迁移质量有了肉眼可见的改善,因为开发者在写平台适配层时,会专门抽一层薄薄的 adapter,而不是把平台判断散落到每个业务函数里。这就是工具从“检查”变成“指导”的过程——标准一旦立住了,行为就会跟着改变。
6.4 工具链维护心得
最后聊点工具链本身的维护。dart_code_metrics 这种命令行工具,最大的维护成本其实不是版本升级,而是“规则集和工程演进节奏不匹配”。工程在不断发展,目录会调整,架构会重组,之前精心设计的 exclude 清单可能三个月后就失效了。
因此我在团队里推行了一套“工具链一切皆配置”的约定:任何涉及扫描范围、阈值、规则的调整,都必须在配置文件的注释里写明调整原因和决策时间。这样后续任何一个人接手,都能理解当初为什么把某个阈值设成 20 而不是 15,为什么排除某个目录,而不是一脸懵地删掉注释、让问题重新暴露出来。
还有一个可迁移的经验:这类工具比较适合以“独立工具包”的形式维护,而不是和具体业务工程耦合在一起。工具包自己维护一份稳定的配置模板,不同业务工程通过继承和覆盖来定制,既保证了全公司或全团队扫描口径的统一,又保留了单项目的灵活性。这个思路在鸿蒙化、Android、iOS 多端并行的时候尤其重要。
自动化规则修复的边界、重复率误报的处理、质量门槛的增量拦截——这些坑如果能提前避开,你的适配过程会顺畅很多。最少先跑通最小可用配置,让报告出来,然后结合自己工程的实际情况逐步调优。毕竟质量工具最终的目的是给团队提供安全感,而不是制造新一轮“指标焦虑”。我觉得先把这个定位想清楚,后续的所有配置和流程设计都会顺理成章。
