发表时间:2019年4月8日 - 7分钟阅读
导读
在提出关于一些设计选择的观点之前,我们需要定义一些标准。
-
意图/声明 > 用法/执行法 代码一写要明确说明意图。 实现并不比意图重要。
-
隐含是不好的 隐性的东西不与意念同在,因此从第一点来看,意味着任何隐性的东西都隐藏着真实的意念。 如果任何东西隐藏了意图--它可以被认为是坏的。
-
把不同的东西混在一起是不好的。 部分与第一点关于意图有关。 如果你做了改变,却不清楚是因为一件事还是另一件事(它们混为一体)--那么意图就被隐藏了,它是坏的。
现在让我们来看看Dart语言中的一些设计选择,并思考它们是否隐藏了开发者的意图,或者它们可以被认为是好的。
可见性修饰符
import和library指令可以帮助你创建一个模块化和可共享的代码库。库不仅提供API,而且是一个隐私单位:以下划线(_)开头的标识符只有在库内可见。 参考文献
基本上在Dart中每个文件都是一个库。 而实际上这意味着在Dart中只有两个可见性修饰符。
- 到处可见(即公共)
- 仅在库/文件中可见(以下使用库时,也指文件)
类里没有私密性。没有受保护的。 我们可以通过在库中只有一个类的情况下使用private-in-library来模拟private in class。 我们可以通过在一个文件中包含父类和子类并使用private-in-library来模拟private in class。
如果你想让一个属性private-in-class和另一个属性protected......那就倒霉了。这不是Dart的目的。
注意:可见性修改器被设计成这样是因为动态的性能和使用(将在文章后面讨论)。 问题是:动态功能是否如此强大,以至于缺乏可见性修饰符? 参考资料
但在Dart中,没有一些常见的可见性修饰符并不是关于它们的唯一问题。如前所述,只有两个可见性修饰符(public和private-in-library)。 为了区分它们,应该使用_(下划线)。
- property/function() - 是一个公共属性/函数。
- _property/_function() - 是图书馆中的私有财产/函数。
区分两种不同情况的 "巧妙解决方案"。但它是有代价的。
- 如果以后语言的设计者决定增加更多的可见性修饰符--那就很难了。
- 如果你想改变某些属性/函数的可见性,你必须进行重命名重构。
这里的问题是,可见性修饰符与属性/函数的名称混在一起。 混合不同的东西是不好的。 如果你想改变属性的可见性,你应该能够改变可见性。 如果你想改变属性名,你应该能够改变属性名。 如果你想改变属性的可见性,你不应该被强迫改变属性名(包括该属性的所有用法)。
Dart并不擅长于此。 虽然同样是因为动态特性而做的(见上面的链接)。
隐式返回
所有函数都会返回一个值。如果没有指定返回值,语句
return null;会隐式地附加到函数体中。 参考资料
隐性是不好的。
我不能说太多。 只是这种语言,并没有真正的试图保护你免受NullPointerException问题的影响,而是提供给你一种如果你犯了错误缺少返回值的方式来面对,看起来很奇怪。
建议:应该设置lint来捕获丢失的返回值。我们应该设置lint来捕获丢失的返回值。而在每一个返回null;的地方,都要刻意放上明文。
注意:当使用void的时候,这个隐式功能就会变得更加奇怪。你可以阅读这篇文章来了解更多内幕。
隐式接口
每个类都隐式定义了一个接口,包含了该类的所有实例成员和它所实现的任何接口。 参考资料
隐式是不好的。 意图/声明 > 用法/执行法
Dart没有接口关键字。 你不能在Dart中声明接口。 我认为这是一个主要问题。接口>实现。 在Dart中模拟接口的唯一方法是创建抽象类。
这意味着在Dart中抽象类与接口混合在一起。 把不同的东西混在一起是不好的。
Dart的设计者决定用不同的方式来扩展类,而不是将接口和抽象类分开。 你可以使用关键字extension,它将像往常一样工作。 你可以使用关键字 implements,在这种情况下,你将实现一些类定义的隐式接口。
注意:另外,你可以使用Dart的mixin功能添加更多的功能。这是一个值得商榷的功能,它类似于Java中的默认函数接口(当然也有一些不同)。 我在这里省略讨论mixins,因为这是一个相当大的话题。 阅读这篇文章作为一些快速的潜入。
这意味着,如果你向一个类添加一些公共属性,它就会立即成为该类接口的一部分。 并且牢记private-in-library的特性,如果你在一个类中添加任何属性,它就会立即成为该类在库中接口的一部分。 因此,类在库内和库外有不同的隐式接口。
建议:创建显式接口。即使考虑到Dart不支持它们。 在一个单独的文件中创建只具有公共属性/功能的抽象类(以确保你不会与库内私有功能发生冲突)。
注:接口关键字被有意删除 参考资料
功能一流的支持
Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型,Function。这意味着函数可以被分配给变量或作为参数传递给其他函数。 参考文献
更新:正如评论中指出的那样,文章的这部分内容实际上是错误的,因为在函数中可以添加类型信息。 只要有可能,就使用带有显式信息的函数。
Dart对函数的支持是一流的,这很好。 Function没有参数和返回类型的信息是不好的。
一般来说,函数可以用一个函数的类来表示。 类可以像函数一样具有通用类型。但是Dart中的Function没有类型信息(作为动态特性我们稍后会看)。
这意味着如果你提供了错误的参数数量,错误的参数类型或者返回错误的类型(或空),编译器不会帮助你。
建议:不要在Dart中使用Function。不要在Dart中使用Function. 尽量使用显式接口。这可以使你在运行时避免出现奇怪的问题。
动态
语言的特点,似乎使其他设计选择需要。
在任何面向对象的编程语言中,都有一个基本类型。在Java中它是Object,在Kotlin中它是Any,在Dart中它是动态的。 不同的是,在Java/Kotlin中,基本类型支持的方法数量有限(如果你想调用一些不属于基本类型接口的方法,你必须先做一个降序),但在Dart中,动态类型可以通过名字调用任何属性/函数。 这意味着像这样的代码是纯粹有效的(对于传递给函数的真/假值)。
void main() {
dynamic c = get(false);
print(c.a());
}
dynamic get(bool flag) {
if (flag) {
return A();
} else {
return B();
}
}
class A {
dynamic a() {
return "a";
}
}
class B {
dynamic a() {
return "b";
}
}
这意味着Dart更像JavaScript而不像Java。
当你决定有更多的动态的东西而不是静态类型的东西(可以通过编译器验证),你将得到类似JavaScript的语言。
考虑到在Web开发中,支持一些类型的TypeScript比普通的JavaScript更受开发者的青睐,这并不奇怪。 开发者喜欢将一些检查委托给测试和编译器,因为这可以让他们专注于重要的事情。
Dart指出的方向可能是可以理解的,虽然不是作为一种现代语言来看。
另外,在编译过程中不使编译器做很多检查,使得Flutter有热重载功能,因为重新编译是相当快的(因为编译器似乎不做很多检查)。
虽然如果有人问我是希望在运行时有问题的快速编译还是能够在编译时发现问题,我肯定会选择第二种。
建议:避免使用动态。使用显式接口代替。
奖励:Dart类型系统
完善的类型系统意味着你永远无法进入一个表达式评估到的值与表达式的静态类型不匹配的状态。例如,如果一个表达式的静态类型是String,在运行时,你保证在评估它时只能得到一个字符串。 Dart的类型系统,就像Java和C#的类型系统一样,是健全的。 参考文献
让我们创建简单的测试。
void main() {
String s = null;
if (s is String) {
print("string");
} else if (s is Null) {
print("null");
} else {
print ("none");
}
}
我们将null(这是Null类的实例:Reference)分配给一个String变量。 正如文档中所说,每个表达式只能返回预期静态类型的值。 在上面的一个例子中,我们期望值是String类型,但程序将打印 "null"。
在Dart中,Null是一个类型,而Null不是String,但我们可以将null赋值给String变量,即使在运行时也不会出错--这就是Dart类型系统的不一致。
这和另一个同样以特殊方式表现的void的情况,看起来Dart类型系统不够稳定(因此设计得不好)。
祝你编码愉快。
通过www.DeepL.com/Translator(免费版)翻译