构建健全的空安全

486次阅读  |  发布于4年以前

这是 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 和应用进行迁移。

空安全发布时间表

我们计划逐步推出空安全功能,分为三步:

如果一切顺利的话,我们计划在年底前发布空安全功能的稳定版本。在此之前,我们将推出一些工具帮助您的代码实现空安全,包括:

即刻上手体验

想要试用空安全,最快的方法是访问 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 带来健全的空安全功能。这个 Dart 独有的功能有助于减少代码编写中的错误,让您获得更好的性能。我们希望您能在技术预览版中尝试这个功能,并通过问题追踪页面和我们分享您的反馈。祝大家编程愉快!

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8