
1. 适配鸿蒙前先拆开 workiva_analysis_options 看看它为什么敢叫“工业级”接触过不少 Flutter 工程大部分团队的代码质量基线其实是相当脆弱的。默认的flutter_lints只能管住变量名、空安全、废弃 API 之类的基础问题但真正让一个大型工程保持健康的往往是更严格、更体系化的规则集。workiva_analysis_options正是这样一套被 Workiva 内部大量 Dart/Flutter 项目长期验证过的开源分析配置。我做鸿蒙端适配这件事时第一反应不是先把 Flutter SDK 换成鸿蒙版本去跑构建而是先把这套质量审计体系想清楚怎么迁过去因为代码能不能编译是一回事迁完之后代码质量会不会劣化是另一回事。1.1 它和官方 flutter_lints 的核心差别规则密度和自定义 lint官方flutter_lints的定位是给大多数 Flutter 项目一个“开箱即用且不过分苛刻”的默认配置它解决的更多是高频、通用的问题。但workiva_analysis_options的定位完全不同它是给大型团队、多模块工程、长期迭代的多平台项目用的。它内部的规则密度比官方配置高一个数量级而且不只是“多开了一些开关”它真正厉害的地方在于自带一组自定义 lint 规则这些规则不是简单的正则匹配而是基于 AST 的分析逻辑能检查出很多“只在团队规范里写过、但没有机器强制执行”的架构问题。我举个例子很多团队规范里都会写“ui 层不能直接 import data 层的内部实现”但这句话在 code review 时靠人眼盯总有漏网之鱼。如果把它写成一条自定义 lint 规则任何违反这个约定的代码在 IDE 里就会直接标红CI 上也会直接失败。这才是工业级的意义不是规则多而是把团队约定变成了编译器级别的约束。1.2 理解它的分层配置v2.yaml、测试配置、示例工程配置workiva_analysis_options的配置入口不是单一文件它针对不同代码场景做了分层。普通 Dart/Flutter 生产代码用一套配置测试代码用单独一套配置示例工程又有一套配置。这个设计很关键因为测试代码和示例代码的诉求不同。测试代码里允许出现大量 mock、动态调用如果拿生产代码的规则去卡测试就会出现一堆无意义的噪音但测试代码同样需要避免明显的反模式比如超长的测试方法、没有断言的测试。在鸿蒙工程里做适配时这种分层配置的优势会被放大。因为鸿蒙端工程通常还混着 ohos 目录、原生桥接代码、平台通道测试代码代码性质差异很大。如果不对规则做分层跑一次flutter analyze会看到大量和自己业务无关的噪音团队很快就会失去耐心然后退回到“不管规则”的状态那这次适配就等于白做了。1.3 一个 include 能带起整条规则链但代价是版本耦合接入这套规则集本身不复杂在pubspec.yaml的dev_dependencies里加上依赖然后在analysis_options.yaml里写一行 include 就够了include: package:workiva_analysis_options/v2.yaml但这里有个容易被忽略的点workiva_analysis_options内部耦合了特定版本的analyzer。Flutter SDK 自带的 Dart SDK 版本决定了analyzer的上限如果鸿蒙 Flutter SDK 的版本比上游慢而新版本 workiva 又需要更强的analyzerAPI就可能出现自定义 lint 插件加载失败或者 analyze 执行到一半直接报错的问题。后面我在踩坑部分会详细说这个这里只先给一个结论做鸿蒙适配时分析规则集本身的依赖版本优先级不亚于 Flutter SDK 版本。很多人只盯着 SDK 能不能跑通构建忽略了analyzer层面的兼容性这是第一层最容易翻车的地方。2. 鸿蒙工程更复杂先做一轮环境兼容性排查再动手普通 Flutter 工程跑flutter analyze是很顺的但鸿蒙工程不是。根目录下多出来的 ohos 目录、鸿蒙专用 Flutter SDK、甚至构建工具的差异都会影响分析结果。我在做适配时严格按照下面几个维度先排查了一遍事实证明排查顺序也很重要如果顺序错了很容易被表象误导。2.1 Flutter SDK 版本和 analyzer 版本的对齐检查鸿蒙 Flutter SDK 的大部分能力和上游 Flutter 一致但它的发布节奏通常不会和上游完全同步版本会滞后。flutter --version看到的 Dart 版本直接决定了当前环境能支持多新版本的analyzer。实战里我用的检查方法是这样的先在工程根目录执行flutter --version拿到 Dart/Flutter 版本号然后去 pub cache 里看workiva_analysis_options的 pubspec 文件看它对analyzer的版本约束区间。两个放在一起看就能判断当前鸿蒙 SDK 是否满足要求。如果发现 SDK 自带的analyzer版本过低我的建议是优先寻找满足约束的鸿蒙 Flutter SDK 版本而不是用dependency_overrides强行拉高analyzer版本。强行拉版本短期内能跑过flutter analyze但自定义 lint 是直接调用analyzer的 AST API 的版本不匹配会导致插件在运行时抛异常反而更难排查。2.2 ohos 目录不是普通的业务目录排除规则要重新设计鸿蒙工程的典型目录结构里ohos 目录放的是 ArkTS 和原生侧代码正常情况下 analyzer 不会去解析这些非 Dart 文件。但现实工程里ohos 目录下经常混着一部分用于平台桥接的 Dart 文件这些文件又是必须被分析覆盖的。这就需要一个更精细的排除策略。我当时的处理方式是这样在根目录analysis_options.yaml的analyzer.exclude里排掉ohos/**、build/**等原生目录analyzer: exclude: - ohos/** - build/** - lib/**/*.g.dart然后把桥接用的 Dart 代码单独收敛到一个lib/harmony_bridge/目录里再给这个目录单独放一个analysis_options.yaml通过相对路径 include 根目录的规则集。这样既不会让 analyzer 去扫大量原生代码也不会让桥接层代码逃出规则监管。2.3 hdc 参与下的真机验证流程鸿蒙端的调试工具是 hdc 而不是 adb这个差异在 lint 验证阶段也会体现出来。静态分析本身不需要连接设备但端侧工程经常会有一些依赖系统能力配置的资源文件比如权限声明、能力配置这些错误在静态分析阶段不一定能完全暴露需要配合一次真机启动验证。我个人习惯的验证流程是先flutter pub get再flutter analyze然后用hdc list targets检查设备连接状态确认设备在线后再安装运行。连接鸿蒙平板的时候hdc偶尔会遇到设备无法识别的问题可以先重启 hdc 服务再试一次这个和 adb 的套路类似。下面的表格是我当时做的检查清单可以直接参考检查项普通 Flutter 工程鸿蒙 Flutter 工程设备工具adbhdcSDK 来源Flutter 官方 SDK鸿蒙适配版 Flutter SDK新增目录android/iosohos/analyze 扫描范围lib/testlib/test 桥接目录需显式控制analyzer 版本随官方 SDK随鸿蒙 SDK可能滞后3. 架构健康度检测把“架构不能坏”从口号变成 linter 规则标题里提到“端侧工程架构健康度自动检测”这是整个适配过程中最有价值、也最容易被误解的部分。很多人觉得架构健康度就是玄学其实不是架构健康度完全可以量化关键是找到正确的切入点。3.1 架构健康度到底有哪些可量化的维度我理解的端侧工程架构健康度至少包含四个维度依赖方向、模块边界、代码坏味道、死代码与重复代码。依赖方向是最常见的比如 ui 层不能反向依赖 data 层、domain 层不能依赖框架细节。模块边界则是 feature_a 不能直接 import feature_b 的内部实现只能通过对外暴露的接口通信。代码坏味道包括超大类、超长方法、过深的嵌套逻辑。死代码和重复代码则包括无效 import、未使用的文件、被绕过接口的重复实现。这四个维度里前两个是可以通过自定义 lint 规则直接检查的后两个需要用工具链配合。workiva 自带规则能解决一部分坏味道问题但架构约束必须自己写这就是整个适配工作的核心增量。3.2 自定义 lint 规则怎么写一个基于 AST 的骨架示例如果团队有精力维护 analyzer 插件架构约束可以直接做成 IDE 可感知的 lint 规则。下面是一个极简示意展示的是“UI 层禁止直接 import data 层”这一条规则的实现思路class NoDataImportInUiRule extends DartLintRule { const NoDataImportInUiRule() : super( code: LintCode( no_data_import_in_ui, UI layer must not import data layer directly, ), ); override void run( CustomLintResolver resolver, ErrorReporter reporter, CustomLintContext context, ) { context.registry.addCompilationUnit((unit) { final filePath unit.declaredElement?.source.fullName ?? ; if (!filePath.contains(/presentation/)) { return; } for (final directive in unit.directives.whereTypeImportDirective()) { final uri directive.uri.stringValue ?? ; if (uri.contains(/data/) || uri.contains(/repositories/)) { reporter.reportLint(directive, violates layering constraint); } } }); } }这段代码的思路是每次分析一个 Dart 文件时先看文件路径是否落在presentation层如果是再遍历文件的所有 import 指令一旦看到 import 路径指向data或repositories目录就直接报一条 lint 错误。实际落地时规则会比这个复杂得多还要处理别名 import、part 文件、动态 import 等边界情况。但如果团队暂时不具备维护 analyzer 插件的能力我会推荐一个更朴素的方案。3.3 没有 AST 能力时用 import 依赖扫描脚本兜底写自定义 analyzer 插件需要维护成本而且对团队的平台工程能力要求比较高。我的做法是分两步走核心架构约束先上脚本在 CI 阶段卡住同时逐渐积累 analyzer 插件能力再迁移到实时 lint。依赖扫描脚本的思路非常直白递归扫描指定目录下所有.dart文件提取每个文件的 import 路径再和一张架构关系表做比对。架构关系表可以是一个 yaml 文件描述允许的依赖关系和禁止的依赖关系。这个方案虽然没有 IDE 实时提示但在 CI 上是绝对可靠的而且只要写一次后续成本很低。# 伪代码架构边界检查脚本 def check_layer_boundaries(): violations [] for dart_file in collect_dart_files(lib): layer detect_layer(dart_file.path) imports extract_imports(dart_file) for imp in imports: if not is_allowed(layer, imp.target_layer): violations.append(f{dart_file.path} - {imp}) return violations把规则落在脚本里的好处是它不依赖特定 IDE 插件也不依赖 analyzer 版本鸿蒙 Flutter SDK 版本再怎么变这个脚本都能稳定运行。4. 从全量报红到增量收敛一套能落地的接入执行路径说实话一个存量庞大的 Flutter 工程直接全量开启 workiva 规则第一反应基本都是“完了这代码没法要了”。几千条 warning 如果一股脑推给团队结果只可能是集体沉默然后规则被悄悄移除。所以接入策略比规则本身更重要。4.1 完整接入五步走我的经验是把接入过程拆成五个阶段每个阶段都有明确的完成标准。第一步是接入并清零 error。把 include 改成 workiva 后先只关注 error 级别的报错这些通常是不安全调用、废弃 API、类型问题属于必须修的高风险项。一个大型工程里 error 数量一般不会太离谱这个阶段通常一周内能收尾。第二步是建立 warning baseline。把flutter analyze的输出保存下来按目录/模块聚合数量形成一个存量问题清单。这一步的目标不是清零而是让团队知道“历史问题有多少分布在哪”。第三步是新代码零 warning。从接入那天起CI 里对新增代码执行零容忍策略。这里需要一点工具支持简单做法是用 git diff 拿到变更文件只对变更文件跑 analyze再检查数量是否超过基线。第四步是按模块消减存量。把 baseline 里的问题按模块拆给对应 owner利用每天的碎片时间逐步修复。建议修的类型优先级从高风险到低风险废弃 API 和空安全相关排最前代码风格类最后。第五步是接入 CI用一套脚本固化整个流程。CI 里每次提交跑flutter pub get和flutter analyze同时跑我前面说的架构边界检查脚本。任何一步失败都直接阻断合并。4.2 用基线机制降低团队抵触情绪存量问题的处理上我最推荐的是“基线 逐步消减”的方式而不是“限期清零”。把 baseline 文件纳入代码库管理CI 只做“新产生的违规不能超过基线”这个判断。这样团队不需要背历史包袱但新代码质量又能被牢牢卡住。baseline 文件的格式可以简单到就是一个带行数的文本文件也可以用 JSON 格式方便比对。我的建议是至少记录三个信息违规文件、违规规则、违规行号。有了行号后续修问题的时候可以精准定位。4.3 团队推广时的一个小技巧推广阶段最容易听到的反馈是“规则太多找不到重点”。我的经验是先挑架构约束类规则重点讲因为这类规则直接和系统稳定性挂钩容易被理解。风格类规则先不急着强调等团队习惯了整体的技术氛围风格类问题自然就不是主要矛盾了。5. 踩坑实录这次适配里最折腾我的三个问题如果只看官方文档会以为切换analysis_options.yaml两分钟就完了。真实情况是从复制配置到完全跑通至少有一个周末是花在排查各种诡异问题上的。下面三个坑是我这次鸿蒙化适配过程中真实遇到过的每个都有完整的排查链路建议收藏备用。5.1 自定义 lint 插件在鸿蒙 SDK 上完全不生效现象非常明确flutter analyze执行成功输出里没有任何自定义规则产生的报错但手工检查明明有代码违反了规则。一开始我以为是规则条件写错了反复检查逻辑没有问题最后才怀疑到插件加载这个环节。排查链路是这样的先看pubspec.yaml里有没有正确声明 analyzer 插件再执行dart pub deps确认 workiva_analysis_options 真的被解析进来。确认之后我用一个故意违反规则的样例文件丢进工程结果还是不报那基本可以确定插件没有被加载。最后定位到的根因是鸿蒙 Flutter SDK 自带的 analyzer 版本与插件编译时用的版本不一致插件的入口类没有匹配上 analyzer 的插件发现机制。解决方案是调整依赖版本约束把 workiva 的版本退到和当前 SDK 兼容的旧版本。这也印证了我前面说的做鸿蒙适配时依赖版本检查和 SDK 检查应该放在同等优先级。5.2 ohos 原生目录混进了 analyze 扫描范围另一个常见问题是 ohos 目录下的文件被 analyzer 当作 Dart 工程代码处理。严格来说 analyzer 只解析.dart文件但问题往往出在 ohos 目录下有专门的 Dart 桥接代码这些代码被扫描后因为不满足 workiva 规则产生了一大堆误报。这个问题的排查其实很简单看 analyze 报错的文件路径凡是ohos/开头的都属于这一类。解决方案并不是简单地把整个 ohos 目录从 exclude 里排掉因为桥接代码本身也是需要质量约束的。我是把桥接代码统一收敛到一个独立目录然后单独给它一个analysis_options.yaml用相对路径 include 根规则同时按需关掉一些不适合桥接场景的规则。5.3 子工程自带 analysis_options.yaml绕过了总规则约束鸿蒙工程通常会有多个模块每个模块目录下可能有自己的analysis_options.yaml。如果某个子工程 include 的是默认 flutter_lints 而不是 workiva那么根目录的规则集对它是无效的整个质量体系就会出现一个巨大的漏洞。这个坑排查起来最隐蔽因为表面看所有模块都在同一个工程里但实际分析结果是分层的。我的解决方案是在 CI 里加一道检查遍历所有analysis_options.yaml确认每一份都 include 了同一个规则集不一致就直接报错。这一步相当于在现有代码仓库之上套了一层“规则集的规则”确保没有人无意中绕过规范。6. 适配效果怎么度量让数据自己说话代码质量审计工具上了线如果没有度量手段最后只会变成“上了个工具但没人知道有没有用”。我的做法是在适配完成后的第一个迭代周期内就开始收集几类硬指标。6.1 最值得盯的几个硬指标第一类规则激活情况比如 workiva 规则集共有多少条规则在当前工程生效其中有几条是自定义 lint这个数据能直接反映审计强度。第二类是静态分析报错趋势error/warning 数量的周变化曲线特别是新代码引入的违规数。第三类是架构约束违规数通过 CI 脚本检查出来结果这个数字应当随着迭代逐步下降而不是上升。下面的表格是我在一个模拟存量工程上记录的适配前后对比实际项目不同但思路类似指标适配前默认 flutter_lints适配后workiva 自定义规则生效规则总量约 150 条约 380 条error 数量120warning 数量756288存量 baseline 中架构违规跨层 import未检测已检测并逐步清零CI 阻断能力无有提交即可检测6.2 别只看数字还要看开发体验的变化代码质量审计体系真正生效的时候code review 的讨论重心会发生变化。之前会为了“这里要不要拆函数”“那里命名是不是准确”争论很久有了规则集兜底之后这些琐碎问题在编辑阶段就被解决了评审人可以集中精力讨论架构设计和业务逻辑。这个变化虽然不是硬指标但团队体感非常明显。我是这样向团队解释这次适配意义的代码质量审计不是给开发者添堵而是把容易重复犯的错误提前拦截把做正确的事情变成默认行为。鸿蒙化适配只是把整个体系延伸到了新的端侧战场让 Android、iOS、鸿蒙几端在同一个质量基线之下今后的迭代才不会因为平台差异出现质量洼地。