Fork me on GitHub

Dart语言最佳实践

代码风格

标识符

  • 类名采用UpperCamelCase风格写法(即:单词首字母大写,多单词直接拼接,同Java中类命名规则)

  • 库和源文件使用lowercase_with_underscores风格(即:小写字母,多单词以_分割—)

  • import的库文件设置别名时,别名也使用lowercase_with_underscores风格

  • 其他标识符均使用驼峰法

  • 字母缩写时,<=两个字符均大写(如:IO),>两个字符首字符大写(如:Http)

  • 不要使用字母前缀(如:Java中常见的mName写法)

排序

  • dart:系列import放在最前面
  • package:系列import放在相对import前面
  • 三方包的package:放在自己写的package:前面
  • export写在所有import后面,且留白一行以分开
  • 同一优先级的import块按照字母进行排序

格式化

  • 使用dartfmt进行代码格式化
  • 避免一行超过80个字符的长度
  • 对所有的流式控制均使用{}

用法

  • 引用自己的lib包中的文件时,考虑使用相对路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /// my_package
    /// └─ lib
    /// ├─ src
    /// │ └─ utils.dart
    /// └─ api.dart

    // api.dart引用utils.dart时
    // good
    import 'src/utils.dart';

    // bad
    import 'package:my_package/src/utils.dart';

字符串

  • 使用相邻字符串方式进行字符串拼接

    1
    2
    3
    4
    5
    6
    7
    8
    // good
    raiseAlarm(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');

    // bad
    raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
    'parts are overrun by martians. Unclear which are which.');
  • 使用插值方式组合字符串和值

    1
    2
    3
    4
    5
    // good
    'Hello, $name! You are ${year - birth} years old.';

    // bad
    'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';
  • 插值时避免使用{},除非必要

    1
    2
    3
    4
    5
    6
    7
    8
    // good
    'Hi, $name!'
    "Wear your wildest $decade's outfit."
    'Wear your wildest ${decade}s outfit.'

    // bad
    'Hi, ${name}!'
    "Wear your wildest ${decade}'s outfit."

集合

Dart支持四种集合:list、map、queue和set

  • 使用集合简化式进行实例化

    1
    2
    3
    4
    5
    6
    7
    // good
    var points = [];
    var addresses = {};

    // bad
    var points = List();
    var addresses = Map();
  • 不要使用.length来判断集合是否为空

    1
    2
    3
    4
    5
    6
    7
    // good
    if (lunchBox.isEmpty) return 'so hungry...';
    if (words.isNotEmpty) return words.join(' ');

    // bad
    if (lunchBox.length == 0) return 'so hungry...';
    if (!words.isEmpty) return words.join(' ');
  • 推荐使用集合的高阶函数进行相关处理(如:wheremap

    1
    2
    3
    4
    // good
    var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);
  • 避免在Iterable.forEach()中处理方法体编码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // good
    for (var person in people) {
    ...
    }

    // bad
    people.forEach((person) {
    ...
    });

    // 只有forEach中是函数指针, 才鼓励使用, 如下
    people.forEach(print);
  • 除了要改变结果类型,否则不要使用List.from()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // good
    // Creates a List<int>:
    var iterable = [1, 2, 3];

    // Prints "List<int>":
    print(iterable.toList().runtimeType);

    // bad
    // Creates a List<int>:
    var iterable = [1, 2, 3];

    // Prints "List<dynamic>":
    print(List.from(iterable).runtimeType);

    需要改变类型时,可使用该方法

    1
    2
    3
    4
    // good
    var numbers = [1, 2.3, 4]; // List<num>.
    numbers.removeAt(1); // Now it only contains integers.
    var ints = List<int>.from(numbers);
  • 使用whereType()来进行集合的类型过滤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // good
    var objects = [1, "a", 2, "b", 3];
    var ints = objects.whereType<int>();

    // bad
    var objects = [1, "a", 2, "b", 3];
    var ints = objects.where((e) => e is int);

    // bad
    var objects = [1, "a", 2, "b", 3];
    var ints = objects.where((e) => e is int).cast<int>();
  • 当有更方便的操作符可使用时,不要使用cast

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // good
    var stuff = <dynamic>[1, 2];
    var ints = List<int>.from(stuff);

    // bad
    var stuff = <dynamic>[1, 2];
    var ints = stuff.toList().cast<int>();


    // good
    var stuff = <dynamic>[1, 2];
    var reciprocals = stuff.map<double>((n) => 1 / n);

    // bad
    var stuff = <dynamic>[1, 2];
    var reciprocals = stuff.map((n) => 1 / n).cast<double>();
  • 尽量避免使用cast关键字

    • 当有更方便的操作符可使用时,不要使用cast

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // good
      var stuff = <dynamic>[1, 2];
      var ints = List<int>.from(stuff);

      // bad
      var stuff = <dynamic>[1, 2];
      var ints = stuff.toList().cast<int>();


      // good
      var stuff = <dynamic>[1, 2];
      var reciprocals = stuff.map<double>((n) => 1 / n);

      // bad
      var stuff = <dynamic>[1, 2];
      var reciprocals = stuff.map((n) => 1 / n).cast<double>();
    • 创建集合时,通过指定泛型类型代替cast

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // good
      List<int> singletonList(int value) {
      var list = <int>[];
      list.add(value);
      return list;
      }

      // bad
      List<int> singletonList(int value) {
      var list = []; // List<dynamic>.
      list.add(value);
      return list.cast<int>();
      }
    • 遍历集合每项元素时,使用as代替cast

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // good
      void printEvens(List<Object> objects) {
      // We happen to know the list only contains ints.
      for (var n in objects) {
      if ((n as int).isEven) print(n);
      }
      }

      // bad
      void printEvens(List<Object> objects) {
      // We happen to know the list only contains ints.
      for (var n in objects.cast<int>()) {
      if (n.isEven) print(n);
      }
      }
    • 需要对集合中的大多数元素做操作时,使用List.from()代替cast

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // good
      int median(List<Object> objects) {
      // We happen to know the list only contains ints.
      var ints = List<int>.from(objects);
      ints.sort();
      return ints[ints.length ~/ 2];
      }

      // bad
      int median(List<Object> objects) {
      // We happen to know the list only contains ints.
      var ints = objects.cast<int>();
      ints.sort();
      return ints[ints.length ~/ 2];
      }

    注:

    • cast()方法返回的是一个Lazy的集合,如果只对少量元素进行操作,Lazy是有好处的;但如果对于大多数元素进行操作时,这种包装的开销会与Lazy带来的好处相抵消,甚至有过之。
    • cast()是在运行时检测,相当于延后了错误发生的时机,而泛型方式是在编译时检测,更可取。

函数

  • 使用函数声明式来绑定函数和名称

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // good
    void main() {
    localFunction() {
    ...
    }
    }

    // bad
    void main() {
    var localFunction = () {
    ...
    };
    }
  • 能够传递函数指针时不要取传递冗余的lambda式

    1
    2
    3
    4
    5
    6
    7
    // good
    names.forEach(print);

    // bad
    names.forEach((name) {
    print(name);
    });

参数

  • 命名可选参数中指定默认参数时使用=代替:

    1
    2
    3
    4
    5
    // good
    void insert(Object item, {int at = 0}) { ... }

    // bad
    void insert(Object item, {int at: 0}) { ... }

    注::作为指定默认参数值的用法,正在被废弃

  • 不要显式地置顶参数默认值为null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // good
    void error([String message]) {
    stderr.write(message ?? '\n');
    }

    // bad
    void error([String message = null]) {
    stderr.write(message ?? '\n');
    }

    注: 不指定默认参数值,Dart语言机制默认指定为null,不需要冗余指定

变量

  • 初始化变量时不要显式地置顶变量为null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // good
    int _nextId;

    class LazyId {
    int _id;

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

    return _id;
    }
    }


    // bad
    int _nextId = null;

    class LazyId {
    int _id = null;

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

    return _id;
    }
    }
  • 不要存储你能计算的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // bad
    class Circle {
    num radius;
    num area;
    num circumference;

    Circle(num radius)
    : radius = radius,
    area = pi * radius * radius,
    circumference = pi * 2.0 * radius;
    }


    // bad
    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;
    }
    }


    // good
    class Circle {
    num radius;

    Circle(this.radius);

    num get area => pi * radius * radius;
    num get circumference => pi * 2.0 * radius;
    }

    主要出于以下方面考虑:

    • radius被重新复制后缓存的有效性
    • 存储的变量有被恶意赋值的安全性
    • 程序占用的内存空间大小

    注:这里针对的是普通计算,而对于开销较大的计算,仍然要考虑存储结果作为cache。

类成员

Dart的类成员包含成员属性和方法(静态方法和实例方法)

  • 不要给成员属性设置settergetter方法,除非必须

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // good
    class Box {
    var contents;
    }

    // bad
    class Box {
    var _contents;
    get contents => _contents;
    set contents(value) {
    _contents = value;
    }
    }
  • 对于只读的成员属性,尽量用final修饰

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // good
    class Box {
    final contents = [];
    }

    // bad
    class Box {
    var _contents;
    get contents => _contents;
    }

    注:对于需要在构造方法之外复制的成员属性,可使用private的成员属性 + public的getter方法来满足

  • 对于简单的成员变量/方法可考虑使用=>

    1
    2
    3
    4
    5
    6
    7
    // good
    double get area => (right - left) * (bottom - top);

    bool isReady(num time) => minTime == null || minTime <= time;

    String capitalize(String name) =>
    '${name[0].toUpperCase()}${name.substring(1)}';

    但不要为了使用=>而强行将多行表达式进行聚合,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // good
    Treasure openChest(Chest chest, Point where) {
    if (_opened.containsKey(chest)) return null;

    var treasure = Treasure(where);
    treasure.addAll(chest.contents);
    _opened[chest] = treasure;
    return treasure;
    }

    // bad
    Treasure openChest(Chest chest, Point where) =>
    _opened.containsKey(chest) ? null : _opened[chest] = Treasure(where)
    ..addAll(chest.contents);

    还可以对无返回值的成员使用=>

    1
    2
    3
    // good
    num get x => center.x;
    set x(num value) => center = Point(value, center.y);
  • 除非你必须要使用this避免歧义,否则不要使用它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // good
    class Box {
    var value;

    void clear() {
    update(null);
    }

    void update(value) {
    this.value = value;
    }
    }

    // bad
    class Box {
    var value;

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

    void update(value) {
    this.value = value;
    }
    }

    另,Dart语言中,构造方法里不会出现成员变量与构造方法参数同名的歧义问题,如下写法是合法的

    1
    2
    3
    4
    5
    6
    7
    8
    // good
    class Box extends BaseBox {
    var value;

    Box(value)
    : value = value,
    super(value);
    }
  • 尽可能在成员变量声明时进行初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // good
    class Folder {
    final String name;
    final List<Document> contents = [];

    Folder(this.name);
    Folder.temp() : name = 'temporary';
    }

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

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

构造方法

  • 尽可能使用标准构造式

    注:标准构造式,官方原文是initializing formals,参代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // good
    class Point {
    num x, y;
    Point(this.x, this.y);
    }

    // bad
    class Point {
    num x, y;
    Point(num x, num y) {
    this.x = x;
    this.y = y;
    }
    }
  • 不要在标准构造式中指定类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // good
    class Point {
    int x, y;
    Point(this.x, this.y);
    }

    // bad
    class Point {
    int x, y;
    Point(int this.x, int this.y);
    }
  • 对于空方法体的构造方法,使用;来替换{}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // good
    class Point {
    int x, y;
    Point(this.x, this.y);
    }

    // bad
    class Point {
    int x, y;
    Point(this.x, this.y) {}
    }
  • 不要使用关键字new

    Dart1中的关键字new是需要的,单Dart2已经将其变为可选的了。官方为了减轻升级迁移的负担,仍然支持了new关键字,但不建议使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // good
    Widget build(BuildContext context) {
    return Row(
    children: [
    RaisedButton(
    child: Text('Increment'),
    ),
    Text('Click!'),
    ],
    );
    }

    // bad
    Widget build(BuildContext context) {
    return new Row(
    children: [
    new RaisedButton(
    child: new Text('Increment'),
    ),
    new Text('Click!'),
    ],
    );
    }
  • 不要使用冗余的关键字const

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // good
    const primaryColors = [
    Color("red", [255, 0, 0]),
    Color("green", [0, 255, 0]),
    Color("blue", [0, 0, 255]),
    ];

    // bad
    const primaryColors = const [
    const Color("red", const [255, 0, 0]),
    const Color("green", const [0, 255, 0]),
    const Color("blue", const [0, 0, 255]),
    ];

异步操作

  • 尽可能使用asyncawait来替换Future的链式风格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // good
    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;
    }
    }


    // bad
    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;
    });
    }
  • 在关键字async并无实际效果时不要使用它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // good
    Future afterTwoThings(Future first, Future second) {
    return Future.wait([first, second]);
    }

    // bad
    Future afterTwoThings(Future first, Future second) async {
    return Future.wait([first, second]);
    }

    以下几种场景,async是有实际效果的

    • 使用了await关键字
    • 返回一个异步错误时,async + throwreturn Future.error(...)的简写形式
    • 你想在你方法的返回值中隐式包一个Future

    以上三种情况对应代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // good
    Future usesAwait(Future later) async {
    print(await later);
    }

    Future asyncError() async {
    throw 'Error!';
    }

    Future asyncValue() async => 'value';
  • 考虑使用高阶函数来处理流

    Streams提供了一系列高阶函数对流数据进行操作处理,建议优先考虑使用高阶函数

  • 避免直接使用Completer对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // bad
    Future<bool> fileContainsBear(String path) {
    var completer = Completer<bool>();

    File(path).readAsString().then((contents) {
    completer.complete(contents.contains('bear'));
    });

    return completer.future;
    }


    // good
    Future<bool> fileContainsBear(String path) {
    return File(path).readAsString().then((contents) {
    return contents.contains('bear');
    });
    }


    // good
    Future<bool> fileContainsBear(String path) async {
    var contents = await File(path).readAsString();
    return contents.contains('bear');
    }
  • FutureOr<T>对象进行消除歧义时,优先判断Future<T>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // good
    Future<T> logValue<T>(FutureOr<T> value) async {
    if (value is Future<T>) {
    var result = await value;
    print(result);
    return result;
    } else {
    print(value);
    return value as T;
    }
    }

    // bad
    Future<T> logValue<T>(FutureOr<T> value) async {
    if (value is T) {
    // 传递Future<Object>时会永远走该分支
    print(value);
    return value;
    } else {
    var result = await value;
    print(result);
    return result;
    }
    }

------------- The end -------------