类型设计
最后更新于:2022-04-02 05:55:27
[TOC]
在程序中记下类型时,会限制流入代码不同部分的值的类型。类型可以出现在两种地方:声明上的类型注释和泛型调用的类型参数。
当您想到“静态类型”时,类型注释是您通常会想到的。您可以键入注释变量,参数,字段或返回类型。在以下示例中,bool和String为类型注释。它们挂起代码的静态声明结构,并且不会在运行时“执行”。
~~~
bool isEmpty(String parameter) {
bool result = parameter.length == 0;
return result;
}
~~~
泛型调用是集合字面量、对泛型类构造函数的调用或泛型方法的调用。在下一个示例中,num和int是泛型调用的类型参数。尽管它们是类型,但它们是一级实体,在运行时被具体化并传递给调用。
~~~
var lists = [1, 2];
lists.addAll(List.filled(3, 4));
lists.cast();
~~~
我们在这里强调“泛型调用”部分,因为类型参数也可以 出现在类型注释中:
~~~
List ints = [1, 2];
~~~
在这里,int是一个类型参数,但是它出现在类型注释中,而不是泛型调用中。您通常不需要担心这种区别,但是在一些地方,对于在通用调用中使用类型而不是类型注释,我们有不同的指导教程。
在大多数情况下,Dart允许您省略类型注释,并根据附近的上下文为您推断类型,或者默认为动态类型。Dart同时具有类型推断和动态类型的事实导致了人们对代码是“非类型”的含义的困惑。这是否意味着代码是动态类型的,或者您没有编写类型?为了避免混淆,我们避免说“untyping”,而是使用以下术语:
* 如果代码是带类型注释的,则该类型显式地写在代码中。
* 如果推断出代码,则没有编写类型注释,Dart自己成功地找到了类型。推理可能会失败,在这种情况下,指南不考虑推断。在某些地方,推理失败是静态错误。在其他情况下,Dart使用了动态备份类型。
* 如果代码是动态的,那么它的静态类型就是特殊的动态类型。代码可以被显式地注释为动态的,也可以被推断出来。
换句话说,某些代码是注释的还是推断的,与它是动态的还是其他类型的正交。
推理是一种强大的工具,可以让您省去编写和阅读那些明显或无趣的类型的工作。在明显的情况下省略类型也会将读者的注意力吸引到显式类型上,因为这些类型很重要,比如强制类型转换。
显式类型也是健壮,可维护代码的关键部分。它们定义了API的静态形状。它们记录并强制允许哪些值允许到达程序的不同部分。
这里的指导方针在我们在简洁性和明确性,灵活性和安全性之间找到了最佳平衡。在决定要编写哪些类型时,您需要回答两个问题:
* 我应该写哪种类型,因为我认为最好让它们在代码中可见?
* 我应该写哪种类型因为推理无法为我提供这些类型?
这些指南可以帮助您回答第一个问题:
* 如果类型不明显,则优先对公共字段和顶级变量进行类型注释。
* 如果类型不明显,请考虑对私有字段和顶级变量进行类型注释。
* 避免类型注释初始化的局部变量。
* 避免在函数表达式上注释推断的参数类型。
* 避免泛型调用上的冗余类型参数。
这些涵盖了第二个:
* 当Dart推断出错误的类型时,请进行注释。
* 优先使用动态注释,而不是让推断失败。
其余指南涵盖了有关类型的其他更具体的问题。
## 如果类型不明显,则优先对公共字段和顶级变量进行类型注释。
类型注释是关于如何使用库的重要文档。它们在程序的区域之间形成边界以隔离类型错误的来源。考虑:
~~~
【bad】
install(id, destination) => ...
~~~
这里,id是什么还不清楚。一个字符串?目的是什么?字符串还是文件对象?这个方法是同步的还是异步的?这是清晰的:
~~~
Future install(PackageId id, String destination) => ...
~~~
但在某些情况下,类型是如此明显,以至于编写它是毫无意义的:
~~~
const screenWidth = 640; // Inferred as int.
~~~
“显而易见”并没有明确的定义,但这些都是很好的候选者:
* 字面量。
* 构造函数调用。
* 对显式类型化的其他常量的引用。
* 数字和字符串的简单表达式。
* 工厂方法,如int.parse()、Future.wait()等,读者应该很熟悉。
如果有疑问,请添加类型注释。即使类型很明显,您可能仍然希望显式注释。如果推断类型依赖于来自其他库的值或声明,您可能希望键入注释您的声明,以便对其他库的更改不会在您没有意识到的情况下悄无声息地更改您自己的API的类型。
## 如果类型不明显,请考虑对私有字段和顶级变量进行类型注释。
在公开声明上键入注释可以帮助代码的用户。私有成员上的类型帮助维护人员。私有声明的范围更小,那些需要知道声明类型的人也更可能熟悉周围的代码。这使得更依赖于推理和省略私有声明类型变得合理,这就是为什么这个指南比上一个指南更温和的原因。
如果您认为初始化器表达式(无论它是什么)足够清晰,那么您可以省略注释。但是如果您认为注释有助于使代码更清晰,那么添加一个注释。
## 避免类型注释初始化的局部变量。
局部变量的作用域非常小,尤其是在函数往往很小的现代代码中。省略类型会将读者的注意力集中在变量的更重要的名称及其初始值上。
~~~
List
';
- > possibleDesserts(Set
- >[];
for (var recipe in cookbook) {
if (pantry.containsAll(recipe)) {
desserts.add(recipe);
}
}
return desserts;
}
~~~
~~~
【bad】
List
- > possibleDesserts(Set
- > desserts =
- >[];
for (List