
Dart的null-safety迁移对于一个简单的小包来说只需要1-2个小时,但对于一个大项目来说可能是一场长达数月的马拉松。理想情况下,你希望逐步迁移你的项目--在这个马拉松过程中,你希望保持项目的可增长性、可维护性和易于发布。
我已经将一个大型项目迁移到null安全状态,并决定将如何使这个过程可靠和高效的步骤和技巧放在一起,希望能为你节省时间。
第一步:转换到不健全的null-safety
首先,将你的项目迁移到不健全的空安全。
从升级你的依赖项到空安全版本开始。无声空安全不要求所有的依赖关系都是空安全的。然而,我们强烈建议等到所有的上游依赖关系都被迁移,因为迁移一个依赖关系可能会迫使你重新审视自己代码中的迁移决定。对于那些相互依赖的包,你可能会被迫不按顺序迁移,或者同时迁移这些包(许多相互依赖的包大多只在测试中提到对方)。遵循dart.dev的指导,在迁移代码前尽可能多地升级依赖关系。
接下来,为你的软件包更新Dart SDK,并通过以下步骤将每个未迁移的库标记为遗留库。
- 确保你的IDE(VSCode, IntelliJ / Android Studio)安装了Dart插件。
- 在IDE中打开你的软件包,确保没有编译错误。
- 更新pubspec.yaml文件中的dart_sdk依赖关系,要求版本范围:'>=2.12.0 <3.0.0'。
- IDE会在尚未实现null-safe的库中突出显示null-safe相关的错误。通过在每个受影响文件的顶部添加注释'//@dart=2.9'来删除这些错误。即使你的main.dart文件没有错误,也要添加注释,以保持应用程序在不健全模式下运行,直到你准备切换。
- 验证所有测试是否通过,并将修改提交给你的主分支。如果你的测试已经是无声的,你需要使用命令行标志 --no-sound-null-safety 来抑制无声的错误。
确保在启动应用程序时,你能在控制台中看到 "Running with unsound null-safety"。
现在,你已经准备好将你的项目逐一迁移到有声空安全库。
第2步:迭代到健全的空安全状态
选择一个库或一组库进行迁移。
专业建议:如果你选择的库比较大,你可能想在迁移之前把它分成几个小库。
使用dart pub deps来创建一个项目的依赖关系图。最好是自下而上迁移软件包;从依赖树的叶子开始,然后向上迭代到根。然而,如果你的项目有依赖性循环,这可能是不可能的,不遵循这个顺序也是可以的。
使用迁移工具迁移库(或一组库)。
- 通过运行dart migrate --skip-import-check来启动交互式迁移工具。你可能希望cd到有所选库的目录,以方便在树上导航。
- 通过取消选择左侧面板中的文件视图树的根部来取消所有的选择。(如果有兴趣,可以投票选出一个取消选择所有的按钮)。
- 使用Control+F来找到你想迁移的文件。
- 选择该文件,然后点击 "应用迁移"。有两个选项可以让你进行调整。(1) 在应用迁移之前使用注释来调整工具的选择,或者(2) 在应用迁移之后使用IDE来评估字段、参数和变量的无效性。
- 在IDE中打开包。修正错误,并在文件中搜索工具可能不准确的情况(见下面的潜在问题列表)。进行修正,并使用lint警告来交互式地清理上游和下游的代码。
你将无法修复两个lint错误。
- import_of_legacy_library_into_null_safe (在迁移的库中)
- avoid_redundant_argument_values (在遗留库中)
现在,用注释禁用这些错误。稍后,在迁移完成后,你会清理这些错误。
要注意哪些潜在的工具不准确的地方。
- 增加了动态或num类型。最有可能的是,你知道应该用哪个特定的类型来代替。
- 在大多数情况下,bool? 可以变成带有默认值的bool。
- 类型转换(搜索'作为')可能意味着工具没有添加一个通用类型参数。在添加之后,有一个提示表明,这个铸型已经成为不必要的,可以被删除。
- 在某些情况下,工具使一个通用参数的约束为空,而最好是使其为非空(搜索?>和?,)。
- 该工具可能会使那些可以更好地使用晚期或晚期final来表达的东西变成nullable,或者可以重构以允许在构造函数的初始化器列表中进行初始化。(如果感兴趣,请投票给lint。)
- 使用null-assert操作!而不首先检查null值,可能意味着变量或参数实际上是不可被null的。(如果感兴趣的话,请投出一个提示。)
- 该工具为集合增加了Iterable形式的铸造。有时,这种改变只是使原本隐式的铸造变得显式。然而,在其他情况下,由于通用参数的无效性不匹配,这些转换可能会带来运行时错误。如果有疑问,可以考虑用一个显式的每个元素转换来代替投射(例如,collection.cast()),或者考虑使用package:collection的whereNotNull扩展方法 。
- 如果一个变量、字段或参数是可归零的,但这种可归零性只在测试中使用,也许应该重构代码以去除该标识符的可归零性。
(感谢Kenzie Davisson,他帮助我识别了这些情况)。
第三步:清理
迁移完所有的库后,做一些最后的清理工作。
- 清理lints的禁用注释。
- 将其余的依赖关系升级到null安全版本。
- 确保你的应用程序中没有`//@dart = 2.9`的注释。在这一点上,当启动应用程序时,你应该在控制台中看到Running with sound null-safety。如果你没有看到这一点,你要么有尚未迁移的库(搜索`//@dart = 2.9`),要么有尚未迁移的依赖。
- 确保应用程序仍然正常运行,并且测试通过。由于声音模式可以实现更强的运行时保证,所以有可能(尽管不太可能)看到新的运行时错误,当你启用声音的null safety时,你需要修复。通常这是由于将一个可归零的集合(如List<int?>)转换为一个不可归零的集合类型(如List)所造成的。
祝你迁移愉快!
大型Dart项目的渐进式空值安全迁移最初发表于Darton Medium,在那里人们通过强调和回应这个故事来继续对话。