阅读 1367

Flutter开发基础——Dart语法规范

字符串

两个常量字符串(不是变量,是放在引号中的字符串),你不需要使用 + 来连接它们。

推荐的写法

print(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');
复制代码

不推荐的写法

print('ERROR: Parts of the spaceship are on fire. Other ' +
    'parts are overrun by martians. Unclear which are which.');
复制代码
不要在字符串中使用不必要的大括号

如果要插入是一个简单的标识符,并且后面没有紧跟着的字母,则应省略 {}

推荐的写法

'Hi, $name!'
"Wear your wildest $decade's outfit."
//标识符后面有紧跟着的字母了 加上大括号用以区分
'Wear your wildest ${decade}s outfit.'
复制代码

不推荐的写法

'Hi, ${name}!'
"Wear your wildest ${decade}'s outfit."
复制代码

布尔值

使用? ?将空值转换为布尔值。

当表达式的值可以为真、假或null,并且您需要将结果传递给不接受null的对象时,此规则适用。一个常见的情况是一个判断空值的方法调用被用作条件:

不推荐的写法

if (optionalThing?.isEnabled) {
  print("Have enabled thing.");
}
复制代码

如果optionalThing为空,此代码将抛出异常。(if只支持判断bool值,不支持null)要解决这个问题,您需要将null值“转换”为true或false。虽然您可以使用==来完成此操作,但我们建议使用?? :

推荐的写法

//如果你想要optionalThing是空值时返回false
optionalThing?.isEnabled ?? false;

//如果你想要optionalThing是空值时返回true
optionalThing?.isEnabled ?? true;
复制代码

不推荐的写法

// 如果你想要optionalThing是空值时返回false
optionalThing?.isEnabled == true;

// 如果你想要optionalThing是空值时返回true
optionalThing?.isEnabled != false;
复制代码

PS:如果这里你有些不理解,可以先阅读一下作者之前的文章Flutter基础——Dart语法,学习一下'?' '??' '??=' 的使用

两种操作都会产生同样的结果,而且做的都是正确的事情,但首选??操作符,主要有三个原因。

  1. ??运算符清楚地表明,代码与空值有关。

  2. 当左边的布尔表达式不会为空时,==true看起来像是多余的。

  3. ??false 和 ??true 清楚地表明了当表达式为null时将使用什么值。对于==true,你必须清楚左边表达式的布尔逻辑,才会明白想要 左边表达式是null时返回false 为什么要用== true。

集合

Dart 集合中原生支持了四种类型:list, map, queue, 和 set。 下面是应用于集合的最佳实践。

尽可能的使用集合字面量。

两种方式来构造一个空的可变 list : []List() 。 同样,有三种方式来构造一个空的Map map:{}Map(), 和 LinkedHashMap()

如果想创建一个固定不变的 list 或者其他自定义集合类型,这种情况下你需要使用构造函数。 否则,使用字面量语法更加优雅。 核心库中暴露这些构造函数易于扩展,但是通常在 Dart 代码中并不使用构造函数。

推荐的写法

var points = [];
var addresses = {};
复制代码

不推荐的写法

var points = List();
var addresses = Map();
复制代码

如果需要的话,你可以提供一个泛型。

推荐的写法

var points = <Point>[];
var addresses = <String, Address>{};
复制代码

不推荐的写法

var points = List<Point>();
var addresses = Map<String, Address>();
复制代码

注意,对于集合类的 命名 构造函数则不适用上面的规则。 List.from()Map.fromIterable() 都有其使用场景。 如果需要一个固定长度的结合,使用 List() 来创建一个固定长度的 list 也是合理的。

不要使用 .length 来判断一个集合是否为空。

通过调用 .length 来判断集合是否包含内容是非常低效的。相反,Dart 提供了更加高效率和易用的 getter 函数:.isEmpty.isNotEmpty。 使用这些函数并不需要对结果再次取非(list.length ! =0)

推荐的写法

if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');
复制代码

不推荐的写法

if (lunchBox.length == 0) return 'so hungry...';
if (!words.isEmpty) return words.join(' ');
复制代码
不要使用 List.from() 除非想修改结果的类型。

给定一个可迭代的对象,有两种常见方式来生成一个包含相同元素的 list:

var copy1 = iterable.toList();
var copy2 = List.from(iterable);
复制代码

推荐的写法

明显的区别是前一个更短。 更重要的区别在于第一个保留了原始对象的类型参数:

// 创建一个 List<int>:
var iterable = [1, 2, 3];

// 输出 "List<int>":
print(iterable.toList().runtimeType);
复制代码

不推荐的写法

// 创建一个 List<int>:
var iterable = [1, 2, 3];

// 输出 "List<dynamic>":
print(List.from(iterable).runtimeType);
复制代码

如果你想要改变原始对象的类型参数,那么可以调用 List.from()

推荐的写法

var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // 现在集合里只包含int型
var ints = List<int>.from(numbers);
复制代码

但是如果你的目的只是复制可迭代对象并且保留元素原始类型, 或者并不在乎类型,那么请使用 toList()

参数

使用 = 来分隔参数名和参数默认值。

由于遗留原因,Dart 同时支持 := 作为参数名和默认值的分隔符。 为了与可选的位置参数保持一致,请使用 =

推荐的写法

void insert(Object item, {int at = 0}) { ... }
复制代码

不推荐的写法

void insert(Object item, {int at: 0}) { ... }
复制代码
不要 显式的为参数设置 null 值。

如果你创建了一个可选参数,那么就不要为其赋默认值, Dart 默认使用 null 作为默认值,所以这里不需要为其 null 赋值语句。

推荐的写法

void error([String message]) {
  stderr.write(message ?? '\n');
}
复制代码

不推荐的写法

void error([String message = null]) {
  stderr.write(message ?? '\n');
}
复制代码

变量

不要 显示的为参数初始化 null 值。

在Dart中,未自动显式初始化的变量或字段将初始化为 null 。 语言保证了赋值的可靠性。在 Dart 中没有“未初始化内存”的概念。 所以使用 = null 是多余的。

推荐的写法

int _nextId;

class LazyId {
  int _id;

  int get id {
    if (_nextId == null) _nextId = 0;
    if (_id == null) _id = _nextId++;

    return _id;
  }
}
复制代码

不推荐的写法

int _nextId = null;

class LazyId {
  int _id = null;

  int get id {
    if (_nextId == null) _nextId = 0;
    if (_id == null) _id = _nextId++;

    return _id;
  }
}
复制代码
避免 保存可计算的结果

在设计类的时候,你常常希望暴露底层状态的多个表现属性。 常常你会发现在类的构造函数中计算这些属性,然后保存起来:

不推荐的写法

class Circle {
  num radius;
  num area;
  num circumference;

  Circle(num radius)
      : radius = radius,
        area = pi * radius * radius,
        circumference = pi * 2.0 * radius;
}
复制代码

上面的代码有两个不妥之处。首先,这样浪费了内存。 严格来说面积和周长是缓存数据。 他们保存的结果可以通过已知的数据计算出来。 他们减少了 CPU 消耗却增加了内存消耗。 我们还没有权衡,到底存不存在性能问题?

更坏的情况是,上面的代码是 错的 。上面的缓存是无效的— 你如何知道什么时候缓存失效了需要重新计算? 在这里,我们永远不会从新计算,即使 radius 是可变的。 你可以给 radius 设置一个不同的值,但是 areacircumference 还是之前的值。

为了正确处理缓存失效,我们需要这样做:

不推荐的写法

class Circle {
  num _radius;
  num get radius => _radius;
  set radius(num value) {
    _radius = value;
    _recalculate();
  }

  num _area;
  num get area => _area;

  num _circumference;
  num get circumference => _circumference;

  Circle(this._radius) {
    _recalculate();
  }

  void _recalculate() {
    _area = pi * _radius * _radius;
    _circumference = pi * 2.0 * _radius;
  }
}
复制代码

这需要编写、维护、调试以及阅读更多的代码。 如果你一开始这样写代码:

推荐的写法

class Circle {
  num radius;

  Circle(this.radius);

  num get area => pi * radius * radius;
  num get circumference => pi * 2.0 * radius;
}
复制代码

上面的代码更加简洁、使用更少的内存、减少出错的可能性。 它尽可能少的保存了表示圆所需要的数据。 这里没有字段需要同步,因为这里只有一个有效数据源。

成员

在 Dart 中,对象成员可以是函数(方法)或数据(实例变量)。 下面是关于对象成员的最佳实践。

不要 为字段创建不必要的 getter 和 setter 方法。

推荐的写法

class Box {
  var contents;
}
复制代码

不推荐的写法

class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}
复制代码
推荐 使用 final 关键字来创建只读属性

如果你有一个变量,对于外部代买来说只能读取不能修改, 最简单的做法就是使用 final 关键字来标记这个变量。

推荐的写法

class Box {
  final contents = [];
}
复制代码

不推荐的写法

class Box {
  var _contents;
  get contents => _contents;
}
复制代码
不要 使用 this. ,在重定向命名函数和避免冲突的情况下除外。

只有当局部变量和成员变量名字一样的时候,你才需要使用 this. 来访问成员变量。 只有两种情况需要使用 this. 。其中一种情况是要访问的局部变量和成员变量命名一样的时候:

推荐的写法

class Box {
  var value;

  void clear() {
    update(null);
  }

  void update(value) {
    this.value = value;
  }
}
复制代码

不推荐的写法

class Box {
  var value;

  void clear() {
    this.update(null);
  }

  void update(value) {
    this.value = value;
  }
}
复制代码
尽可能的在定义变量的时候初始化变量值。

如果一个字段不依赖于构造函数中的参数, 则应该在定义的时候就初始化字段值。 这样可以减少需要的代码并可以确保在有多个构造函数的时候你不会忘记初始化该字段。

不推荐的写法

class Folder {
  final String name;
  final List<Document> contents;

  Folder(this.name) : contents = [];
  Folder.temp() : name = 'temporary'; // Oops! Forgot contents.
}
复制代码

推荐的写法

class Folder {
  final String name;
  final List<Document> contents = [];

  Folder(this.name);
  Folder.temp() : name = 'temporary';
}
复制代码

当然,对于变量取值依赖构造函数参数的情况以及不同的构造函数取值也不一样的情况, 则不适合本条规则。

构造函数

尽可能的使用初始化形式。

许多字段直接使用构造函数参数来初始化,如:

不推荐的写法

class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}
复制代码

为了初始化一个字段,我们需要取_四_次 x 。使用下面的方式会更好:

推荐的写法

class Point {
  num x, y,z;
  Point(this.x, this.y,{this.z});
}
复制代码

这里的位于构造函数参数之前的 this. 语法被称之为初始化形式(initializing formal)。 有些情况下这无法使用这种形式。特别是,这种形式下在初始化列表中无法看到变量。 但是如果能使用该方式,就应该尽量使用。(如果使用命名参数)

; 来替代空的构造函数体 {}

在 Dart 中,没有具体函数体的构造函数可以使用分号结尾。 (事实上,这是不可变构造函数的要求。)

推荐的写法

class Point {
  int x, y;
  Point(this.x, this.y);
}
复制代码

不推荐的写法

class Point {
  int x, y;
  Point(this.x, this.y) {}
}
复制代码
不要 使用 new

创建对象不要使用new

推荐的写法

Widget build(BuildContext context) {
  return Row(
    children: [
      RaisedButton(
        child: Text('Increment'),
      ),
      Text('Click!'),
    ],
  );
}
复制代码

不推荐的写法

Widget build(BuildContext context) {
  return new Row(
    children: [
      new RaisedButton(
        child: new Text('Increment'),
      ),
      new Text('Click!'),
    ],
  );
}
复制代码

异步

推荐 使用 async/await 而不是直接使用底层的特性。

显式的异步代码是非常难以阅读和调试的, 即使使用很好的抽象(比如 future)也是如此。 这就是为何 Dart 提供了 async/await。 这样可以显著的提高代码的可读性并且让你可以在异步代码中使用语言提供的所有流程控制语句。

推荐的写法

Future<int> countActivePlayers(String teamName) async {
  try {
    var team = await downloadTeam(teamName);
    if (team == null) return 0;

    var players = await team.roster;
    return players.where((player) => player.isActive).length;
  } catch (e) {
    log.error(e);
    return 0;
  }
}
复制代码

不推荐写法

Future<int> countActivePlayers(String teamName) {
  return downloadTeam(teamName).then((team) {
    if (team == null) return Future.value(0);

    return team.roster.then((players) {
      return players.where((player) => player.isActive).length;
    });
  }).catchError((e) {
    log.error(e);
    return 0;
  });
}
复制代码

标识符

在 Dart 中标识符有三种类型。

  • UpperCamelCase 每个单词的首字母都大写,包含第一个单词。
  • lowerCamelCase 每个单词的首字母都大写,除了第一个单词, 第一个单词首字母小写,即使是缩略词。
  • lowercase_with_underscores 只是用小写字母单词,即使是缩略词, 并且单词之间使用 _ 连接。
使用 UpperCamelCase 风格命名类型。

Classes(类名)、 enums(枚举类型)、 typedefs(类型定义)、 以及 type parameters(类型参数)应该把每个单词的首字母都大写(包含第一个单词), 不使用分隔符。

class SliderMenu { ... }

class HttpRequest { ... }

typedef Predicate = bool Function<T>(T value);
复制代码
要在文件夹源文件中使用 lowercase_with_underscores 方式命名。
lowercase_with_underscores 风格命名库和源文件名。

推荐的写法

library peg_parser.source_scanner;

import 'file_system.dart';
import 'slider_menu.dart';
复制代码

不推荐的写法

library pegparser.SourceScanner;

import 'file-system.dart';
import 'SliderMenu.dart';
复制代码
使用 lowercase_with_underscores 风格命名导入的前缀。

推荐的写法

import 'dart:math' as math;
import 'package:angular_components/angular_components'
    as angular_components;
import 'package:js/js.dart' as js;
复制代码

不推荐的写法

import 'dart:math' as Math;
import 'package:angular_components/angular_components'
    as angularComponents;
import 'package:js/js.dart' as JS;
复制代码
使用 lowerCamelCase 风格来命名其他的标识符。

类成员、顶级定义、变量、参数以及命名参数等 除了第一个单词,每个单词首字母都应大写,并且不使用分隔符。

推荐的写法

var item;

HttpRequest httpRequest;

void align(bool clearItems) {
  // ...
}
复制代码
把超过两个字母的首字母大写缩略词和缩写词当做普通单词。

首字母大写缩略词比较难阅读, 特别是多个缩略词连载一起的时候会引起歧义。 例如,一个以 HTTPSFTP 开头的名字, 没有办法判断它是指 HTTPS FTP 还是 HTTP SFTP 。

为了避免上面的情况,缩略词和缩写词要像普通单词一样首字母大写, 两个字母的单词除外。 (像 ID 和 Mr. 这样的双字母缩写词仍然像一般单词一样首字母大写。)

推荐的写法

HttpConnectionInfo
uiHandler
IOStream
HttpRequest
Id
DB
复制代码

不推荐的写法

HTTPConnection
UiHandler
IoStream
HTTPRequest
ID
Db
复制代码

译者注:

  • acronyms :首字母缩略词,指取若干单词首字母组成一个新单词,如:HTTP = HyperText Transfer Protocol
  • abbreviations : 缩写词,指取某一单词的部分字母(或其他缩短单词的方式)代表整个单词,如:ID = identification
不要 使用前缀字母

推荐的写法

defaultTimeout
复制代码

不推荐的写法

kDefaultTimeout
复制代码

格式化

使用 dartfmt 格式化你的代码。

格式化是一项繁琐的工作,尤其在重构过程中特别耗时。 庆幸的是,你不必担心。 我们提供了一个名为 dartfmt 的优秀的自动代码格式化程序,它可以为你完成格式化工作。 我们有一些关于它适用的规则的 文档 , Dart 中任何官方的空格处理规则由 dartfmt 生成

其余格式指南用于 dartfmt 无法修复的一些规则。

对所有流控制结构使用花括号。

这样可以避免 dangling else (else悬挂)的问题。

if (isWeekDay) {
  print('Bike to work!');
} else {
  print('Go dancing or read a book!');
}
复制代码

这里有一个例外:一个没有 elseif 语句, 并且这个 if 语句以及它的执行体适合在一行中实现。 在这种情况下,如果您愿意,可以不用括号

if (arg == null) return defaultValue;
复制代码

但是,如果执行体包含下一行,请使用大括号:

推荐的写法

if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}
复制代码

不推荐的写法

if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;
复制代码

如果有时间推荐看一看官网上详细的Effective Dart,https://www.dartcn.com/guides/language/effective-dart/
作者把一些flutter常用的dart语法规范总结了出来,一些语法做了补充。

欢迎关注微信公众号,文章会首发在微信公众号。 可以搜索公众号名 ‘’杰克的程序人生‘’

整洁的代码是创造一个好工程的前提