Dart 编码规范:函数的正确使用方式

4,696 阅读3分钟

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

前言

在 Dart 中,函数也是一种对象,因此在任何时候我们都可以当做和普通的变量一样进行传值或使用。有所区别的是函数是可以携带参数的,同时在 Dart 中也有 lambda 函数。在使用函数的时候,也需要遵循一定的规范,使得我们的代码更易于阅读。

规范1:如果是命名函数,那么请使用声明的方式

在现代的编程语言中,可以很方便地创建匿名函数,例如闭包的使用。函数内部也可以定义其他函数,实现嵌套调用。大多数情况下这种方式适用于进行即时的回调。但是,如果函数需要命名的话,那么应该在使用声明的方式,而不是赋值的形式。例如下面的情况:

// 正确示例
void main() {
  void localFunction() {
    ...
  }
}

// 错误示例1
void main() {
  var localFunction = () {
    ...
  };
}

// 错误示例2
void main() {
  var localFunction = () => something;
}

规范2:将函数作为参数传递,而不是创建 lambda 函数

在某些情况下,我们需要在一个函数中,再调用另一个函数对一个对象进行处理,通常的做法是创建 lambda 来实现。但是如果这个被调用的函数已经定义好了,那么就不要再创建 lambda 函数了,因为 Dart 提供了一种直接将该函数作为参数传递过去就好了。听着有点绕,我们看个例子,比如我们要循环打印一个名字数组,Dart 允许直接将 print 方法传递个 forEach 直接操作每一个元素。

var names = ['Bobby', 'Jack', 'Rose'];
// 正确示例
name.forEach(print);

// 错误示例
name.forEach((name) {
  print(name);
});

规范3:使用=操作符设置命名参数的默认值

Dart 的函数支持命名参数,定义方式是如下:

void someFunction(Object item, {required int a, int b = 0, Object? c}) {
  ...
}

其中第一个参数 item 是非命名参数,第2个参数 a 是必传命名参数,第3个参数 b 是非必传参数(有默认值),第4个参数是可为空的参数(可不传,也可传 null)。Dart 虽然也支持使用冒号来设置默认值,但是不推荐:

// 错误示例
void someFunction(Object item, {required int a, int b: 0, Object? c}) {
  ...
}

规范4:类型声明放在参数前面,而不是使用冒号声明类型

其实这 是一个错误,也是本人一个偶然的发现。有些语言例如Swift 和 Kotlin会把类型声明放到参数后面。

// swift 函数
func sayHello(name: String) -> Void {
  print( "Hello, \(name)");
}
// Kotlin 函数
fun sayHello(name: String) {
  println("Hello, $name");
}

一开始如果从这两种语言转换到 Dart 的话,可能也会有类似的写法,而且 Dart 并不会报错

void sayHello({name: String}) {
  print('Hello, $name');
}

这样也能执行,这是怎么回事呢?实际上在 Dart 中,所有的类型也是一种对象,因此上面的代码,实际上 name 的类型是 dynamic,只是初始值是 String 类型对象。我们打印出来看一下。

void sayHello({name: String}) {
  print(name);
  print(name.runtimeType);
  print('Hello, $name');
}
sayHello();
sayHello(name: 'Jack');


// 结果
String
Type
Hello, String
Jack
String
Hello, Jack

可以看到,类型是随着传入的参数动态变化的,因此这是一个错误的用法

规范5:合理地使用函数别名

很多时候我们可以使用函数作为参数传递,以便用于回调。使用 Function 这种通用的函数类型也能用,但是这样会降低代码的可读性,而且 Function 这样的声明可以接收任意形式的回调方法,例如下面这样是不推荐的。

// 错误示例
void callbackFunc({callback: Function}) {
    callback();
  }

  callbackFunc(callback: () {
    print('Hello!');
  });

正确的做法是设置函数别名,比如 Flutter 自带的 VoidCallBackValueChanged就是函数的别名。

// 正确示例
typedef VoidCallback = void Function();
typedef ValueChanged = void Function<T>(T value);

void callbackFunc({callback: ValueChanged}) {
    callback('Jack');
  }

  callbackFunc(callback: (name) {
    print('Hello, $name');
  });

通过这种方式可以明确地知道回调函数的需要什么参数和具体的返回值,代码的健壮性会好很多。

总结

本篇介绍了 Dart 的函数的合理的使用方式,实际上 Dart本身是很灵活的一门编程语言,这也带来了一个弊端就是写法引人而异,可能导致代码质量参差不齐。因此,开发时最好是参照官方制定内部的规范,以保障编码的合理性。

我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!