如何閱讀指南
-
DO
應(yīng)始終遵循的準則 -
DON'T
不應(yīng)該這么使用的準則 -
PREFER
應(yīng)該遵循的準則辅鲸,但是在某些情況下,可以根據(jù)個人理解忽略确封,不遵循 -
AVOID
不應(yīng)該遵循的準則暇韧,但是在某些情況下巧号,可以根據(jù)個人理解忽略,不遵循 -
CONSIDER
可遵循或者不遵循的規(guī)則煎娇,取決于個人偏好
庫
這些指南可幫助您以一致二庵、可維護的方式從多個文件中編寫程序贪染。為了簡化這些指導(dǎo)原則,他們使用“import”來涵蓋import
和export
指示.該指南同樣適用于兩者催享。
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異常處理很可能不是您想要的。您的代碼是否正確地處理了StackOverflowError或OutOfMemoryError?如果您錯誤地將錯誤的參數(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甥郑。(這是顯而易見的。)
正在異步返回一個錯誤荤西。async比throw跑出更短的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>氏堤,它會錯誤地將其視為一個空的同步值。