高效編寫Dart--使用指南

如何閱讀指南

  • DO 應(yīng)始終遵循的準則
  • DON'T 不應(yīng)該這么使用的準則
  • PREFER 應(yīng)該遵循的準則辅鲸,但是在某些情況下,可以根據(jù)個人理解忽略确封,不遵循
  • AVOID 不應(yīng)該遵循的準則暇韧,但是在某些情況下巧号,可以根據(jù)個人理解忽略,不遵循
  • CONSIDER 可遵循或者不遵循的規(guī)則煎娇,取決于個人偏好

這些指南可幫助您以一致二庵、可維護的方式從多個文件中編寫程序贪染。為了簡化這些指導(dǎo)原則,他們使用“import”來涵蓋importexport指示.該指南同樣適用于兩者催享。

DO part of指令中使用字符串

如果您確實選擇使用 part將部分庫拆分為另一個文件杭隙,則Dart要求另一個文件依次指示它所屬的庫。

library my_library;

part "some/other/file.dart";

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

// Bad
part of my_library;

DON’T 導(dǎo)入另一個包的src目錄中的庫

lib下的src目錄被指定為由自己實現(xiàn)的私有庫因妙。包維護人員可以自由地對src下的代碼進行徹底的更改痰憎,而不會對包造成破壞。

這意味著攀涵,如果您導(dǎo)入其他包的私有庫铣耘,那么該軟件包的一個次要的以故、理論上非破壞性的版本可能都會破壞你的代碼蜗细。

PREFER 導(dǎo)入包的lib目錄中的庫時的使用相對路徑

Linter規(guī)則:avoid_relative_lib_imports

當lib從同一個包中的另一個庫引用包的目錄中的庫時相對URI顯式package:都能實現(xiàn)。
例如怒详,假設(shè)你的目錄結(jié)構(gòu)如下:

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

如果api.dart想要導(dǎo)入utils.dart炉媒,它應(yīng)該使用:

// Good
import 'src/utils.dart';

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

遵循以下兩條規(guī)則:

  • 導(dǎo)入路徑永遠不應(yīng)包含/lib/
  • lib下的庫不應(yīng)該使用../要轉(zhuǎn)義lib目錄。

字符串

DO 使用相鄰的字符串來連接字符串文字

Linter規(guī)則:prefer_adjacent_string_concatenation
如果你有兩個字符串文字——不是值昆烁,而是實際引用的文字形式——你不需要+用來連接它們吊骤。就像在C和C ++中一樣,只需將它們放在一起就可以了善玫。這是制作不適合一行的單個長字符串的好方法水援。

// 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.');

PREFER 使用插值來組合字符串和值

Linter規(guī)則:prefer_interpolation_to_compose_strings

// Good
'Hello, $name! You are ${year - birth} years old.';

// Bad
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' y...';

AVOID 在不需要時使用花括號進行插值

Linter規(guī)則:unnecessary_brace_in_string_interps

// 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支持四種集合類型:lists(列表), maps(映射), queues(隊列),sets(集合)。

DO 盡可能使用集合字面意義的方式

有兩種方法可以制作一個空的可增長列表:[]List()茅郎。同樣蜗元,有三種方法可以使一個空的哈希映射:{}, Map()系冗,和LinkedHashMap()奕扣。
如果要創(chuàng)建不可擴展的列表或其他一些自定義集合類型,那么請務(wù)必使用構(gòu)造函數(shù)掌敬。否則惯豆,請使用字面意義的語法。核心庫公開了那些構(gòu)造函數(shù)以便于采用奔害,但慣用的Dart代碼不使用它們楷兽。

// Good
var points = [];
var addresses = {};


// Bad
var points = List();
var addresses = Map();

如果重要的話,你甚至可以為它們提供一個類型參數(shù)华临。

// Good
var points = <Point>[];
var addresses = <String, Address>{};


// Bad
var points = List<Point>();
var addresses = Map<String, Address>();

注意芯杀,這并不適用于這些類的命名構(gòu)造函數(shù)。List.from()、Map.fromIterable()都有它們的用途揭厚。如果你傳遞一個大小來通過List()創(chuàng)建一個不可增長的大小却特,那么使用構(gòu)造函數(shù)的方式創(chuàng)建是有意義的。

DON’T 用.length來查看集合是否為空

調(diào)用.length只是為了查看集合是否包含任何內(nèi)容可能會非常緩慢筛圆。相反裂明,有更快,更可讀的getter:.isEmpty.isNotEmpty

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

CONSIDER 使用高階方法轉(zhuǎn)換序列

如果你有一個集合并希望從它產(chǎn)生一個新修改的集合太援,使用.map()闽晦,.where()以及其他方便的方法,往往更短粉寞,更簡明尼荆。使用這些而不是命令性for循環(huán)可以清楚地表明您的意圖是生成新序列而不產(chǎn)生副作用。

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

AVOID 使用Iterable.forEach()函數(shù)

Linter規(guī)則:avoid_function_literals_in_foreach_calls
forEach()函數(shù)在JavaScript中被廣泛使用唧垦,因為內(nèi)置 for-in循環(huán)不能達到您通常想要的效果捅儒。在Dart中,如果要迭代序列振亮,那么慣用的方法就是使用循環(huán)巧还。

// Good
for (var person in people) {
  ...
}

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

例外情況是,如果要執(zhí)行的操作是調(diào)用一些已存在的函數(shù)坊秸,并將每個元素作為參數(shù)麸祷。在那種情況下,forEach()很方便褒搔。

people.forEach(print);

DON’T 使用List.from()阶牍,除非您打算更改結(jié)果的類型

給定Iterable,有兩種顯而易見的方法可以生成包含相同元素的新List:

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

最明顯的區(qū)別是第一個更短星瘾。重要的區(qū)別是走孽,第一個保留了原始對象的類型參數(shù):

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

如果要更改類型,則調(diào)用List.from()很有用:

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

但是琳状,如果您的目標只是復(fù)制iterable并保留其原始類型磕瓷,或者您不關(guān)心類型,那么請使用toList()念逞。

DO whereType()按類型過濾集合

Linter規(guī)則:prefer_iterable_whereType
假設(shè)您有一個包含對象混合的數(shù)組困食,并且您希望只獲取整數(shù)。你可以where()像這樣使用:

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

// Bad
// 它返回一個可能不是你想要的類型的iterable翎承。在這里的示例中硕盹,它返回一個Iterable<Object>即使你可能想要一個,Iterable<int>因為那是你要過濾它的類型叨咖。
var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int);

// 這是冗長的莱睁,并導(dǎo)致創(chuàng)建兩個包裝器待讳,具有兩層間接和冗余運行時檢查芒澜。
var objects = [1, "a", 2, "b", 3];
var ints = objects.where((e) => e is int).cast<int>();

DON’T 當附近的操作可以執(zhí)行時仰剿,使用cast()

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

AVOID 使用cast()

這是對上一個規(guī)則的更為柔和的概括。有時候沒有附近的操作可以用來修復(fù)某些對象的類型痴晦。即便如此南吮,盡可能避免使用cast()更改集合的類型。

請改為選擇以下任何選項:

  • 使用正確的類型創(chuàng)建它誊酌。更改首次創(chuàng)建集合的代碼時部凑,使其具有正確的類型。

  • 在訪問時強制轉(zhuǎn)換元素碧浊。如果您立即迭代集合涂邀,則在迭代內(nèi)部轉(zhuǎn)換每個元素。

  • 使用List.from()箱锐。如果您最終將訪問集合中的大多數(shù)元素比勉,并且您不需要該對象由原始活動對象支持,請使用它進行轉(zhuǎn)換List.from()驹止。

cast()方法返回一個惰性集合浩聋,該集合檢查每個操作的元素類型。如果只對少數(shù)元素執(zhí)行少量操作臊恋,那么懶惰就會很好衣洁。但在許多情況下,延遲驗證和包裝的開銷超過了好處抖仅。

以下是使用正確類型創(chuàng)建它的示例:

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

這是在訪問時轉(zhuǎn)換每個元素:

// 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()

// 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()是正確的答案撤卢。但是考慮到這種方法有點危險和不可取——它可能很慢环凿,如果你不小心,可能會在運行時失敗凸丸。

功能

DO 使用函數(shù)聲明將函數(shù)綁定到名稱

Linter規(guī)則:prefer_function_declarations_over_variables

// Good
void main() {
  localFunction() {
    ...
  }
}

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

DON’T 當省略時創(chuàng)建lambda

Linter規(guī)則:unnecessary_lambdas
如果您引用了對象上的一個方法拷邢,但是省略了圓括號,那么Dart會給您一個“tearoff”——一個閉包屎慢,它接受與該方法相同的參數(shù)瞭稼,并在您調(diào)用它時調(diào)用它。

如果有一個函數(shù)調(diào)用的方法具有與傳遞給它的參數(shù)相同的參數(shù)腻惠,則不需要手動將調(diào)用包裝在lambda中环肘。

// Good
names.forEach(print);

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

參數(shù)

DO 使用 = 將命名參數(shù)與其默認值分隔開

Linter規(guī)則:prefer_equal_for_default_values

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

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

DON’T 顯式的設(shè)置默認值為null

Linter規(guī)則:avoid_init_to_null

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

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

變量

DON’T 將變量顯式初始化為null

在Dart中,未自動顯式初始化的變量或字段將初始化為null集灌。這是由語言可靠地指定的悔雹。Dart中沒有“未初始化記憶”的概念复哆。添加 = null 是多余的,不需要腌零。

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

AVOID 存儲可以計算的內(nèi)容

在設(shè)計類時梯找,經(jīng)常希望將多個視圖暴露給相同的基礎(chǔ)狀態(tài)。通常益涧,您會看到在構(gòu)造函數(shù)中計算所有這些視圖的代碼锈锤,然后存儲它們:

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

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

這段代碼有兩個問題。首先闲询,它可能會浪費內(nèi)存久免。嚴格地說,面積和周長是緩存扭弧。它們是存儲的計算阎姥,我們可以從已有的其他數(shù)據(jù)中重新計算。他們正在用增加的內(nèi)存換取減少的CPU使用鸽捻。

更糟糕的是呼巴,代碼是錯誤的。緩存的問題是無效——您如何知道緩存何時過期并需要重新計算?在這里泊愧,我們從不這樣做伊磺,即使半徑是可變的。您可以指定一個不同的值删咱,那么面積和周長將保留它們以前的屑埋、現(xiàn)在不正確的值。

為了正確處理緩存失效痰滋,我們需要這樣做:

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

要編寫摘能、維護、調(diào)試和讀取的代碼實在太多了敲街。相反团搞,您的第一個實現(xiàn)應(yīng)該是:

class Circle {
  num radius;

  Circle(this.radius);

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

成員

在Dart中,對象具有可以是函數(shù)(方法)或數(shù)據(jù)(實例變量)的成員

DON’T 在getter和setter中不必要地包裝字段

Linter規(guī)則:unnecessary_getters_setters

在Java和C#中多艇,通常隱藏getter和setter(或C#中的屬性)后面的所有字段逻恐,即使實現(xiàn)只是轉(zhuǎn)發(fā)到字段。這樣峻黍,如果你需要在這些成員中做更多的工作复隆,你可以不需要觸摸呼叫。這是因為調(diào)用getter方法與訪問Java中的字段不同姆涩,訪問屬性與訪問C#中的原始字段不是二進制兼容的挽拂。

Dart沒有這個限制。字段和getter / setter是完全無法區(qū)分的骨饿。您可以在類中公開一個字段亏栈,然后將其包裝在getter和setter中台腥,而不必觸及任何使用該字段的代碼。

// Good
class Box {
  var contents;
}

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

PREFER 使用final字段來創(chuàng)建只讀屬性

如果您有一個外部代碼應(yīng)該能夠看到但不能分配給它的字段绒北,那么在許多情況下黎侈,一個簡單的解決方案就是將其標記為final。

// Good
class Box {
  final contents = [];
}

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

當然镇饮,如果您需要在構(gòu)造函數(shù)外部對字段進行內(nèi)部分配蜓竹,您可能需要執(zhí)行“private field, public getter”模式,但在需要之前不要進行此操作储藐。

CONSIDER 使用=>簡單的成員

Linter規(guī)則:prefer_expression_function_bodies

除了=>用于函數(shù)表達式之外,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)}';

人們編寫的代碼似乎更喜歡=>,但它很容易濫用它聂喇,并用代碼很難結(jié)束閱讀辖源。如果您的聲明超過幾行或包含深層嵌套的表達式——級聯(lián)和條件運算符是常見的違規(guī)者, 請使用塊體和一些語句希太。

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

您還可以對不返回值的成員使用=>克饶。當setter很小并且有一個使用=>的相應(yīng)getter時,這是一種習(xí)慣用法誊辉。

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

DON’T 用 this. 當不需要避免shadowing時

Linter規(guī)則:unnecessary_this
JavaScript要求顯式this.引用當前正在執(zhí)行其方法的對象上的成員矾湃,但類似Dart的C ++,Java和C#不具有該限制堕澄。

您唯一需要使用的this.是當具有相同名稱的局部變量隱藏要訪問的成員時邀跃。

// Bad
class Box {
  var value;

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

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

// Good
class Box {
  var value;

  void clear() {
    update(null);
  }

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

請注意,構(gòu)造函數(shù)參數(shù)從不影響構(gòu)造函數(shù)初始化列表中的字段:

class Box extends BaseBox {
  var value;

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

DO 盡可能在聲明時初始化字段

如果字段不依賴于任何構(gòu)造函數(shù)參數(shù)蛙紫,那么應(yīng)該在聲明時初始化它拍屑。它使用更少的代碼,并且確保如果類有多個構(gòu)造函數(shù)坑傅,您不會忘記初始化它僵驰。

// 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.
}

當然吆录,如果字段依賴于構(gòu)造函數(shù)參數(shù)旋圆,或者由不同的構(gòu)造函數(shù)進行不同的初始化痹届,則本指南不適用

構(gòu)造函數(shù)

DO 盡可能使用初始化形式

Linter規(guī)則:prefer_initializing_formals

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

this.構(gòu)造函數(shù)參數(shù)之前的語法稱為“初始化形式”君纫。你不能總是利用它共啃。特別是桩匪,使用它意味著參數(shù)在初始化列表中不可見槐秧。但是承边,當你能做到的時候室谚,你就應(yīng)該去做毡鉴。

DON’T 類型注釋初始化形式

Linter規(guī)則:type_init_formals

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

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

DO 使用;而不是{}空構(gòu)造函數(shù)體

Linter規(guī)則:empty_constructor_bodies
在Dart中崔泵,具有空主體的構(gòu)造函數(shù)可以用分號結(jié)束。(實際上猪瞬,const構(gòu)造函數(shù)需要它)憎瘸。

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

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

DON’T 使用new

Dart 2使new關(guān)鍵字成為可選的。即使在Dart 1中陈瘦,它的含義也從來沒有明確過幌甘,因為工廠構(gòu)造函數(shù)意味著新的調(diào)用可能仍然不會實際返回新的對象。

為了減少遷移的痛苦痊项,這種語言仍然允許使用new锅风,但是考慮到它已被棄用并從代碼中刪除它。

// 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!'),
    ],
  );
}

DON’T 多余地使用const

Linter規(guī)則:unnecessary_const
在表達式必須是常量的上下文中鞍泉,const關(guān)鍵字是隱式的皱埠,不需要寫,也不應(yīng)該寫咖驮。這些上下文是其中的任何表達式:

  • const集合文字边器。
  • const構(gòu)造函數(shù)調(diào)用
  • 元數(shù)據(jù)注釋
  • const變量聲明的初始化
  • 交換用例表達式 - 緊接在case之前的部分:,而不是用例的主體托修。

默認值不包括在這個列表中忘巧,因為Dart的未來版本可能支持非const默認值)
基本上,在寫new而不是const時會出錯的任何地方睦刃,Dart 2都允許您省略const

// 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]),
];

錯誤處理

AVOID 捕捉?jīng)]有 on 的錯誤子句

Linter規(guī)則:avoid_catches_without_on_clauses
一個沒有on限定符的catch子句捕獲try塊中的代碼拋出的任何內(nèi)容砚嘴。Pokemon異常處理很可能不是您想要的。您的代碼是否正確地處理了StackOverflowErrorOutOfMemoryError?如果您錯誤地將錯誤的參數(shù)傳遞給try塊中的方法眯勾,您是希望調(diào)試器將錯誤指向您枣宫,還是希望有用的ArgumentError被吞噬?在捕獲拋出的assertionerror之后,您希望代碼中的任何assert()語句有效地消失嗎?

答案可能是“否”吃环,在這種情況下也颤,您應(yīng)該過濾捕獲的類型。在大多數(shù)情況下郁轻,您應(yīng)該有一個on子句翅娶,它將您限制到您所知道的和正在正確處理的運行時故障的類型。

在極少數(shù)情況下好唯,您可能希望捕獲任何運行時錯誤竭沫。這通常是在框架或底層代碼中,這些代碼試圖將任意應(yīng)用程序代碼與問題隔離開來骑篙。即使在這里蜕提,捕獲異常通常也比捕獲所有類型要好。Exception是所有運行時錯誤的基類靶端,并排除指示代碼中編程錯誤的錯誤谎势。

DON’T 沒有on子句情況下,放棄捕獲的錯誤凛膏,

如果您確實覺得需要捕獲代碼區(qū)域中可以拋出的所有內(nèi)容,那么就用捕獲的內(nèi)容做一些事情脏榆。記錄它猖毫,將它顯示給用戶或重新拋出它,但不要悄悄地丟棄它须喂。

DO 拋出Error僅用于編程錯誤的對象

Error類是程序錯誤的基類吁断。當拋出該類型的對象或它的一個子接口(如ArgumentError)時,意味著代碼中存在錯誤坞生。當您的API想要向調(diào)用者報告它正在被錯誤地使用時仔役,拋出一個錯誤會清楚地發(fā)送該信號。

相反恨胚,如果異常是某種運行時故障骂因,而該故障并不表示代碼中有錯誤,那么拋出錯誤是一種誤導(dǎo)赃泡。相反,拋出一個核心異常類或其他類型乘盼。

DON’T 顯式捕獲錯誤或?qū)崿F(xiàn)錯誤的類型

這是由上面得出的升熊。由于錯誤指示代碼中的錯誤,因此它應(yīng)該展開整個調(diào)用堆棧绸栅,停止程序级野,并打印堆棧跟蹤信息,以便找到并修復(fù)錯誤粹胯。

捕捉這些類型的錯誤會破壞該過程并掩蓋錯誤蓖柔。不要在異常發(fā)生后添加錯誤處理代碼來處理該異常,而是返回并修復(fù)最初導(dǎo)致異常拋出的代碼风纠。

DO 使用rethrow重新拋出捕獲的異常

Linter規(guī)則:use_rethrow_when_possible
如果決定重新拋出異常况鸣,請使用rethrow語句,而不是使用throw拋出相同的異常對象竹观。rethrow保留異常的原始堆棧跟蹤镐捧。另一方面,throw將堆棧跟蹤重置為最后一個拋出的位置臭增。

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

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

異步

PREFER 使用async/await

眾所周知懂酱,異步代碼很難閱讀和調(diào)試,即使使用像futures這樣的抽象也是如此誊抛。async/ wait語法提高了可讀性列牺,允許您在異步代碼中使用所有Dart控制流結(jié)構(gòu)。

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

DON’T 在沒有用時使用async

在任何執(zhí)行與異步相關(guān)的操作的函數(shù)上都很容易養(yǎng)成使用異步的習(xí)慣拗窃。但在某些情況下瞎领,這是無關(guān)緊要的泌辫。如果可以在不更改函數(shù)行為的情況下可以省略異步,那么就省略默刚。

// 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甥郑。(這是顯而易見的。)

  • 正在異步返回一個錯誤荤西。asyncthrow跑出更短的return Future.error(...)澜搅。

  • 正在返回一個值,并且希望在將來隱式地包裝它邪锌。 async比Future.value(...)短

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

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

Future asyncValue() async => 'value';

CONSIDER 使用高階方法轉(zhuǎn)換流

這與上述關(guān)于迭代的建議相似勉躺。Streams支持許多相同的方法,并且還可以正確處理傳輸錯誤觅丰、關(guān)閉等操作饵溅。

AVOID 直接使用Completer

許多剛接觸異步編程的人想要編寫可以產(chǎn)生未來的代碼。Future中的構(gòu)造函數(shù)似乎不符合他們的需要妇萄,因此他們最終找到了Completer類并使用它

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

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

  return completer.future;
}

兩種低級代碼需要Completer: new asynchronous primitives以及與不使用future的異步代碼的接口蜕企。大多數(shù)其他代碼應(yīng)該使用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 在消除類型參數(shù)為Object的FutureOr<T>的歧義時轻掩,測試Future<T>。

可以用做任何有用的FutureOr<T>事情之前懦底,通常需要用is檢查是否有一個Future<T>或一個空的T唇牧。如果類型參數(shù)是某些特定類型的FutureOr<int>,在不重要的測試中可以使用is int或is Future<int>聚唐。兩者都有效丐重,因為這兩種類型是不相交的。

但是杆查,如果值類型是Object或可能用Object實例化的類型參數(shù)扮惦,則這兩個分支重疊。Future<Object>本身實現(xiàn)Object根灯,所以is Object或is T径缅,其中T是可以用Object實例化的類型參數(shù),即使對象是Future烙肺,也返回true纳猪。相反,顯式地測試未來的情況:

// 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) {
    print(value);
    return value;
  } else {
    var result = await value;
    print(result);
    return result;
  }
}

在這個糟糕的示例中桃笙,如果您傳遞給它一個Future<Object>氏堤,它會錯誤地將其視為一個空的同步值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鼠锈,隨后出現(xiàn)的幾起案子闪檬,更是在濱河造成了極大的恐慌,老刑警劉巖购笆,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粗悯,死亡現(xiàn)場離奇詭異,居然都是意外死亡同欠,警方通過查閱死者的電腦和手機样傍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铺遂,“玉大人衫哥,你說我怎么就攤上這事〗笕瘢” “怎么了撤逢?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長粮坞。 經(jīng)常有香客問我蚊荣,道長,這世上最難降的妖魔是什么莫杈? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任妇押,我火速辦了婚禮,結(jié)果婚禮上姓迅,老公的妹妹穿的比我還像新娘。我一直安慰自己俊马,他們只是感情好丁存,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柴我,像睡著了一般解寝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上艘儒,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天聋伦,我揣著相機與錄音,去河邊找鬼界睁。 笑死觉增,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的翻斟。 我是一名探鬼主播逾礁,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼访惜!你這毒婦竟也來了嘹履?” 一聲冷哼從身側(cè)響起腻扇,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砾嫉,沒想到半個月后幼苛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡焕刮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年舶沿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片济锄。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡暑椰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荐绝,到底是詐尸還是另有隱情一汽,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布低滩,位于F島的核電站召夹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恕沫。R本人自食惡果不足惜监憎,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望婶溯。 院中可真熱鬧鲸阔,春花似錦、人聲如沸迄委。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叙身。三九已至渔扎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間信轿,已是汗流浹背晃痴。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留财忽,地道東北人倘核。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像定罢,于是被迫代替她去往敵國和親笤虫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容