Contents

Effective Dart: Usage

Contents

您每天都可以在Dart代码的正文中使用这些准则. 图书馆的用户可能无法告诉您您已经在这里将想法内部化了,但是图书馆的维护者肯定会的.

Libraries

这些准则可帮助您以一致,可维护的方式由多个文件组成程序. 为了保持这些原则简单地说,他们使用的"进口",以覆盖importexport指令. 该准则对两者均适用.

DO use strings in part of directives.

许多Dart开发人员避免完全使用part . 当每个库是单个文件时,他们发现更容易推理其代码. 如果您确实选择使用part将库的part拆分为另一个文件,则Dart要求另一个文件依次指示它属于哪个库. 出于遗留原因,Dart允许指令的这一part of使用其所属库的名称 . 这使工具很难物理上找到主库文件,并使零件实际上属于哪个库变得模棱两可.

首选的现代语法是使用直接指向库文件的URI字符串,就像在其他指令中使用的一样. 如果您有一些库my_library.dart ,其中包含:

library my_library;

part "some/other/file.dart";

然后零件文件应如下所示:

part of "../../my_library.dart";

并不是:

part of my_library;

DON’T import libraries that are inside the src directory of another package.

Linter规则: implementation_imports

lib下的src目录被指定为包含该包自己的实现专用的库. 软件包维护者对其软件包进行版本管理的方式考虑了此约定. 他们可以自由地在src下对代码进行大范围的更改,而无需对软件包进行重大更改.

这意味着,如果导入其他软件包的私有库,则该软件包的较小的,理论上不中断的发行版可能会破坏您的代码.

PREFER relative paths when importing libraries within your own package’s lib directory.

短绒规则: void_relative_lib_imports, preferred_relative_imports

当从同一程序包中的另一个库引用程序包的lib目录中的库时,相对URI或显式package:都将起作用.

例如,假设您的目录结构如下所示:

my_package
└─ lib
   ├─ src
   │  └─ utils.dart
   └─ api.dart

如果api.dart要导入utils.dart ,则应使用以下api.dart进行导入:

import 'src/utils.dart';

并不是:

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

并没有更深的理由偏爱前者-后者更短,我们希望保持一致.

"在您自己包的lib目录中"部分很重要. lib内的lib可以导入lib (或其子目录中)的其他库. lib之外的lib可以使用相对导入来访问lib之外的其他库. 例如,你可能有下一个测试程序库test ,在其他库test进口.

但是您不能"越过溪流". lib之外的lib永远不要使用相对导入来访问lib下的lib ,反之亦然. 这样做将破坏Dart正确判断两个库URI是否引用同一库的能力. 请遵循以下两个规则:

  • 导入路径不应包含/lib/ .
  • lib下的lib永远不要使用../来转义lib目录.

Booleans

DO use ?? to convert null to a boolean value.

此规则适用于表达式可以评估为truefalsenull ,并且您需要将结果传递给不接受null . 一个常见的情况是将空值感知方法调用用作条件的结果:

if (optionalThing?.isEnabled) {
  print("Have enabled thing.");
}

如果optionalThingnull则此代码将引发异常. 要解决此问题,您需要将null值"转换"为truefalse . 尽管您可以使用==进行此操作,但我们建议使用??

// If you want null to be false:
optionalThing?.isEnabled ?? false;

// If you want null to be true:
optionalThing?.isEnabled ?? true;
// If you want null to be false:
optionalThing?.isEnabled == true;

// If you want null to be true:
optionalThing?.isEnabled != false;

两种操作都产生相同的结果,并且做正确的事,但是?? 之所以被选中是因为三个主要原因:

  • ?? 运算符清楚地表明代码与null值有关.

  • == true看起来像是一个常见的新程序员错误,其中相等运算符是多余的并且可以删除. 当左侧的布尔表达式不会产生null ,但会产生null时,这是正确的.

  • ?? false ?? false?? true ?? true清楚地表明表达式为null时将使用什么值. 使用== true ,您必须仔细考虑布尔逻辑以意识到这意味着将null转换为false .

Strings

在Dart中编写字符串时,请记住以下一些最佳做法.

DO use adjacent strings to concatenate string literals.

短绒规则: preferred_adjacent_string_concatenation

如果您有两个字符串文字(不是值,而是实际的带引号的文字形式),则无需使用+来串联它们. 就像在C和C ++中一样,只需将它们彼此相邻放置即可. 这是制作单个一行不适合的长字符串的好方法.

raiseAlarm(
    'ERROR: Parts of the spaceship are on fire. Other '
    'parts are overrun by martians. Unclear which are which.');
raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ' +
    'parts are overrun by martians. Unclear which are which.');

PREFER using interpolation to compose strings and values.

Linter规则: preferred_interpolation_to_compose_strings

如果您来自其他语言,则习惯于使用+长链从文字和其他值中构建字符串. 这在Dart中确实有效,但是使用插值几乎总是更简洁,更短:

'Hello, $name! You are ${year - birth} years old.';
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';

AVOID using curly braces in interpolation when not needed.

Linter规则: 不必要的 _brace_in_string_interps

如果您要插入一个简单标识符,而不是紧随其后的是更多字母数字文本,则应省略{} .

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

Collections

Dart开箱即用,支持四种集合类型:列表,地图,队列和集合. 以下最佳做法适用于集合.

DO use collection literals when possible.

短绒规则: prefer_collection_literals

有两种创建空的可增长列表的方法: []List() . 同样,有三种方法可以制作空的链接哈希图: {}Map()LinkedHashMap() .

如果要创建不可增长的列表或其他自定义集合类型,则一定要使用构造函数. 否则,请使用漂亮的文字语法. 核心库公开了这些构造函数以简化采用,但是惯用的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()以创建不可增长的大小,则可以使用该大小.

DON’T use .length to see if a collection is empty.

迭代合约不要求集合知道其长度或能够在恒定时间内提供它. 仅查看集合是否包含任何内容而调用.length可能会非常缓慢.

而是有更快,更易读的getter: .isEmpty.isNotEmpty . 使用不需要您否定结果的方法.

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(' ');

CONSIDER using higher-order methods to transform a sequence.

If you have a collection and want to produce a new modified collection from it, it’s often shorter and more declarative to use .map(), .where(), and the other handy methods on Iterable.

使用这些命令而不是命令式的for循环可以使您清楚地知道,您的意图是产生新的序列而不产生副作用.

var aquaticNames = animals
    .where((animal) => animal.isAquatic)
    .map((animal) => animal.name);

同时,这可能太过分了. 如果要链接或嵌套许多高阶方法,则编写命令式代码块可能会更清楚.

AVOID using Iterable.forEach() with a function literal.

Linter规则: void_function_literals_in_foreach_calls

因为内置的for-in循环无法实现您通常想要的功能,所以forEach()函数在JavaScript中得到了广泛使用. 在Dart中,如果要遍历序列,惯用的方法是使用循环.

for (var person in people) {
  ...
}
people.forEach((person) {
  ...
});

请注意,该指南专门说"函数文字 ". 如果要在每个元素上调用一些已经存在的函数,则forEach()很好.

people.forEach(print);

还要注意,使用Map.forEach()总是可以的. 地图不是可迭代的,因此该指南不适用.

DON’T use List.from() unless you intend to change the type of the result.

给定一个Iterable,有两种显而易见的方法来生成一个包含相同元素的新List:

var copy1 = iterable.toList();
var copy2 = List.from(iterable);

明显的区别是第一个较短. 重要的区别在于,第一个保留原始对象的type参数:

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

// Prints "List<int>":
print(iterable.toList().runtimeType);
// Creates a List<int>:
var iterable = [1, 2, 3];

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

如果更改类型,则调用List.from()很有用:

var numbers = [1, 2.3, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);

但是,如果您的目标只是复制Iterable并保留其原始类型,或者您不关心该类型,请使用toList() .

DO use whereType() to filter a collection by type.

Linter规则: preferred_iterable_whereType

假设您有一个包含对象混合的列表,而您只想从中获取整数. 您可以这样使用where()

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

这很冗长,但更糟糕的是,它返回一个可能不是您想要的类型的Iterable. 在这里的示例中,即使您可能需要Iterable<int> ,它也会返回Iterable<Object> Iterable<int>因为这是您将其过滤到的类型.

有时您会看到通过添加cast()来"纠正"上述错误的代码:

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

这很冗长,并导致创建两个包装器,并具有两层间接和冗余运行时检查. 幸运的是,核心库针对此确切用例具有whereType()方法:

var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();

使用whereType()简洁明了,可生成所需类型的Iterable ,并且没有不必要的包装级别.

DON’T use cast() when a nearby operation will do.

通常,当您处理可迭代或流时,会对它进行多次转换. 最后,您想产生一个带有特定类型参数的对象. 无需考虑对cast()的调用,而是查看现有转换之一是否可以更改类型.

如果您已经在调用toList()toList()替换为对List<T>.from() toList()的调用,其中T是您想要的结果列表的类型.

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

如果要调用map() ,请为其提供一个显式类型参数,以便它生成所需类型的可迭代对象. 类型推断通常根据传递给map()的函数为您选择正确的类型,但有时您需要明确.

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

AVOID using cast().

这是先前规则的较简单概括. 有时没有附近的操作可用于固定某些对象的类型. 即使这样,在可能的情况下,也请避免使用cast()来"更改"集合的类型.

最好选择以下任一选项:

  • 用正确的类型创建它. 更改首次创建集合的代码,以使其具有正确的类型.

  • 在访问时投射元素. 如果您立即遍历集合,请在迭代中强制转换每个元素.

  • 使用List.from()进行List.from() . 如果最终将访问集合中的大多数元素,并且不需要原始活动对象支持该对象,请使用List.from()对其进行转换.

    cast()方法返回一个惰性集合,该集合检查每个操作上的元素类型. 如果只对几个元素执行几个操作,那么懒惰就可以了. 但是在许多情况下,延迟验证和包装的开销超过了好处.

这是使用正确类型创建它的示例

List<int> singletonList(int value) {
  var list = <int>[];
  list.add(value);
  return list;
}
List<int> singletonList(int value) {
  var list = []; // List<dynamic>.
  list.add(value);
  return list.cast<int>();
}

这是在访问时强制转换每个元素:

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);
  }
}
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()铸造:

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];
}
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()是正确的答案. 但是请考虑该方法有点风险和不受欢迎的情况-如果您不小心,它可能会很慢并且在运行时可能会失败.

Functions

在Dart中,甚至函数都是对象. 这里是一些涉及功能的最佳实践.

DO use a function declaration to bind a function to a name.

Linter规则: preferred_function_declarations_over_variables

现代语言已经意识到本地嵌套函数和闭包的实用性. 通常在另一个函数中定义一个函数. 在许多情况下,此函数立即用作回调,并且不需要名称. 函数表达式对此非常有用.

但是,如果确实需要命名,请使用函数声明语句,而不是将lambda绑定到变量.

void main() {
  localFunction() {
    ...
  }
}
void main() {
  var localFunction = () {
    ...
  };
}

DON’T create a lambda when a tear-off will do.

短绒规则: 不必要的 lambdas

如果您引用对象上的方法但省略括号,则Dart会给您"撕除"(tear-off),该闭包采用与方法相同的参数,并在调用时调用该方法.

如果您有一个函数使用与传递给它的参数相同的参数来调用方法,则无需手动将调用包装在lambda中.

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

Parameters

DO use = to separate a named parameter from its default value.

Linter规则: pretty_equal_for_default_values

出于传统原因,Dart允许将:=用作命名参数的默认值分隔符. 为了与可选的位置参数保持一致,请使用= .

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

DON’T use an explicit default value of null.

Linter规则: void_init_to_null

如果您将参数设为可选参数,但未给其提供默认值,则该语言会隐式使用null作为默认值,因此无需编写该参数.

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

Variables

以下最佳做法描述了如何在Dart中最佳使用变量.

DON’T explicitly initialize variables to null.

Linter规则: void_init_to_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;
  }
}

AVOID storing what you can calculate.

设计类时,您通常希望将多个视图公开到相同的基础状态. 通常,您会看到在构造函数中计算所有这些视图然后存储它们的代码:

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

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

这段代码有两点错误. 首先,它很可能浪费内存. 严格地说,面积和周长是缓存 . 它们是存储的计算,我们可以从已经拥有的其他数据中重新计算. 他们正在交换增加的内存以减少CPU使用率. 我们是否知道我们有一个值得权衡的性能问题?

更糟糕的是,代码是错误的 . 缓存的问题是无效 -您如何知道缓存何时过期并且需要重新计算? 在这里,即使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;
}

This code is shorter, uses less memory, and is less error-prone. It stores the minimal amount of data needed to represent the circle. There are no fields to get out of sync because there is only a single source of truth.

在某些情况下,您可能需要缓存慢速计算的结果,但是只有在知道性能问题后才这样做,请仔细进行处理,并在注释中说明优化.

Members

在Dart中,对象的成员可以是函数(方法)或数据(实例变量). 以下最佳做法适用于对象的成员.

DON’T wrap a field in a getter and setter unnecessarily.

短绒规则: 不必要的 _getters_setters

在Java和C#中,通常将所有字段隐藏在getter和setter(或C#中的属性)之后,即使实现只是转发到该字段也是如此. 这样,如果您需要在这些成员中进行更多工作,则无需触摸呼叫站点. 这是因为调用getter方法不同于访问Java中的字段,并且访问属性与访问C#中的原始字段不兼容.

Dart没有此限制. 领域和获取者/设置者是完全无法区分的. 您可以在一个类中公开一个字段,然后将其包装在一个getter和setter中,而无需触摸使用该字段的任何代码.

class Box {
  var contents;
}
class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}

PREFER using a final field to make a read-only property.

Linter rule: unnecessary_getters

如果您有一个外部代码应该可以看到但不能分配给它的字段,那么在许多情况下有效的简单解决方案就是将其标记为final .

class Box {
  final contents = [];
}
class Box {
  var _contents;
  get contents => _contents;
}

当然,如果需要在构造函数外部内部分配字段,则可能需要执行"私有字段,公共获取器"模式,但是除非需要,否则不要这么做.

CONSIDER using => for simple members.

Linter规则: preferred_expression_function_bodies

除了在函数表达式中使用=>之外,Dart还允许您使用它定义成员. 该样式非常适合只计算并返回值的简单成员.

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)}';

编写代码的人似乎很喜欢=> ,但是很容易滥用它并最终得到难以阅读的代码. 如果您的声明多于两行或者包含深层嵌套的表达式(级联和条件运算符是常见的违规者),请您自己和每个需要阅读代码的人都帮忙,并使用一个块体和一些语句.

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;
}
Treasure openChest(Chest chest, Point where) =>
    _opened.containsKey(chest) ? null : _opened[chest] = Treasure(where)
      ..addAll(chest.contents);

您也可以在不返回值的成员上使用=> . 当setter很小并且具有使用=>的相应getter时,这是惯用的.

num get x => center.x;
set x(num value) => center = Point(value, center.y);

DON’T use this. except to redirect to a named constructor or to avoid shadowing.

短绒规则: 不必要的 this

JavaScript需要显式的this. 引用对象上当前正在执行其方法的成员,但是Dart(例如C ++,Java和C#)没有此限制.

您只需要使用两次this. . 一种是当具有相同名称的局部变量遮盖了您要访问的成员时:

class Box {
  var value;

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

  void update(value) {
    this.value = value;
  }
}
class Box {
  var value;

  void clear() {
    update(null);
  }

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

其他时间使用this. 是在重定向到命名构造函数时:

class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;

  ShadeOfGray.black() : this(0);

  // This won't parse or compile!
  // ShadeOfGray.alsoBlack() : black();
}
class ShadeOfGray {
  final int brightness;

  ShadeOfGray(int val) : brightness = val;

  ShadeOfGray.black() : this(0);

  // But now it will!
  ShadeOfGray.alsoBlack() : this.black();
}

请注意,构造函数参数永远不会在构造函数初始化列表中隐藏阴影字段:

class Box extends BaseBox {
  var value;

  Box(value)
      : value = value,
        super(value);
}

这看起来很令人惊讶,但可以按您的意愿工作. 幸运的是,由于初始化了形式,这样的代码很少见.

DO initialize fields at their declaration when possible.

如果字段不依赖于任何构造函数参数,则可以并且应该在其声明中对其进行初始化. 它花费更少的代码,并且确保如果类具有多个构造函数,您也不会忘记对其进行初始化.

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';
}

当然,如果字段取决于构造函数参数,或者由不同的构造函数以不同的方式初始化,则此准则不适用.

Constructors

以下最佳做法适用于声明类的构造函数.

DO use initializing formals when possible.

Linter规则: preferred_initializing_formals

Many fields are initialized directly from a constructor parameter, like:

class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}

我们必须键入x在这里四次定义一个字段. 我们可以做得更好:

class Point {
  num x, y;
  Point(this.x, this.y);
}

这个this. 构造函数参数之前的语法称为"初始化形式". 您不能总是利用它. 有时您想要一个命名参数,其名称与您要初始化的字段的名称不匹配. 但是,当您可以使用初始化形式时, 应该使用 .

DON’T type annotate initializing formals.

Linter规则: type_init_formals

如果构造函数参数正在使用this.参数this. 要初始化字段,则参数的类型应理解为与字段相同的类型.

class Point {
  int x, y;
  Point(this.x, this.y);
}
class Point {
  int x, y;
  Point(int this.x, int this.y);
}

DO use ; instead of {} for empty constructor bodies.

Linter规则: empty_constructor_bodies

在Dart中,带有空主体的构造函数可以仅用分号终止. (实际上,它是const构造函数所必需的.)

class Point {
  int x, y;
  Point(this.x, this.y);
}
class Point {
  int x, y;
  Point(this.x, this.y) {}
}

DON’T use new.

短绒规则: 不必要的新

Dart 2将new关键字设为可选. 即使在Dart 1中,其含义也从未明确,因为工厂构造函数意味着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!'),
    ],
  );
}

DON’T use const redundantly.

短绒规则: 不必要的常量

在表达式必须为常量的情况下, const关键字是隐式的,不需要编写,也不需要编写. 这些上下文是其中的任何表达式:

  • const集合文字.
  • const构造函数调用
  • 元数据注释.
  • const变量声明的初始化程序.
  • 转换大小写表达式-在:之后紧接在case之后的部分,而不是case的正文.

(默认值未包含在此列表中,因为Dart的未来版本可能会支持非const默认值.)

基本上,在任何地方写new而不是const都会出错的地方,Dart 2允许您省略const .

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

Error handling

当程序中发生错误时,Dart将使用异常. 以下最佳做法适用于捕获和引发异常.

AVOID catches without on clauses.

短绒规则: void_catches_without_on_clauses

一个没有on限定词的catch子句捕获try块中的代码抛出的任何内容 . 神奇宝贝异常处理很可能不是您想要的. 您的代码是否正确处理了StackOverflowErrorOutOfMemoryError ? 如果您在该try块中错误地将错误的参数传递给方法,您是想让调试器将您指向该错误,还是希望吞噬有用的ArgumentError ? 您是否想要在该代码内的所有assert()语句都消失,因为您正在捕获抛出的AssertionError

答案可能是"否",在这种情况下,您应该过滤捕获的类型. 在大多数情况下,应该有一个on子句,将您限制为您知道并正确处理的各种运行时故障.

在极少数情况下,您可能希望捕获任何运行时错误. 这通常是在框架或低级代码中,它们试图隔离任意应用程序代码以免引起问题. 即使在这里,捕获Exception通常也比捕获所有类型更好. 异常是所有运行时错误的基类,并且排除指示代码中编程错误的错误.

DON’T discard errors from catches without on clauses.

如果您确实确实需要捕获可能从代码区域抛出的所有内容对所捕获的内容进行处理. 记录它,将其显示给用户或重新扔掉,但不要静默丢弃.

DO throw objects that implement Error only for programmatic errors.

Error类是程序错误的基类. 抛出该类型的对象或其子接口之一(例如ArgumentError )时,这意味着代码中存在错误 . 当您的API想要向调用者报告错误地使用了该API时,抛出错误将清楚地发送该信号.

Conversely, if the exception is some kind of runtime failure that doesn’t indicate a bug in the code, then throwing an Error is misleading. Instead, throw one of the core Exception classes or some other type.

DON’T explicitly catch Error or types that implement it.

短绒规则: void_catching_errors

这是从上面得出的. 由于"错误"指示代码中存在错误,因此它应展开整个调用堆栈,暂停程序并打印堆栈跟踪,以便您可以找到并修复该错误.

捕获这些类型的错误会中断该过程并掩盖该错误. 事实发生后,与其添加错误处理代码来处理此异常,不如回过头来修复导致该异常被抛出的代码.

DO use rethrow to rethrow a caught exception.

短绒规则: use_rethrow_when_possible

如果决定重新抛出异常,则最好使用rethrow语句,而不要使用throw相同的异常对象. rethrow保留了异常的原始堆栈跟踪. 另一方面, throw将堆栈跟踪重置为最后抛出的位置.

try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) throw e;
  handle(e);
}
try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) rethrow;
  handle(e);
}

Asynchrony

Dart具有多种语言功能来支持异步编程. 以下最佳做法适用于异步编码.

PREFER async/await over using raw futures.

众所周知,即使使用诸如Future之类的漂亮抽象,异步代码也很难读取和调试. async / await语法提高了可读性,并允许您在异步代码中使用所有Dart控件流结构.

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;
  });
}

DON’T use async when it has no useful effect.

很容易养成在任何与异步有关的函数上使用async的习惯. 但是在某些情况下,这是多余的. 如果您可以在不更改功能行为的情况下忽略async ,请这样做.

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

情况下, async 有用的包括:

  • 您正在使用await . (这是显而易见的.)

  • 您正在异步返回错误. async然后throwreturn Future.error(...)短.

  • 您正在返回一个值,并且希望将来将其隐式包装. asyncFuture.value(...)短.

Future usesAwait(Future later) async {
  print(await later);
}

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

Future asyncValue() async => 'value';

CONSIDER using higher-order methods to transform a stream.

这与以上关于可迭代项的建议相类似. 流支持许多相同的方法,并且还正确处理传输错误,关闭等问题.

AVOID using Completer directly.

许多不熟悉异步编程的人都想编写能创造未来的代码. Future中的构造函数似乎无法满足他们的需求,因此他们最终找到了Completer类并使用了它.

Future<bool> fileContainsBear(String path) {
  var completer = Completer<bool>();

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

  return completer.future;
}

两种低级代码需要使用Completer:新的异步原语,以及与不使用期货的异步代码进行接口. 大多数其他代码应使用async / await或Future.then() ,因为它们更清晰,并且使错误处理更加容易.

Future<bool> fileContainsBear(String path) {
  return File(path).readAsString().then((contents) {
    return contents.contains('bear');
  });
}
Future<bool> fileContainsBear(String path) async {
  var contents = await File(path).readAsString();
  return contents.contains('bear');
}

DO test for Future<T> when disambiguating a FutureOr<T> whose type argument could be Object.

在对FutureOr<T>做任何有用的事情之前,通常需要先进行一次is检查,看看是否有Future<T>或裸T 如果类型参数是FutureOr<int>某个特定类型,则使用哪个测试, is int还是is Future<int>都无关紧要. 之所以起作用,是因为这两种类型是不相交的.

但是,如果值类型是Object或可能用Object实例化的类型参数,则两个分支重叠. Future<Object>本身实现Object ,因此is Objectis T ,其中T是可以用Object实例化的某些类型参数,即使对象是Future<Object> ,它也返回true. 相反,显式测试Future案例:

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;
  }
}
Future<T> logValue<T>(FutureOr<T> value) async {
  if (value is T) {
    print(value);
    return value;
  } else {
    var result = await value;
    print(result);
    return result;
  }
}

In the bad example, if you pass it a Future<Object>, it incorrectly treats it like a bare, synchronous value.

by  ICOPY.SITE