Dart 健全的空安全: 技术预览版 2

2020年10月28日 276点热度 0人点赞 0条评论
图片
作者 / Michael Thomsen, Dart & Flutter Product Manager, Google


Dart 在 Flutter 中扮演着特别的角色——它为开发者提供了热重载等功能,并通过灵活的编译器技术实现移动、桌面和 web 端的多平台应用。我们努力使 Dart 语言能够为 Flutter 应用开发者提供最大的生产力。例如,我们添加了 UI-as-code 语言结构来优化 Dart 语法,以便于编写 Flutter widget 树。


我们于 6 月推出了 Dart 空安全的首个技术预览版。现在则是我们期待已久的另一个重要里程碑: 健全的空安全第二个技术预览版本,其中包含对 Flutter 框架的支持。


  • Dart 2.10

    https://medium.com/dartlang/announcing-dart-2-10-350823952bd5


空安全是一项全新的能够提高生产力的重要特性,可帮助您避免空值异常,这是一类通常很难发现的错误。在此之外,空安全还可以带来一系列性能提升。我们非常期待听到您的使用反馈。


为什么需要空安全?

Dart 是一种类型安全的语言。即当您拿到某个类型的变量时,编译器可以保证它属于该类型。但是类型安全本身并不能保证变量不为空 (null)。

空值错误十分常见。在 GitHub 上搜索,您会看到数以千计由 Dart 代码中意外的空值导致的问题,更有成千上万的开发者在试图修复这些问题。请您试着找出下面这个应用中的空值问题 (ConfigWeatherService 是该应用使用的后端服务):

class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {// Get data from services. Note: in a real application,// these would be async calls, but we're using sync calls// for simplicity.final localizedAppName = Config.getAppName();final temperatures = WeatherService.getTemperatures();return MaterialApp(        home: Scaffold(            appBar: AppBar(title: Text(localizedAppName)),            body: Padding(                padding: const EdgeInsets.all(8.0),                child: Column(                    crossAxisAlignment: CrossAxisAlignment.start,                    children: [                        Text('Temperature next 3 days:'),for (final t in temperatures)                            Text(t.round().toString()),                        ],                    ),                ),            ),        );    }}class Config {static String getAppName() { ... }}class WeatherService {static List<double> getTemperatures() { ...}}

如果 getAppName() 返回 null,则应用一定会出错;在这种情况下,我们会向 AppBar 标题中的 Text widget 传递 null。

但是,还有更多微妙的情况需要考虑: getTemperatures() 也可以返回 null。在这种情况下,for 循环将失败。或者,getTemperatures() 也可能会如预期返回一个列表,但是该列表可能包含 null 值,在这种情况下,我们对 null 调用 round(),这同样也会出错。

空安全特性可在您编写代码时就对其进行验证,从而消除这些问题:

  • DartPad: 验证代码
    https://nullsafety.dartpad.cn/28f1db5ef4401d9e063375e5c58f0f86
图片
△ 借助空安全,Dart 会在代码中发现潜在的空值错误

有了空安全,您就可以更放心地编写和使用代码。不再需要等到运行时才遭遇空引用错误,在编写代码时您就能看到静态错误提醒。

空安全设计原则

Dart 的空安全支持基于以下三个核心设计原则:

  • 默认不可空。除非您明确告诉 Dart 一个变量是可空的,否则它将一概认为所有变量都不可空。我们之所以这么做,是因为我们看到不可空是 API 中最常见的选择。

  • 可逐步采用。用 Dart 编写的代码数量庞大。您将可以择机且逐步地迁移至空安全。在同一项目中将可以同时包含空安全代码和非空安全代码。我们还将提供工具来帮助您进行迁移。

  • 彻底健全。Dart 提供健全的空安全。这意味着我们可以信任类型系统: 如果类型系统确定某项不为空,则它一定不会为空。这样可实现编译器优化。在将整个项目和依赖都迁移到空安全之后,您将尽享健全空安全的优势: 错误会更少,二进制文件也会更小,执行速度则会更快。

让我们来详细介绍上述这些设计原则。

1. 默认非空

空安全的核心语法十分简单。以下是一些以不同方式声明的非空变量。请记住,非空是默认的,因此这些声明虽然看起来与当下的代码相同,但其含义已经发生了变化。
// 在空安全的 Dart 中,以下变量皆不可空。var widget = Text('Hello');final status = GetStatus();String m = '';

Dart 将确保您无法将空值赋予上述任何变量。如果您尝试在一千行之后执行 widget = null,则会触发静态分析错误并看到红色波浪线,程序也将无法编译。

可空变量

如果您希望变量可空,则可以使用 ?,如下所示:

// 下列变量可空。Text? t = Text('Hello');  // 之后可被赋予 null 值。final Status? s = getStatus();  // 方法可以返回 null。String? n;  // 初始即为 null,之后任何时间也可为 null。
您也可以在函数参数和返回值中使用 ? 语法:
// 在函数参数里使用。void initialize(int? count) {    // It's possible that count is null.}// 在函数返回值使用。static List<double?>? getTemperatures() {    // 可以返回 null,或者 List 里也可以含 null 项。}

但再次强调,理想情况是您几乎不需要使用 ?。您的大多数类型均不可为空。

用空安全提高生产力
空安全并非局限于安全性。我们还希望您能借此特性提高生产力,这意味着该特性必须易于使用。例如下面的代码,其中使用 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 变量不会为空。之后我们不必大费周章便能轻松调用 clamp() 方法。这种便利性是通过流程分析 (flow analysis) 来实现的: Dart 分析器会像执行代码一样检查您的代码,自动找出代码中附加的信息。

下面是另一个示例,Dart 可以确定一个变量非空,因为我们总是给它分配一个非空值:

class StatusLine extends StatelessWidget {    final Status status;    StatusLine({this.status: Status.failed});    @override    Widget build(BuildContext context) {        // 这个本地变量不可空,只是未被初始化。        String statusText;        if (status == Status.ok) {            statusText = 'Update succeeded';        } else {            statusText = 'Update failed';        }        // 运行到这里,Dart 就知道 statusText 不为空,        // 因为我们在 if 和 else 里都对其进行了赋值。        // 因此我们可以将其传给 Text widget(该 widget 不接受空值)并且不报错。        return Text(statusText);    }}

如果您移除上述任何赋值语句 (例如,删除 statusText = 'Update failed'; 一行),则 Dart 将无法保证 statusText 非空: 您将看到一个静态错误,且代码将无法编译。您可以在 DartPad 中体验这种空安全。

  • 体验空安全

    https://nullsafety.dartpad.cn/ecc0f87fa5af5cc7ff30d8bd3e3b12e2

2. 可逐步采用

空安全对于我们的类型系统而言是一项根本性的改变,因此如果我们执意强制采用,那么势必会造成严重的混乱。我们想让开发者们自行决定采用空安全的合适时机,因此空安全将是一项可选特性: 无需强制启用空安全,您也可以使用最新版本的 Dart 和 Flutter,您可以在做好相关准备后再启用空安全。您甚至可以在尚未启用空安全的应用或 package 中依赖已启用空安全的 package。
一旦您选择采用空安全,我们强烈建议按顺序迁移代码,应首先迁移依赖关系图中的子项。例如,如果 C 依赖于 B,而 B 依赖于 A,那么应首先将 A 迁移到空安全,然后再依次迁移 B 和 C。无论 A、B 和 C 是开发库、package 还是应用,该顺序都适用。

迁移顺序为何如此重要?虽然您可以在迁移依赖项之前先处理一些代码迁移工作,但如果依赖项在迁移期间更改了其 API,那么您将面临重新执行迁移的风险。进入 beta 测试版后,我们将提供工具来帮助您确定哪些依赖项已迁移。如果您是 package 的作者,那么为了避免破坏 API,请等到所有依赖项都已迁移完毕后再发布空安全版本。

依赖项准备就绪后,您便可以使用我们的迁移工具。该工具会对您所有的现有代码执行分析。迁移工具是交互式的,因此您可以查看该工具的可空性推断结果。如果您对工具给出的结论有异议,则可以添加可空性提示以更改推断。添加迁移提示有可能大幅提升迁移质量。

图片
△ 迁移工具可帮助您以交互的方式将代码迁移到空安全

3. 彻底健全

完全迁移后,Dart 的空安全将十分健全。这意味着 Dart 在前文的示例代码中能够万无一失地确保返回变量、列表和元素不为 null。当 Dart 分析您的代码并确定某个变量不可为空时,该变量将始终不可为空: 如果您在调试程序中检查正在运行的代码,可以看到运行时会保留不可空这一状态。相比之下,某些其他语言的空安全实现则不够健全,在许多情况下仍然需要执行运行时的空检查。Dart 与 Swift 都拥有健全的空安全,但有些编程语言在这方面仍有待改进。
Dart 空安全的健全性还暗含了另一项备受关注的优势: 这意味着您的程序可以更小、更快。由于 Dart 完全能够确定不可空的变量绝不为空,因此可以做出优化。例如,Dart 的运行前 (ahead-of-time, AOT) 编译器可以生成更小更快的原生代码,因为当其知道变量不为空时,便不再需要添加空检查了。
请注意,要获得健全的空安全,您需要将整个项目以及所有依赖项都迁移到空安全。如果您的应用或依赖项有一部分尚未迁移,那么您将获得部分空安全,这会保留大多数空安全检查,但并未得到完全优化,因此不能保证应用是完全安全的。


空安全发布时间表

空安全何时可用于生产?目前的时间表如下:
1. 通过 技术预览版 2 为 Flutter 提供实验性支持: 即这次发布的版本。由于我们已经将核心 Flutter 框架成功迁移到空安全,因此您可以尝试使用空安全,以了解新的语言特性,请使用这个简单的 Flutter 示例进行体验。如果您是 package 作者,并且使用的依赖不多且已由我们完成了空安全迁移,那么您也可以开始试着进行迁移。您需要提供实验性的参数标记 (experiment flag),不要在生产环境中使用,也不要发布任何迁移的 package。
  • Flutter 空安全示例

    https://github.com/flutter/samples/tree/master/experimental/null_safety

  • 实验性的参数标记

    https://dart.cn/tools/experiment-flags

2. 使用 beta 测试版 进行 package 早期迁移: 今年晚些时候,我们将完成性能调试并进行充分的测试,以确保空安全特性可以按预期运行,并且具备优秀的向后兼容性。届时,我们将发布空安全特性的 beta 测试版,您将无需再提供实验性参数标记。我们希望 package 所有者届时着手将其 package 迁移至空安全,这将帮助我们进行最后一轮验证,确保该特性已做好发布稳定版的准备。
3. 使用 稳定版 进行生产: 接下来,我们将解决来自 beta 测试版的反馈,修复所有遗留问题,然后发布稳定版。很难为此设定一个具体的时间表,但我们正在考虑于明年年初发布。空安全特性稳定后,我们希望开发者可以广泛使用空安全,将空安全应用发布到商店,并将众多空安全 package 以稳定版本的形式发布到 pub.dev

即刻上手体验

您可以立即体验空安全!为了帮助大家快速上手,我们准备了这个空安全版 DartPad

  • 空安全版 DartPad
    https://nullsafety.dartpad.cn/

如果您想在 VS Code、Android Studio 或终端上尝试空安全,请查看 Flutter 空安全示例应用。此应用包含执行说明以及两个版本的小型天气应用: 一个版本未使用空安全且包含一些零星的空值错误,而另一个版本则使用空安全来确保这些问题得到处理。如果您希望使用全新的 Flutter 应用进行实验,则可以运行 flutter create,然后按照实验说明来启用空安全。请注意,您需要使用 dev 渠道的 Flutter SDK (1.24.0-3.0.pre 或更高版本),因为当前的 stable 版和 beta 版 Flutter 不支持空安全。

  • Flutter 空安全示例应用

    https://github.com/flutter/samples/tree/master/experimental/null_safety

  • 实验说明

    https://github.com/flutter/flutter/wiki/Experimenting-with-null-safety-in-Flutter

要详细了解空安全特性的设计,请阅读新版的《深入理解空安全》文档。如果您希望通过视频短片来了解空安全,请观看在几个月前的 Flutter Day 中的空安全演讲视频。

  • 腾讯视频链接

    https://v.qq.com/x/page/i3126s1tjww.html

  • Bilibili 视频链接

    https://www.bilibili.com/video/BV1TD4y1U7ST/

  • 深入理解空安全
    https://dart.cn/null-safety/understanding-null-safety
  • Flutter Day
    https://space.bilibili.com/64169458/favlist?fid=1025514958
我们非常高兴为 Dart 引入健全的空安全,它是 Dart 的一项独特特性,可帮助您减少代码中的错误并获得更好的性能。我们希望您积极尝试技术预览版中的空安全特性,并通过问题反馈页面向我们提交反馈。祝大家编程愉快!

  • 体验空安全特性
    https://github.com/flutter/flutter/wiki/Experimenting-with-null-safety-in-Flutter
  • 提交反馈
    https://github.com/dart-lang/sdk/issues/new?title=Null%20safety%20feedback:%20[issue%20summary]&labels=NNBD&body=Describe%20the%20issue%20or%20potential%20improvement%20in%20detail%20here

图片

推荐阅读

图片
图片
图片
图片 点击屏末  | 即刻访问 Flutter 开发者社区中文资源

图片

图片

图片

83280Dart 健全的空安全: 技术预览版 2

这个人很懒,什么都没留下

文章评论