☰
鸿蒙 Flutter 工程 import_sorter 配置与代码规范治理
2026/10/29 5:56:02 网站建设 项目流程

1. 鸿蒙 Flutter 工程里,import 顺序为什么值得单独上工具

先说一个挺反直觉的现象:在鸿蒙(HarmonyOS)生态里做 Flutter 应用,团队吵得最多的往往不是 ohos 目录怎么配、har 包怎么引,而是合并请求里那几行 import 到底该不该放在一起。这事儿听着琐碎,但它确实是工程整洁度里最先崩掉、也最难靠人盯住的一块。import_sorter就是专门干这件事的 Dart 侧工具:它读取你的.dart文件,把 import 声明按既定规则重新排序、重新分组、补上或去掉组间空行,然后把结果写回文件。名字里的 sorter 说明了它的全部野心——只排序,不做别的。

我这两年在几个鸿蒙 Flutter 项目上推这套东西,最大的感受是:它的价值不在"排得好看",而在"排得一致且可被机器判定"。一个人排得再整齐,十个人就是十种风格;而import_sorter跑一遍之后,仓库里所有文件的引用区段长得一模一样,评审时没人再因为引用顺序提意见,代码检查也能把"没排序"当成一个硬性失败条件卡住。它属于那种装上去当天就见效、但配置不对会持续给人添堵的工具,所以下面我会把配置、坑和治理链路都拆开讲。

适合读这篇的人大概三类:正在把 Flutter 工程往鸿蒙侧迁移、需要重建代码规范的团队;手上有一堆历史遗留文件、想低成本做一次静态分析治理的维护者;以及单纯受够了 import 顺序争论、想一劳永逸定规矩的个人开发者。文中所有配置项和参数,我会标注哪些是工具本身的确定行为、哪些是基于常见版本实践的经验补充,你落地前都建议先在小范围跑一遍看 diff 再全量铺开。

1.1 格式化工具管不到的那块地

很多人第一反应是:dart format不就够了吗?不够。dart format处理的是缩进、换行、空格、尾逗号这类排版问题,它明确不重排 import 的顺序,也不会替你决定dart:组和package:组之间要不要空一行。也就是说,一个文件可以被dart format处理得非常"标准",同时 import 区段依旧是乱七八糟的——先引相对路径、再引三方包、中间夹一条dart:async,全都合法。

分析器层面倒是有directives_ordering这条 lint 规则,能提示"import 应该按字母序排列",但它只是提示,不会自动改;部分工具链版本的dart fix能修一部分这类提示,可它对分组粒度、组间空行、组内子分组这些细节几乎没有控制力,修完的结果也未必符合团队约定。所以现实就是:格式化工具负责排版,lint 负责报警,中间那段"按团队约定重排并保证空行结构"的活儿,落在import_sorter这类专门的工具身上。

这个分工在鸿蒙工程里尤其重要。鸿蒙侧的 Flutter 项目通常会有三份代码在同一仓库:lib/下的纯 Dart 业务代码、ohos/下的 ArkTS 与配置、以及 Dart 侧生成代码(序列化、路由、资源索引等)。后两类不该被 import 排序碰,第三类碰了会出事。工具能不能守住这条边界,取决于你的忽略配置写得对不对,这也是后面要重点讲的。

1.2 一个工具能省下的评审时间

算笔粗账。假设一个中型项目每周有 40 个合并请求,每个请求里平均 6 个文件发生引用变动。人工评审时,reviewer 需要确认新 import 有没有重复、有没有引到不该引的内部包、分组是否符合约定。哪怕每个文件只看 20 秒,一周就是 80 分钟纯粹消耗在格式确认上,而且这种确认的质量极不稳定——赶进度的时候基本靠扫一眼。

换成机器判定之后,这部分时间归零,而且判定结果是二值的:要么过,要么不过,没有"看起来还行"。更隐蔽的收益是 diff 噪音下降。import 顺序统一之后,两个人先后改同一个文件,不会出现"我挪了三行 import,你 rebase 冲突"这种让人火大的场面。我做过的项目里,这类冲突占全部冲突的比例不算高但极烦人,因为它的解决方式全靠手动对齐,几乎没有自动化空间。

2. import_sorter 的重写边界:它动什么、不动什么

要配置好这个工具,得先知道它是怎么干活的。import_sorter依赖 Dart 的analyzer包把源文件解析成语法树,拿到CompilationUnit之后遍历其中的 directive 节点,把import指令提取出来,连同它们各自的 URI、show/hide组合器、as前缀、以及挂在这一行上的注释(包括前置注释和行尾注释)一起收集。收集完按配置规则排序,再把新拼出来的文本块替换回原文件对应区间。整个过程不改动 AST 的其他部分,也不做语义分析。

理解了这条链路,很多行为就自然解释得通了。它是文本级重写,不是"重新格式化整个文件",所以除 import 区段之外的内容一个字都不会变;它不做未使用检查,删不掉没用到的 import(那是unused_importlint 的活);它不理解依赖图,不会因为两个文件互相引用就做特殊处理。换句话说,它是一个规则明确、边界清晰的文本处理器,这也意味着它出问题的时候,症状通常很具体、很好定位。

2.1 从 AST 到文本重排的处理链路

具体到文件里的位置,工具处理的起点通常是第一条import指令,终点是最后一条import(部分版本会把export和part一起纳入,行为以当时版本为准,落地前务必实测一次)。这意味着文件顶部的版权注释、library指令、@TestOn这类注解、以及part of声明,都应当留在原处不被搬动。如果你的团队习惯在文件头写一长串// ignore_for_file:注释,那块也属于"区段之外"的内容,正常不会被动。

真正容易出岔子的是"挂载关系"。想象一下:

// 这是某个特定实现,别删 import 'package:app/internal/legacy_bridge.dart'; import 'dart:async';

第一行 import 上面挂着一个前置注释。工具重排时会把这个注释当成这条 import 的附属物一起搬运,所以排序之后它会跟着legacy_bridge走到新位置,而不是留在文件顶部。这个行为是对的,但如果你原本期望它是"文件级注释",那就会被搬走,看起来像丢了。实战中的处理办法很简单:文件级说明写在library指令上方或者import区段下方,别紧贴着第一条 import 写。

另一个高频现象是 URI 字母序带来的"意外"。ASCII 排序里.(46)小于所有字母,d(100)小于p(112),所以默认升序排下来,相对路径'../foo.dart'和'./bar.dart'会跑到最前面,dart:组紧随其后,package:组最后才出现。这个顺序和很多人的直觉相反——不少团队习惯把相对路径放最后。这不是 bug,是排序规则的自然结果,想要改变就得靠分组配置,而不是靠手动调。

2.2 条件导入、注释与空行这些边角

条件导入是个必须点名的场景。Dart 允许这种写法:

import 'src/stub.dart' if (dart.library.io) 'src/io_impl.dart' if (dart.library.js_interop) 'src/web_impl.dart';

一条逻辑上的 import 横跨三行。工具在重排时如何处理这种多行声明,不同版本表现不同:有的会把整个声明当成一个不可分割的整体搬动,有的可能会把续行缩进重排。这里没有通用答案,我只能说先在项目里找一两个条件导入的文件,单独跑一遍看结果。我在一个多端项目上就吃过这个亏:全量跑完之后发现三个条件导入文件被重排成了单行超长语句,虽然语法仍然合法,但可读性直接崩了,最后是靠ignored_files把那个目录排除掉才收场。

空行处理则是分组配置的直接结果。默认配置下,工具通常会在不同分组之间插入一个空行,组内连续排列。这个行为本身很稳,但要注意它和directives_orderinglint 的关系:那条 lint 对空行的要求比较宽松,一般不会因为多一个空行报警,所以你加的空行不会被 lint 打回来。反过来,如果你把组间空行去掉,lint 也不会介意,纯看团队约定。

至于行尾注释,比如import 'package:foo/foo.dart'; // 临时绕过,等上游修复,工具一般会把它和这条 import 绑在一起搬动。这个行为很贴心,但也提醒你:任何写在 import 行尾的说明都别指望它留在原地。

3. 在鸿蒙 Flutter 工程里落地:依赖、配置、第一次跑通

好消息是,import_sorter是一个纯 Dart 包,不需要任何平台通道、不涉及原生代码、也不碰ohos/目录下的任何东西。它在鸿蒙 Flutter 工程里的"适配"成本,本质上等于"在一个 Dart 工程里配一个 dev 依赖"的成本。所谓鸿蒙化适配,真正要处理的是三件事:pub 依赖解析能不能正常走通、工程目录结构里哪些路径要排除、以及团队用的 flutter 命令是哪个版本。

坏消息是,这三件事里任何一件没处理好,都会表现为"工具跑不起来"或者"跑起来乱改文件",而报错信息往往指向不到根因。所以我把它拆成三个小节,从依赖加起到第一次成功 diff,按顺序走一遍。

3.1 dev_dependencies 与 pub 解析的注意点

第一步永远是加依赖。它只在开发期用,所以进dev_dependencies:

dev_dependencies: import_sorter: ^4.6.0

版本号别照抄,去包仓库确认当前可用版本再填。鸿蒙侧的 Flutter 工程用的是定制过的 Flutter SDK,其内置的 Dart SDK 版本可能和官方稳定版有偏差,所以选版本时要看一眼包的environment: sdk约束,确保能兼容你手上的 Dart 版本。如果解析下来报 SDK 约束冲突,通常用两种办法:降一个次版本试,或者临时用dependency_overrides顶一下——但dependency_overrides属于权宜之计,正式提交前记得清掉,否则会污染整棵依赖树。

flutter pub get跑完之后,确认.dart_tool/package_config.json里出现了import_sorter的条目,路径指向 pub 缓存目录。这一步看着废话,但它能帮你快速区分两类问题:依赖没解析成功(配置文件里没条目),和依赖解析成功但运行时找不到可执行入口(有条目但命令报错)。后者的常见原因是命令写法过时——老项目里写flutter pub run import_sorter:main,新版本 Dart 更推荐dart run import_sorter:main,两种写法在不同 SDK 版本上的兼容性不一样,报错就换一种试。

还有一个鸿蒙工程特有的细节:很多团队会把业务代码拆成多个本地包,用path:依赖串起来,这些包各自的pubspec.yaml里都要单独加dev_dependencies。别指望根工程的配置能覆盖子包,pub 的作用域是按包走的。

3.2 第一版配置文件长什么样

配置放在项目根目录的import_sorter.yaml(也有团队写在pubspec.yaml的import_sorter:键下,两种都行,团队统一一种即可)。下面这版是我在鸿蒙 Flutter 工程里常用的起始配置,字段含义会逐条解释:

emojis: false ignored_files: - "**/*.g.dart" - "**/*.freezed.dart" - "**/*.mocks.dart" - "lib/generated/**" - "ohos/**" - ".dart_tool/**" - "build/**" trailing_comments: true groups: - main - dart - flutter - package - other

emojis这个开关控制的是组间是否插入表情符号作为视觉分隔。我给的建议是关掉——不是因为不好看,而是它在多人协作里会带来两个小麻烦:某些 diff 工具对非 ASCII 字符的对齐处理不理想,以及部分终端环境渲染宽度不一致,看起来会歪。风格统一的前提下,纯空行已经足够。

groups是决定排序结构的核心,键名在不同版本上可能有出入,建议加完配置先跑一次看结果。常见的分组语义是:main指带main入口语义的那类特殊引用,dart指dart:前缀的核心库,flutter指package:flutter及其子库,package指其余三方包,other指相对路径。组间的先后顺序就是你写下的顺序,组内按字母升序。

trailing_comments决定行尾注释要不要跟着 import 一起搬。设成true时注释会被保留并随行移动;如果你的项目里有大量"临时注释掉的 import",比如// import 'package:debug_tools/debug_tools.dart';,那这些被注释掉的行工具是识别不到的,会原样留在原地——这点要有心理预期,别以为它们也被整理了。

3.3 先看 diff 再提交

配置写完,第一件事不是直接提交,而是先跑一遍、看一眼 git diff 的规模。我的习惯是先在一个子目录里跑,确认行为符合预期再全量。

cd packages/app_feature dart run import_sorter:main git diff --stat

如果--stat显示改动文件数在两三百、行数上万,别慌,这通常只是历史欠账一次性还清。真正要警惕的是"改到了不该改的文件"——比如ohos/下的 ArkTS 文件出现在 diff 里(说明忽略规则没生效),或者某个.g.dart文件被改了(说明通配规则写法有问题)。这两类一旦出现,先回滚,改配置,再重跑,别带着错误配置往下走。

一个很实用的技巧:把第一次全量排序单独做成一个提交,提交信息写清楚"仅 import 顺序调整,无逻辑变更",这样 review 的时候可以直接跳过。同时让 reviewer 只抽查几个关键文件的 diff,确认没有 import 被意外删除或合并即可。这一步做扎实,后面接入流水线才不会有历史包袱。

4. 配置项逐条过:分组、忽略清单与退出码

配置这东西,抄一份能跑,但跑得舒服不舒服全在细节。这一节我把几个真正影响日常使用的项拎出来讲,包括它们的匹配规则、常见误用,以及在这类多端工程里需要额外注意的点。

4.1 groups 与空行分组的实际效果

分组配置决定了文件的"视觉骨架"。一份典型的分组配置产出的结果会长这样(示意):

import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/user.dart'; import 'home_page.dart';

从上到下依次是dart:组、flutter组、三方package组、相对路径组,组间一个空行。这个结构的好处是扫读成本极低:找核心库看上两块,找三方依赖看中间,找本地文件看最后。

要注意的是,分组只是"分组",组内排序规则你改不了。如果某个包名以大写字母开头(比如某些历史遗留的本地包路径里有大写目录名),它会因为 ASCII 码值靠前而排到组内最前面,看起来略显突兀。这种时候只能接受,或者改目录命名——我倾向前者,改目录的连带成本太高。

另外,flutter组和package组在语义上是重叠的(package:flutter/material.dart本身也是 package 引用),分组配置的作用是把前者从后者里摘出来单独成组。所以如果分组没生效,你会看到flutter相关引用混在 package 组里按字母排列,cupertino排在provider后面——这是最直观的判断信号。

4.2 ignored_files 的匹配规则与踩点

忽略清单是最容易出错的地方,因为它用的是 glob 匹配,而 glob 的细节很多人不熟。几个必须记住的点:

  • **表示"任意层级目录",*只匹配单层内的任意字符。**/*.g.dart能匹配lib/models/user.g.dart和lib/a/b/c.g.dart,但*.g.dart只能匹配根目录下的。
  • 用引号包住带*的规则,避免被 shell 展开。
  • 路径分隔符统一用/,即使在别的平台上用别的分隔符,这里也写/。
  • 目录规则要带/**,写lib/generated通常匹配不到目录里的文件。

踩得最多的坑是生成代码。.g.dart(序列化生成)、.freezed.dart(不可变类生成)、.mocks.dart(测试替身生成)这三类必须排除,理由下一节细讲。第二多的是第三方代码的本地副本,有些团队会把某个依赖 fork 一份放进仓库,那目录下的文件不该被整理,否则下次同步上游时会冲突成灾。

还有一个鸿蒙工程特有的点:ohos/目录下可能有构建脚本或工具生成的临时 Dart 文件,也要一并排除。虽然正常情况下那里不该有.dart文件,但项目演进过程中什么都会出现,一条ohos/**的规则成本极低。

4.3 --exit-if-changed 在流水线里的语义

这个参数是整个工具链里最有工程价值的一个。带上它运行,工具在完成排序之后会检查是否有文件内容发生变化:有变化就返回非零退出码,没变化返回 0。

dart run import_sorter:main --exit-if-changed echo $?

它的妙处在于,你可以把它当成一个幂等性检查:在一条干净的、刚刚提交过代码的工作区里跑,如果退出码非零,说明有人提交了未排序的代码。放进流水线里,这就成了一个零误报、零配置的硬性门禁。相比"跑完之后git diff看不干净就失败"这种土办法,它的好处是不依赖工作区的暂存状态,也不会因为换行符差异导致误判。

要注意它的语义是"被修改",不是"格式错误的行数"。所以如果你在本地手动改好了文件再跑,退出码是 0,工具什么都不会说。反过来,如果你跑完之后git checkout回滚了,那也是 0。想验证门禁是否真的生效,最靠谱的办法是故意留一个顺序错乱的文件提交上去试一次。

5. 三个真实翻车点:生成物被改、顺序打架、跨平台路径

工具越简单,出错时的甩锅空间越小,但症状往往越隐蔽。这一节列的三个场景,都是我在实际项目里真实遇到的,排查链路我会尽量写全,方便你复现。

5.1 .g.dart 与 .freezed.dart 被重排后的连锁反应

症状:CI 上某个测试突然挂了,报错指向一个 Model 类的构造函数参数顺序不对,但业务代码一行没改。本地跑没问题,CI 上稳定失败。

排查过程是这样的:先看 CI 和本地的差异,发现 CI 流水线里多了一步"代码生成",生成完之后又跑了一次格式化。再看生成的.g.dart文件,发现里面$UserFromJson和$UserToJson两个函数的顺序和本地不一样。继续往上追,才反应过来——生成代码那一步之后,流水线里跑了一次全量的import_sorter,而.g.dart文件里也有一堆 import 声明,被重排之后触发了后续的构建缓存失效,重建出来的产物和本地不一致。

根因其实很朴素:生成代码是工具的产物,不该被第二个工具二次改写。生成器写出来的 import 顺序有它自己的逻辑,人工干预之后,下次重新生成又会覆盖回去,形成"改了又被覆盖、覆盖了又改"的反复。更糟的是有些生成器会带一个校验步骤,比对现有文件和预期产物,一旦被改写就判定不一致并重新生成,导致构建时间翻倍。

修复方案就是前面配置里的那三条通配规则,把**/*.g.dart、**/*.freezed.dart、**/*.mocks.dart全部排除。加完之后还有一个配套动作:在生成目录里放一个 README 或者配置注释,写明"本目录由代码生成,请勿手工调整",防止后来的人手动去收拾。

注意:排除规则写好之后,建议专门造一个改动来验证它真的生效——改一下某个.g.dart的 import 顺序,跑一遍工具,看它是否被还原。别只相信配置文件的字面意思。

5.2 dart format 与 import_sorter 的先后顺序

症状:本地跑完全绿,提交之后 CI 报"格式检查失败",但dart format本地明明跑过了。

根因在于两个工具互相改对方的地盘。import_sorter重排 import 之后,可能产生某些行的长度变化,或者把多行 import 合并、把单行拆开;而dart format随后会对这些变化做换行和缩进处理。如果顺序反了——先format再sorter——那sorter的输出就是"未经格式化"的状态,CI 上再跑一次format --set-exit-if-changed就会失败。

正确顺序是固定的:先跑import_sorter,再跑dart format。写进脚本里就是:

dart run import_sorter:main --exit-if-changed dart format --set-exit-if-changed .

更进一步,如果你的流水线里有代码生成,完整顺序应该是:生成 →import_sorter→dart format。生成放最前,因为生成器不认你的排序格式;排序放中间,因为它只动 import;格式化放最后,收拾所有排版。这个顺序我踩过一次之后,直接在团队文档里定为硬规矩,后续没再复发。

顺带说一个相关的小坑:dart format在较新版本改过默认的行宽和格式化算法,同一个文件在旧版和新版 SDK 下格式化结果可能不同。如果团队里有人用不同版本的 Flutter SDK,格式化检查就会常年飘红。解决办法是在仓库里固定一个 SDK 版本声明,或者干脆把格式化检查只放在 CI 端的固定镜像里跑。

5.3 大小写、下划线与条件导入引发的诡异 diff

症状:跑完工具,某个文件的 diff 显示一条 import 消失了,另一条重复出现了,但文件总行数没变。打开文件看,实际上两条都在,只是位置换了。

这种情况多半和"看起来一样但实际不同"的 URI 有关。两个典型来源:一是路径大小写,在某些不区分大小写的文件系统上,package:app/Utils.dart和package:app/utils.dart都能解析到同一个文件,但在排序时它们是两个不同的字符串,会被分别搬动,视觉上就像重复了。二是尾部斜杠或多余空格,'package:foo/bar.dart'结尾带个空格,肉眼完全看不出来,排序时却会被当成不同条目。

排查这类问题的办法是让 diff 工具显示空白字符差异:

git diff --ignore-space-change --ignore-blank-lines git diff -w

先看清到底改了什么,再决定是修文件还是改配置。真正干净的做法是加一条custom_lint或者简单的 grep 检查,禁止 import URI 里出现首尾空格和大写字母(除特殊情况)。这类检查写起来十行不到,收益却很高。

条件导入的处理前面提过了,这里补一个实操判断:如果你在项目里搜到if (dart.library.这样的模式出现在 import 区段,就先把那些文件加入忽略清单,观察一段时间再说。等到确认当前版本的import_sorter能正确处理三行式条件导入,再放出来。宁可少排几个文件,也别让多端适配代码被改坏——那类代码改坏了往往编译能过、运行时才炸,排查成本极高。

6. 从单点工具到团队规约:钩子、流水线与评审口径

工具跑通只是起点,真正让它产生价值的是"不给人偷懒的机会"。我的经验是:只要有一处可以绕过,就一定有人绕过,然后格式就会在半年内慢慢退化成原样。所以这一节讲的是怎么把它变成一条不可绕过的链路,以及在这类多端工程里怎么控制它带来的额外成本。

6.1 pre-commit 钩子的最小实现

最轻量的拦截点是 Git 钩子。不依赖任何额外框架,一个 bash 脚本就够:

#!/usr/bin/env bash set -e STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.dart$' || true) if [ -z "$STAGED" ]; then exit 0 fi echo "检查 import 排序..." if ! dart run import_sorter:main --exit-if-changed > /dev/null 2>&1; then echo "存在未排序的 import,正在自动修复..." dart run import_sorter:main git add $STAGED fi echo "检查格式化..." dart format $STAGED > /dev/null git add $STAGED

这段脚本的重点有三处。第一,先筛选出暂存区里的.dart文件,没有就直接退出,避免每次提交都跑一遍全仓库。第二,排序失败时不是拒绝提交,而是自动修复并重新暂存——这一点很关键,如果直接拒绝,开发者的体验会很差,久而久之大家会习惯性--no-verify绕过。第三,修完之后一定重新git add,否则修好的内容不会被包含进这次提交,下次提交时又会出现同样的改动。

钩子放在.git/hooks/pre-commit并chmod +x。但这个位置不会被版本控制,团队协作时需要在仓库里放一份模板,配合一个初始化脚本分发。这是所有钩子方案的共同短板,绕不过去,只能靠文档和新人引导补。

6.2 流水线里的阻断策略与灰度

钩子能挡住大部分情况,但挡不住"用了--no-verify"和"在网页端直接改文件"。所以流水线上必须有第二道门。

流水线这一层的策略,我建议分阶段推。第一阶段只告警不阻断:跑--exit-if-changed,失败时输出一份"需要排序的文件清单",但不让整条流水线红。这个阶段通常持续一到两周,用来收集历史欠账、调整忽略规则、发现配置盲区。第二阶段开始阻断:把检查步骤挪到构建之前,失败即终止。

灰度期的价值在于,你能在不影响交付的前提下看到真实的失败率。我在一个项目上做灰度时发现,失败请求里有三成集中在同一批生成目录,说明忽略规则漏了;还有两成是条件导入文件,最后靠加忽略清单解决。如果一上来就阻断,这两类问题会直接卡主发布流程,团队对工具的印象就毁了。

清单输出的写法参考:

if ! dart run import_sorter:main --exit-if-changed > /dev/null 2>&1; then echo "以下文件存在未排序的 import:" git diff --name-only exit 1 fi

注意这段要在一个干净的工作区里跑,否则git diff会把别的改动也算进去。流水线环境里天然干净,本地调试时记得先git stash。

6.3 大仓耗时控制与分批治理

最后聊性能。import_sorter的处理方式是逐文件解析,没有并行,所以耗时基本和文件数量线性相关。几百个.dart文件的工程,跑一遍通常在十几秒到一分钟这个量级,日常够用,但在两种场景下会成为痛点:钩子里的等待,以及大仓库的历史欠账清理。

钩子里的优化很容易——前面脚本已经做了暂存文件筛选,只处理变更文件。但要注意,import_sorter本身没有"只处理指定文件"的参数(至少常见版本如此),所以脚本层面的筛选只能用来决定"要不要跑",不能把范围缩小到某几个文件。真正的做法是:在子目录里跑。

cd packages/app_core && dart run import_sorter:main cd ../app_feature && dart run import_sorter:main

多包工程天然支持这种做法,按包粒度并行(比如用xargs -P或者简单的后台任务),总耗时能压到可接受范围。

历史欠账清理则需要分批。我的做法是按目录分三到五批,每批单独一个提交,提交信息统一写成"规范化:第 N 批 import 排序"。这样 review 时不会因为 diff 太大而被跳过,出问题也能精确回滚到某一批。清理期间流水线的检查策略要相应放宽——只检查本批改动涉及的文件,或者干脆暂停阻断一周,等清完再开。

提示:全量清理之前,务必先在.gitattributes里确认换行符策略。如果仓库里混着 CRLF 和 LF,工具重写文件时可能顺手把整个文件的换行符统一了,导致 diff 从"改了三行 import"变成"整个文件都变了",review 成本爆炸。

7. 我个人在用这套方案时的几条体会

写了这么多配置和坑,最后分享几个不在文档里、但真正影响长期使用体验的点。

第一个体会是:别追求一步到位。我见过太多团队一上来就把排序、格式化、lint 全量门禁全部打开,结果第一个月的合并请求全部卡在格式问题上,团队怨气很大,最后整条链路被拆掉。稳的做法是顺序推进——先让import_sorter在本地可用(不阻断),再让它在钩子里自动修(不阻断提交),最后才上流水线阻断。每一步都留一两周适应期。

第二个体会是关于规则的"最小共识"。分组配置的讨论最容易失控,每个人都有自己的偏好:有人坚持相对路径放最前(因为 Dart 官方风格指南倾向于这样),有人坚持放最后(因为改本地文件时好找)。这种争论没有对错,我的处理办法是:谁能拿出一个"减少事故"的理由,就采纳谁;拿不出就按默认来。规则的价值在于一致,不在于最优。

第三个体会是工具态度。import_sorter只做一件事,也就意味着它不该被赋予超出能力范围的期待。删未使用的 import 交给unused_import加dart fix,检查依赖是否合规交给自定义 lint,控制是否允许跨层引用交给架构检查脚本。把这些职责分清楚之后,每条规则的失败原因都很明确,排查起来也快。我见过一个项目把所有引用治理都塞到一个自研脚本里,结果脚本从两百行涨到两千行,最后没人敢动。反而是这种"一个工具一件事、靠流水线串起来"的结构,维护成本低得多。

第四个体会是迁移期的心态。鸿蒙侧的 Flutter 工程本来就在快速演进,目录结构、依赖方式、构建链路都可能反复调整,这意味着你写的忽略规则和钩子脚本也要跟着改。建议把这些配置集中放在仓库显眼的位置,配上写清楚"为什么这么排除"的注释,别只留一堆通配符。半年后接手的人(很可能就是你自己)看到"**/*.g.dart"的时候,会庆幸当初多写了那两行说明。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询