【CSDN 编者按】同受 Google 支持,一个是其官方开发的语言——Dart,一个被扶持为 Android 开发的官方语言——Kotlin。放在一起看,Kotlin 对比 Dart 的优越性远不止更安全这么简单,本文作者罗列出 13 个理由。
我非常喜欢 Flutter,我认为它是开发高质量多平台应用的最佳选择之一,然而,我却不怎么喜欢 Dart(编写 Flutter 的语言)。为什么?和 Kotlin 相比,原因如下。
没有 null 安全
我知道这个问题近期就能得到解决(https://dart.dev/null-safety),但目前我们仍然需要面对。2018 年,Dart 首次发布的时候(我指的是 Dart 2),就没有 null 安全,太不应该了。
没有数据类
数据类的主要目的是保存数据。在数据驱动开发模型中,数据类就是值对象,而且在适当的语言支持下,不可变的数据类型非常方便函数式编程。
Kotlin 有下面这样的支持:
data class User(val name: String, val age: Int)
val user1 = User(name =“John”, age = 30)
val user2 =user1.copy(age = 20)
即便不考虑复制(和相等比较)功能的实现,Dart 的写法也非常冗长:
class User {
User({this.name,this.age});
final String name;
final int age;
}
final user1 = User(name: ‘John’, age: 30);
final user2 = User(name: user1.name, age: 20);
而且 Dart 还缺少 Kotlin 提供的某些功能。例如,在 Kotlin 中,你可以将这些参数作为位置参数或命名参数进行传递(甚至可以混合使用:val user = User('John', age = 30)),而在 Dart 中,位置或名称,你只能选择其一(但是它们是可选的,默认为 null)。
有一个很好的包 build_value,可以解决深度相等性检查和生成复制方法的问题,我们经常在代码库中使用这个包,但这不是理想的解决方案。
首先,与 Kotlin 版本相比,Dart 需要一些样板代码:
abstract class User implements Built<User, UserBuilder> {
User._();
factory User([voidFunction(UserBuilder) updates]) = _$User;
String get name; int getage;
}
final user1 = User((b) => b ..name = ‘John’ ..age = 30);
final user2 = user1.rebuild((b) => b..age = 20);
其次,Dart 缺乏一些功能,比如没有简单的方法让编辑器在编译期间检查必须参数是否传递。
Dart 能够提供数据类的支持吗?可能会,但近期内还不行。
没有密封类
我们经常使用的 Kotlin 的另一个功能是密封类。从本质上讲,密封类表示了一种受限的类层次结构:值只能是有限的几种类型中的一种。与枚举不同,这个值不是 singleton,而是一个适当的类,可以拥有多个不同状态的实例。
为什么密封类很实用?我们来看一个很常见的例子:
sealed class Result<out VALUE> {
data class Error(valerror: Throwable) : Result<Nothing>()
data classSuccess<VALUE>(val value: VALUE) : Result<VALUE>()
}
fun process(result: Result<String>): String = when(result) {
is Result.Error ->“Error: ${result.error}”
is Result.Success ->“Success: ${result.value}”
}
用这个方法来代替异常处理很不错:它并不是去捕获异常(因为有时候会忘记捕获异常),而是强迫使用者处理结果,结果可能是错误的或正确的(在许多函数式语言和库中该类型被称为 Either)。不仅你需要考虑可能出现的错误,而且 Kotlin 也会提供一些很好的功能。你看,when 分支中有类型转换吗?你不需要进行类型转换,因为 Kotlin 很聪明,会自动进行转换。
那么 Dart 呢?我们希望有一天 Dart 也能提供这些功能。
枚举不支持自定义值
有时,我们需要在枚举中添加一些值。Kotlin 的写法很简单:
enum class Level(val value: Int) {
INFO(10),
WARNING(20),
ERROR(30)
}
val value = Level.WARNING.value
Dart 没有类似的功能。但你可以使用扩展:
enum Level {
info,
warning,
level
}
extension LevelValue on Level {
int get value {
switch(this) {
case Level.info:
return 10;
case Level.warning:
return 20;
case Level.level:
return 30;
}
}
}
final value = Level.warning.value;
首先,这段代码太冗长了;其次,请参照下面这一点。
编译器不够智能
前面的示例给出了如下警告:
This function has a return type of ‘int’, butdoesn’t end with a return statement. Try adding a return statement, or changingthe return type to ‘void’.
什么意思?Level 枚举只有 3 个选项,而且都列出来了。该函数不可能返回 void。我不想添加 default 语句(否则,如果我在枚举中添加了另一个选项,但忘记了更新扩展,就可能会返回错误的结果)。出于相同的原因,我不想忽略这个警告。我希望当且仅当所有选项都不匹配的时候报错,而 Kotlin 就是这么做的。
没有 singleton
如何在Kotlin中定义singletons?很简单:object Singleton。Dart有类似的东西吗?最简单的写法可能是:
class Singleton {
const Singleton._();
factory Singleton()=> const Singleton._();
}
当然,这也没什么大不了,但是如果你必须针对每种情况重复编写上述代码,那么工作量就大了。在 Kotlin 项目中,我们经常使用 singletons,例如作为密封类的“常量”等等,但 Dart 中连密封类都没有。
没有 switch / if / try 表达式
还记得在讨论 Dart 没有密封类时,我们提到的示例吗?虽然没有关键字 return,但函数仍然会返回字符串。这有可能要归功于两个方面:
-
fungetAnswer(): String = "42" 等价于 fun getAnswer(): String { return "42" }。在 Dart 中,你甚至只需编写 StringgetAnswer() => '42';
-
when 是一个表达式:这意味着你可以返回 when 的结果,并且编译器足够聪明,可以推断出正确的类型,因为每个条件分支都会返回 String。
这不仅仅是语法简洁性的问题,编译器返回 when,是为了强迫我们提供所有的选项(比如对于枚举或密封类,你必须指定所有选项或使用 else 语句)。
没有关键字 protected
为了私有化方法(或变量),我们需要在其名称前添加下划线。然而,当你需要从允许的范围之外访问该方法时,就会遇到编译错误。
为了保护某个方法,你可以使用 meta 包中的 @protected 注释。但是,你会收到静态分析的警告。
Dart 不支持 protected,所以要么是 public,要么是 private,不能折中。
没有类型别名
实际上,Dart 中有类型别名,但仅适用于函数类型。
你可以编写如下代码:
typedefFormatDate = String Function(DateTime);
但是不能写:
typedefJson = Map<String, dynamic>;
如果你的项目需要使用 json,就要在代码中书写大量的Map<String, dynamic>。
也许,有一天 Dart 会解决这个问题吧……
没有简洁的语法
你可能想说:“既然你已经习惯了 Kotlin 的语法,为什么不直接使用 Kotlin?为什么还要用 Dart?”
没错,我是很喜欢 Kotlin 的语法,但不仅仅是因为我习惯了。在 Kotlin 中编写 lambda 语法非常简洁,比如 listOf(1, 2, 3).map {it.toString() },远胜于[1,2,3].map((i) => i.toString());。如果遇到很多行的 lambda(或者 lambda 链),Dart 的书写就会非常复杂。甚至还需要分号……
没有嵌套类/扩展
我非常希望 Dart 能支持嵌套类。比如我们想做一些消息处理:
abstract class Translations {
static abstract classCommon {
static String yes =‘Yes’;
static String no =‘No’;
}
static abstract classAuth {
static String logIn =‘Log in’;
static String logOut =‘Log out’;
}
}
final message = Translations.Auth.logIn;
没有恰当的泛型协变
在 Dart 的类型中,泛型类中的变量是协变的。这有什么不好?因为这种方法无异乎自讨苦吃。我们来看一个例子:
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
void main() {
List<Animal>animals = [Dog()];
List<Cat> cats =[];
cats.add(animals.first);
}
虽然可以通过编译,但是运行的时候会出错:TypeError: Instance of 'Dog': type 'Dog' is not a subtype of type'Cat'。
如果在 Kotlin 中尝试这样的小把戏:
abstract class Animal
class Dog : Animal()
class Cat : Animal()
fun main() {
val animals =listOf<Animal>(Dog())
val cats =mutableListOf<Cat>()
cats.add(animals.first())
}
编译都不过去,它会直接报错:Type inference failed. Expected type mismatch: inferred type isAnimal but Cat was expected。
没有 final 类
《Effective Java》这本书中说:“继承必须经过设计,并通过文档说明,否则就不该使用。”因此,在 Java 中,我们应该尽量使用 final 类。
Kotlin 则更进一步,默认情况下所有类都是 final。那么,Dart 呢?我们根本没办法指定 final 类,没办法禁止继承。
Dart 真的“一文不值”?
虽然上面我罗嗦了一大堆 Dart 的不足,但 Dart 也有一些我非常喜欢的优点:
-
类声明本身就是接口。每个类都定义了一个由 public 成员组成的接口。因此,你只需 implement 类,并提供重载功能:在测试中模拟实现。
-
无类型擦除。与Kotlin不同,List <String>在运行时仍然是List<String>。
-
最后,很重要的一点:Dart 仍在不断发展中。我们有扩展程序,也许很快就能获得 null 安全。
希望有一天,本文所提及的不足都能得到改善,Dart 能够成为一门安全的现代编程语言。
原文链接:https://medium.com/codex/13-reasons-why-dart-is-worse-than-kotlin-eba93dfedd8
声明:本文为 CSDN 翻译,转载请注明来源。
☞三年白干!程序员孙某因违反《竞业协议》赔偿腾讯 97.6 万元,返还 15.8 万元
文章评论