这是 Dart 团队的一个重要里程碑: 我们为大家带来了空安全功能的技术预览版。空安全可以帮助您避免一类通常难以发现的错误,并且还可以带来一系列的性能改进。欢迎大家体验日前发布的早期技术预览版本,我们期待您的反馈。
本文将为大家介绍 Dart 团队推出空安全的计划,并解释空安全的健全之处,以及与其他语言之间的差异。
Dart 是一种类型安全的语言,即当您拿到某个类型的变量时,编译器可以保证它属于该类型。但是类型安全本身并不能保证变量不为空 (null)。
空值错误是非常常见的。在 GitHub 上搜索一下就能找到成千上万个由 Dart 代码中的空值引发的错误,更有成千上万的开发者在试图修复这些问题。
试试看,您能否在下面的示例代码中发现可空性问题:
void printLengths(List<File> files) {
for (var file in files) {
print(file.lengthSync());
}
}
如果用空值进行调用,这个函数肯定会出错。但还有第二种情况需要考虑:
void main() {
// 错误情况 1: 将一个空文件传递给方法的 files 参数。
printLengths(null);
// 错误情况 2: 将文件列表传递给 files,但列表中包含空项目。
printLengths([File('filename1'), File('filename2'), null]);
}
空安全功能则可以解决这个问题:
有了空安全,您就可以更放心地使用代码。不再需要等到运行时才遭遇空引用错误,在编写代码时您就能看到静态错误提醒。
Dart 的空安全机制是健全的。这意味着 Dart 能 100% 确保上面例子中的文件列表 (files),以及其中的元素都不能为空。当 Dart 分析代码,并确定某个变量不可空的时候,这个变量就会一直不可空: 如果您在调试器中检查正在运行的代码,就会看到它在运行时保留了不可空性。相比之下,其他的一些空安全实现则不够健全,在很多情况下,它们仍然需要在运行时执行空检查。Dart 与 Swift 都拥有健全的空安全,而有些编程语言在这方面还仍待改进。
Dart 健全的空安全还有另一层妙处: 您的程序可以更小更快。因为 Dart 能够真正确定 files 永不为空,所以 Dart 可以做出优化。例如,Dart 的运行前 (ahead-of-time, AOT) 编译器可以生成更小更快的原生代码,因为当它知道一个变量不会为空的时候,就不再需要添加空检查了。 我们已经看到了一些值得期待的前期结果。例如,在模拟典型 Flutter 框架渲染模式的小规模测试中,性能提升 19%。
在开始进行详细的空安全设计之前,Dart 团队确立了以下三个核心原则:
核心语法很简单。下面给出了一些不可空变量,但通过不同的方式声明。请记住,不可空是默认的,所以这些声明虽然看起来和我们今天常见的形式一样,但其含义已经不同。
// 在空安全的 Dart 中,下列变量皆不可空。
var i = 42;
final b = Foo();
String m = '';
Dart 会确保您永远无法将空值赋予上述任何一个变量,如果您试图在一千行代码之后输入 "i = null",就会触发静态分析错误并看到红色的波浪下划线,程序也将无法编译。
如果您希望变量是可空的,您可以使用 "?",比如:
// 下列变量可空。
int? j = 1; // 之后可被赋予 null 值。
final Foo? c = getFoo(); // 方法可以返回 null。
String? n; // 初始即为 null,之后任何时间也可以为 null。
上面这几个变量的行为会与如今所有变量的行为一致。
您也可以在其他地方使用 "?" 语法:
// 在函数参数里使用。
void boogie(int? count) {
// count 可以为 null。
}
// 在函数返回值使用。
Foo? getFoo() {
// 可以返回 null 值。
}
// 也可以在泛型 (generics)、函数别名 (typedefs)、类型检查 (type checks) 等处使用。
// 上述用法均可混合使用。
但我们要再强调一遍,我们的目标是让您几乎不再需要使用 "?",您的绝大多数类型都将是不可空的。
Dart 团队正在尽可能地使空安全方便使用。比如下面这段代码,它使用 "if" 来检查空值:-
void honk(int? loudness) {
if (loudness == null) {
// 在没有给出 loudness 值时直接用最大音量告知开发者。
_playSound('error.wav', volume: 11);
return;
}
// Loudness 不为空,直接将其调整 (clamp) 至可接受的范围。
_playSound('honk.wav', volume: loudness.clamp(0, 11));
}
需要强调的是,Dart 对这段代码的处理方式相当 "机智",通过分析它知道在 if 语句通过之后,loudness 这个变量就不可能为空值,因此如代码的第 8 行所示,它可以让我们直接调用 loudness 的 clamp() 方法,而无需额外的操作。这种便利性是通过流程分析 (flow analysis) 来实现的: Dart 分析器会像执行代码一样检查您的代码,自动找出代码中附加的信息。
下面还有一个例子。Dart 可以确定一个变量非空,因为我们总是给它分配一个非空值:
int sign(int x) {
// result 不可空。
int result;
if (x >= 0) {
result = 1;
} else {
result = -1;
}
// 运行到这里 Dart 就知道 result 不会为 null。
return result;
}
如果您删除了上例中任意赋值语句 (例如删除 result = -1; 这一行),Dart 将无法保证 result 非空: 您会看到一个静态错误,代码则无法编译。
流程分析只在函数内部有效。如果您有全局变量或类字段,那么 Dart 无法保证什么时候会分配什么值给它。Dart 无法对整个应用的流程进行建模。出于这个原因,您可以使用新的 "late" 关键字: 您在第一次读取某个变量之前就知道它不会为空,但您不能立刻初始化它。
class Goo {
late Viscosity v;
Goo(Material m) {
v = m.computeViscosity();
}
}
请注意上例中的 v 是非空的,尽管它开始时没有被初始化。Dart 相信您在给 v 分配一个非空值之前不会尝试去读取它,因此您的代码在编译时不会报错。
Dart 团队通过一年多的努力将空安全推进至技术预览版。这是自 Dart 2 推出以来,我们对 Dart 语言最大的扩充。但这个变化却没有任何破坏性。现有代码可以调用使用了空安全的代码,反之亦然。即使在空安全正式可用之后,它也将是可选功能,您可以在准备好之后再采用它。您现有的代码不需要任何修改就可以继续运行。
我们最近将 Dart 核心库全部迁移至空安全。作为空安全向下兼容的展示,我们直接替换了既有的运行在 Dart 和 Flutter 测试环境中的测试和示例应用中的核心库,而且没有引发任何问题。我们甚至将新的核心库发给许多 Google 内部的开发者,直接用于他们的生产环境,也没有出现任何问题。我们计划在该功能正式推出时,将所有 package 和应用迁移到空安全。我们希望您也这么做,但您完全可以根据自身节奏,逐个对 package 和应用进行迁移。
我们计划逐步推出空安全功能,分为三步:
技术预览版。即本周发布的版本,您可以在 Dart 的 dev 渠道获取。请继续阅读至本文的 "即刻上手体验" 部分了解详情。我们依然会做出修改,所以暂时不要在生产环境中使用此版本的空安全。不过请务必上手进行体验,并和我们分享您的反馈!
测试版。空安全将在 Dart 的 beta 渠道可用,不再被标记为 "实验" 状态。这时空安全将非常接近最终版本。如果您手上有 pub.dev package 或插件,这时就可以着手进行迁移,但暂时不要以稳定版的形式进行发布。
稳定版。在这个阶段,所有的用户都可以使用空安全,我们也会鼓励您以稳定版的形式将迁移完成的 package 和插件进行发布。您也需要迁移自己的正式版应用。
dev 渠道 https://dart.cn/get-dart#release-channels
实验状态 https://github.com/dart-lang/sdk/blob/master/docs/process/experimental-flags.md
pub.dev https://pub.flutter-io.cn
如果一切顺利的话,我们计划在年底前发布空安全功能的稳定版本。在此之前,我们将推出一些工具帮助您的代码实现空安全,包括:
一款迁移工具,帮助您在升级现有 package 和应用时,在很多步骤中实现自动化。
在 pub.dev 中提供标识,这样您就能知道某个 package 是否支持空安全。
对 pub outdated 命令进行扩展,让您可以查找支持空安全的最新版本依赖。
pub outdated https://dart.cn/tools/pub/cmd/pub-outdated
想要试用空安全,最快的方法是访问 nullsafety.dartpad.cn,这是一个启用了空安全的 DartPad 版本。打开 Learn with Snippets 下拉菜单即可找到一系列的练习项目,里面包含新的语法介绍和空安全的基础知识。
您也可以在小型命令行应用中尝试空安全 (我们还没有迁移像 Flutter 这样的大型框架)。您可以先下载一份 dev 渠道的 Dart SDK,然后下载这个 Dart CLI 示例应用 (可以在 GitHub repo 里找到,对应的 zip 包在这里)。示例应用的 README 文件中说明了如何通过空安全实验标识运行应用。示例中的其他文件提供了启动配置,供您在 VS Code 和 Android Studio 中进行调试。
https://github.com/dart-lang/samples/blob/master/null_safety/calculate_lix/README.md#dart-preview-sdk-installation
Dart CLI 示例应用 https://github.com/dart-lang/samples/tree/master/null_safety/calculate_lix
GitHub repo https://github.com/dart-lang/samples
Zip 文件 https://github.com/dart-lang/samples/archive/master.zip
README 文件 https://github.com/dart-lang/samples/blob/master/null_safety/calculate_lix/README.md
我们为大家准备了一些文档,之后会进一步进行扩充:
空安全指引
https://dart.cn/null-safety
空安全核心库 API 文档
https://api.dart.cn/dev/2.9.0-13.0.dev/index.html
很高兴能为 Dart 带来健全的空安全功能。这个 Dart 独有的功能有助于减少代码编写中的错误,让您获得更好的性能。我们希望您能在技术预览版中尝试这个功能,并通过问题追踪页面和我们分享您的反馈。祝大家编程愉快!
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8