代码风格
标识符
类名采用
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(' ');推荐使用集合的高阶函数进行相关处理(如:
where
、map
)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的类成员包含成员属性和方法(静态方法和实例方法)
不要给成员属性设置
setter
和getter
方法,除非必须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]),
];
异步操作
尽可能使用
async
和await
来替换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
+throw
是return 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;
}
}