├── style.md
├── README.md
├── documentation.md
├── design.md
└── usage.md
/style.md:
--------------------------------------------------------------------------------
1 |
2 | ## 🔥 Effective Dart: Style 🔥
3 | 优秀的代码很重要的一部分就是有好的`代码风格`.一致的命名,代码顺序和格式化可以让我们的代码看起来风格统一.如果我们从始至终在Dart生态中使用一致的代码风格,可以让我们很容易的去学习别人的代码和为别人的代码做贡献.
4 |
5 | 1. **标识符命名**
6 | - [DO] [类型命名使用大写驼峰风格](#1-1)
7 | - [DO] [库个源文件命名使用小写加下划线风格](#1-2)
8 | - [DO] [import前缀命名使用小写加下划线风格](#1-3)
9 | - [DO] [其他命名使用小写驼峰风格](#1-4)
10 | - [PREFER] [常量命名使用小写驼峰风格](#1-5)
11 | - [DO] [缩略词大写](#1-6)
12 | - [DON'T] [不要使用前缀字符](#1-7)
13 | 2. **代码顺序**
14 | - [DO] [将`dart:xx`导入代码放在其他导入之前](#2-1)
15 | - [DO] [将`package:xx`导入代码放在相对文件导入之前](#2-2)
16 | - [PREFER] [将自己的包的引入代码放在其他三方包引入之后](#2-3)
17 | - [DO] [导出代码应该放在所有导入代码之后](#2-4)
18 | - [DO] [同一类导入代码顺序根据文件或者包名的首字母排序](#2-5)
19 | 3. **格式化**
20 | - [DO] [使用`dartfmt`工具格式化你的代码](#3-1)
21 | - [CONSIDER] [修改你的代码使之变得对格式化工具友好](#3-2)
22 | - [AVOID] [单行代码不超过80个字符](#3-3)
23 | - [DO] [使用大括号包裹你的控制流结构](#3-4)
24 |
25 | ### 标识符命名 💅
26 |
27 | #### 在dart中有三种命名方式
28 | - 大写驼峰命名 `UpperCamelCase`
29 | - 小写驼峰命名 `lowerCamelCase`
30 | - 小写加下划线命名 `lower_case_width_underscores`
31 |
32 |
33 | #### [DO] 类型命名使用*大写驼峰命名*
34 | > 类,枚举,typedef和类型参数应该将每个单词首字母大写(包括第一个单词)并且不使用任何分隔符
35 | ```dart
36 | class SliderMenu { ... }
37 |
38 | class HttpRequest { ... }
39 |
40 | typedef Predicate = bool Function(T value);
41 | ```
42 | > 当类被用来作为元数据注解时也使用*大写驼峰命名*
43 | ```dart
44 | class Foo {
45 | const Foo([arg]);
46 | }
47 |
48 | @Foo(anArg)
49 | class A { ... }
50 |
51 | @Foo()
52 | class B { ... }
53 | ```
54 | > 当注解类的构造函数不需要参数时,可以使用*小写驼峰命名*的常量代替
55 | ```dart
56 | const foo = Foo();
57 |
58 | @foo
59 | class C { ... }
60 | ```
61 |
62 | #### [DO] 对库和源文件命名时用下划线命名使用*下划线命名*
63 | > 一些文件系统是大小写不敏感的,所以很多项目需要将文件名设置为小写,使用下划线将单词分割可以有效的提高可读性。使用下划线命名时应该确保名字是一个合法的dart命名。
64 |
65 | ```dart
66 | // good
67 | library peg_parser.source_scanner;
68 |
69 | import 'file_system.dart';
70 | import 'slider_menu.dart';
71 |
72 | // bad
73 | library pegparser.SourceScanner;
74 |
75 | import 'file-system.dart';
76 | import 'SliderMenu.dart';
77 | ```
78 |
79 | #### [DO] 当import包并取别名时使用*下划线命名*
80 |
81 | ```dart
82 | // good
83 | import 'dart:math' as math;
84 | import 'package:ng_component' as ng_component;
85 | import 'package:js/js.dart' as js;
86 |
87 | //bad
88 | import 'dart:math' as Math;
89 | import 'package:ng_component' as ngComponent;
90 | import 'package:js/js.dart' as JS;
91 | ```
92 |
93 | #### [DO] 在为下面标识符命名时使用*小写驼峰命名*
94 | > 类成员,顶层(全局)定义,变量,参数,命名参数除了首单词其余单词首字母应该大写,并且不使用分隔符
95 |
96 | ```dart
97 | var item;
98 |
99 | HttpRequest httpRequest;
100 |
101 | void align(bool clearItems) {
102 | //...
103 | }
104 | ```
105 |
106 | #### [PREFER] 对常量命名时使用*小写驼峰命名*
107 | >在dart2.x之前,dart对于常量命名使用的是SCREAMING_CAPS,详见dart sdk的changelog
108 |
109 | ```dart
110 | // good
111 | const pi = 3.14;
112 | const defaultTimeout = 4000;
113 | final urlScheme = RegExp('^([a-z]+):');
114 |
115 | class Dice {
116 | static final numberGenerator = Random();
117 | }
118 |
119 | // bad
120 | const PI = 3.14;
121 | const DefaultTimeout = 1000;
122 | final URL_SCHEME = RegExp('^([a-z]+):');
123 |
124 | class Dice {
125 | static final NUMBER_GENERATOR = Random();
126 | }
127 | ```
128 |
129 | #### [DO] 对于缩略词命名的一些建议
130 | > 大写缩略词难于阅读理解,特别是多个缩略词组合时容易产生歧义,举个例子比如HTTPSFTP,没法说清楚这是指的HTTPS FTP还是HTTP SFTP。为了避免这种问题,缩略词应该和普通单词一样首字符大写,而特别的双单词缩写比如IO,DB不改变写法。
131 |
132 | ```dart
133 | // good
134 | HttpConnectionInfo
135 | uiHandler
136 | IOStream
137 | Id
138 | DB
139 |
140 | // bad
141 | HTTPConnection
142 | UiHandler
143 | IoStream
144 | ID
145 | Db
146 | ```
147 |
148 | #### [DON'T] 不要使用前缀字符
149 | > 这没有任何意义,因为dart会告诉你类型,作用域和其他你声明的属性,没有理由需要去标注前缀
150 | ```dart
151 | // good
152 | defaultTimeout
153 | // bad
154 | kDefaultTimeout
155 | ```
156 | **[⬆ back to top](#top)**
157 | ## 代码顺序 💧
158 | > 为了你的代码文件内容整洁,我们对于代码顺序有一些规定,每一个区块应该用空白行分割开
159 |
160 | #### [DO] 'dart:' 的引入代码放在其他引入之前
161 |
162 | ```dart
163 | import 'dart:async';
164 | import 'dart:html';
165 |
166 | import 'package:bar/bar.dart';
167 | ```
168 |
169 | #### [DO] 'package:' 的引入代码放在相对路径引入之前
170 |
171 | ```dart
172 | import 'package:bar/bar.dart';
173 |
174 | import '../util.dart';
175 | ```
176 |
177 | #### [PREFER] 将自己的包的引入代码放在其他三方包引入之后
178 |
179 | ```dart
180 | import 'package:bar/bar.dart';
181 |
182 | import 'package:my_package/util.dart';
183 | ```
184 |
185 | #### [DO] 导出代码应该放在所有导入代码之后
186 | ```dart
187 | // good
188 | import 'src/error.dart';
189 | import 'src/foo.dart';
190 |
191 | export 'src/error.dart';
192 |
193 | // bad
194 | import 'src/error.dart';
195 | export 'src/error.dart';
196 | import 'src/foo.dart';
197 | ```
198 |
199 | #### [DO] 同一类导入代码顺序根据文件或者包名的首字母排序
200 |
201 | ```dart
202 | // good
203 | import 'package:bar/bar.dart';
204 | import 'package:foo/foo.dart';
205 |
206 | // bad
207 | import 'package:foo/foo.dart';
208 | import 'package:bar/bar.dart';
209 | ```
210 | **[⬆ back to top](#top)**
211 | ## 代码格式化 🔨
212 | > 和大多数语言一样,Dart会忽略空格,然而语言使用者不会。拥有统一的空白行风格可以让人民阅读代码时想编译器一样高效。
213 |
214 | #### [DO] 使用dartfmt工具格式化你的代码
215 | > 格式化是很无聊的工作,在重构时特别耗费时间。幸运的是你现在不必为此担心,我们提供了一个复杂的自动化代码格式化工具叫做[dartfmt](https://github.com/dart-lang/dart_style) 🔗 。下面的一些格式化指导是为了解决一些dartfmt不能帮你解决的问题。
216 |
217 | #### [CONSIDER] 修改你的代码使之对于dartfmt处理变得亲和
218 | >格式化工具会尽力而为的格式化你的代码,但是它无法创造奇迹。如果你的代码有很长的标识符,或者很深层的嵌套,不同类型的运算符号混合等等,格式化后的输出代码依然难以阅读。
219 |
220 | >这种情况下,请重构或者简化你的代码。推荐你缩短变量名或者将表达式赋值给一个新的变量。换句话说,尽可能的提高你的代码的可读性。将dartfmt视为你的合作伙伴,合作产出漂亮的代码。
221 |
222 | #### [AVOID] 单行代码不要超过80个字符
223 | >可读性研究发现一行文字过长是难以阅读的,因为你的眼睛需要看一行文字从头看到位。这也是为什么报纸或者杂志使用多行文字来承载内容。
224 |
225 | >如果你发现你的一行代码将要超过80个字符,我们的经验是你的代码可能过于冗长需要一点压缩。最主要的原因可能是你使用了超长的类名。这时候你应该问自己是否每个单词都有特殊的含义或者防止了冲突,如果不是请考虑省略它。
226 |
227 | >请注意dartfmt会为你做99%的工作,但是最后1%的工作需要你来完成,字符限制这种工作需要你自己来做。我们对URL和文件路径做了处理,当这些出现在注释或者字符串(通常是importh和export)中即使超出限制我们也会保留成一行,这样方便搜索给定路径的源文件。
228 |
229 | #### [DO] 使用大括号包住所有控制流结构
230 | > 这样做可以避免悬空else问题
231 |
232 | ```dart
233 | // good
234 | if (isWeekDay) {
235 | print('I am verhappy');
236 | } else {
237 | print('Holy shit');
238 | }
239 |
240 | // bad
241 | if (overflowChars != other.overflowChars)
242 | return overflowChars < other.overflowChars;
243 | ```
244 | >下面是对于控制流代码风格的建议
245 |
246 | ```dart
247 | // good
248 | if (arg == null) return defaultValue;
249 |
250 | // good
251 | if (overflowChars != other.overflowChars) {
252 | return overflowChars < other.overflowChars;
253 | }
254 |
255 | // bad
256 | if (overflowChars != other.overflowChars)
257 | return overflowChars < other.overflowChars;
258 | ```
259 | **[⬆ back to top](#top)**
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | | Ⅰ | Ⅱ | Ⅲ | Ⅳ |
10 | | :--------: | :---------: | :---------: | :---------: |
11 | | [Style Guide](style.md):nail_care: | [Usage Guide](usage.md):bookmark:|[Documentation Guide](documentation.md):page_with_curl: | [Design Guide](design.md):gem: |
12 |
13 | ### What is the project ?
14 | ```dart
15 | // dart language
16 | void main() {
17 | Map repo = new Map()
18 | ..addAll({'name':'Effective Dart 中文版'})
19 | ..addAll({'author':'雷仔'})
20 | ..allAll({'lang':'中文'})
21 | ..adddAll({'status':'work in progress'});
22 | print(repo);
23 | }
24 | ```
25 | ## Table of contents
26 | ## 🔥 Effective Dart: Style 🔥
27 |
28 | 1. **标识符命名**
29 | - [DO] [类型命名使用大写驼峰风格](style.md#1-1)
30 | - [DO] [库个源文件命名使用小写加下划线风格](style.md#1-2)
31 | - [DO] [import前缀命名使用小写加下划线风格](style.md#1-3)
32 | - [DO] [其他命名使用小写驼峰风格](style.md#1-4)
33 | - [PREFER] [常量命名使用小写驼峰风格](style.md#1-5)
34 | - [DO] [缩略词大写](style.md#1-6)
35 | - [DON'T] [不要使用前缀字符](style.md#1-7)
36 | 2. **代码顺序**
37 | - [DO] [将`dart:xx`导入代码放在其他导入之前](style.md#2-1)
38 | - [DO] [将`package:xx`导入代码放在相对文件导入之前](style.md#2-2)
39 | - [PREFER] [将自己的包的引入代码放在其他三方包引入之后](style.md#2-3)
40 | - [DO] [导出代码应该放在所有导入代码之后](style.md#2-4)
41 | - [DO] [同一类导入代码顺序根据文件或者包名的首字母排序](style.md#2-5)
42 | 3. **格式化**
43 | - [DO] [使用`dartfmt`工具格式化你的代码](style.md#3-1)
44 | - [CONSIDER] [修改你的代码使之变得对格式化工具友好](style.md#3-2)
45 | - [AVOID] [单行代码不超过80个字符](style.md#3-3)
46 | - [DO] [使用大括号包裹你的控制流结构](style.md#3-4)
47 |
48 | ### Effective Dart: Usage
49 |
50 | - 库
51 | - [DO] [`part of`指令之后使用字符串](usage.md#1-1)
52 | - [DON'T] [不要在你的库的src目录下引入其他库](usage.md#1-2)
53 | - [PREFER] [库lib目录下的引入请使用相对路径](usage.md#1-3)
54 | - 字符串
55 | - [DO] [使用`adjacent strings`串联字符串而不是使用`+`号](usage.md#2-1)
56 | - [PREFER] [使用模板字符串来拼接值和字符而不是用`+`拼接](usage.md#2-2)
57 | - [AVOID] [在不需要使用大括号时省略大括号](usage.md#2-3)
58 | - 集合
59 | - [DO] [尽量使用字面量定义集合](usage.md#3-1)
60 | - [DON'T] [不要使用`.length`去判断集合是否为空](usage.md#3-2)
61 | - [CONSIDER] [使用高阶函数对集合进行转换处理](usage.md#3-3)
62 | - [AVOID] [避免在`Iterable.forEach()`里写函数](usage.md#3-4)
63 | - [DON'T] [不要使用`List.from()`除非你想转换集合类型](usage.md#3-5)
64 | - [DO] [使用`whereType()`去过滤集合类型](usage.md#3-6)
65 | - [DON'T] [当其他操作符可以转换类型时不要使用`cast()`](usage.md#3-7)
66 | - [AVOID] [避免使用`cast()`](usage.md#3-8)
67 | - 函数
68 | - [DO] [直接声明函数而不是将lambda函数赋值给一个变量](usage.md#4-1)
69 | - [DON'T] [`lambda`表达式尽量简洁](usage.md#4-2)
70 | - 参数
71 | - [DO] [使用`=`为吗命名参数设置默认值](usage.md#5-1)
72 | - [DON'T] [不要将默认值显式设置为`null`](usage.md#5-2)
73 | - 变量
74 | - [DON'T] [不要将初始化变量显式设置为`null`](usage.md#6-1)
75 | - [AVOID] [避免存储你可以计算的值](usage.md#6-2)
76 | - 成员
77 | - [DON'T] [不要在不必要的时候设置`getter`和`setter`](usage.md#7-1)
78 | - [PREFER] [使用`final`声明一个只读属性](usage.md#7-2)
79 | - [CONSIDER] [对于一个简单属性的获取使用`=>`](usage.md#7-3)
80 | - [DON'T] [不要在不必要的时候使用`this`](usage.md#7-4)
81 | - [DO] [尽量在初始值声明时赋值初始值](usage.md#7-5)
82 | - 构造函数
83 | - [DO] [尽量使用简洁的构造函数声明方式](usage.md#8-1)
84 | - [DON'T] [不要为构造函数参数声明类型](usage.md#8-2)
85 | - [DO] [构造函数body为空时使用`;`而不是`{}`](usage.md#8-3)
86 | - [DON'T] [不要使用`new`关键字声明实例](usage.md#8-4)
87 | - [DON'T] [不要重复冗余的声明`const`](usage.md#8-5)
88 | - 错误处理
89 | - [AVOID] [避免在没有条件控制下捕捉错误](usage.md#9-1)
90 | - [DON'T] [不要忽略错误](usage.md#9-2)
91 | - [DO] [仅仅在语法错误的情况下抛出实现`Error`的类](usage.md#9-3)
92 | - [DON'T] [开发时不要对错误做处理,let's crash](usage.md#9-4)
93 | - [DO] [使用`rethrow`关键词重新抛出无法处理的异常](usage.md#9-5)
94 | - 异步
95 | - [PREFER] [使用`async/await`优于传统的`Future`](usage.md#10-1)
96 | - [DON'T] [不要在`async`没有任何作用时使用它](usage.md#10-2)
97 | - [CONSIDER] [使用高阶函数处理转换流`stream`](usage.md#10-3)
98 | - [AVOID] [避免直接使用`Completer`类](usage.md#10-4)
99 | - [DO] [当参数声明类型为`Future`时候,参数可能为`Object`的情况下请用`Future`做类型判断](usage.md#10-5)
100 |
101 | ## Effective Dart: Documentation
102 |
103 | - 注释
104 | - [DO] [用描述性的语句写注释](documentation.md#1-1)
105 | - [DON'T] [使用块注释作为文档](documentation.md#1-2)
106 | - 文档注释
107 | - [DO] [使用`\\\`文档注释去注释成员和类型](documentation.md#2-1)
108 | - [PREFER] [为公共API写文档注释](documentation.md#2-2)
109 | - [CONSIDER] [编写库级别的文档注释](documentation.md#2-3)
110 | - [CONSIDER] [为私有API编写文档注释](documentation.md#2-4)
111 | - [DO] [使用一句话完成文档总结](documentation.md#2-5)
112 | - [DO] [将文档注释段落的第一句话分离出来](documentation.md#2-6)
113 | - [AVOID] [注释避免冗余](documentation.md#2-7)
114 | - [PREFER] [注释函数或者方法时使用第三人称动词](documentation.md#2-8)
115 | - [PREFER] [注释变量,getter/setter时使用名词短语](documentation.md#2-9)
116 | - [PREFER] [注释库或者类型时使用名词短语](documentation.md#2-10)
117 | - [CONSIDER] [在注释中提供代码例子](documentation.md#2-11)
118 | - [DO] [在文档注释中使用方括号突出作用域內标识符](documentation.md#2-12)
119 | - [DO] [使用简介的描述来注释参数,返回值和抛出的异常](documentation.md#2-13)
120 | - [DO] [将文档注释放在元数据注解`@`之前](documentation.md#2-14)
121 | - Markdown
122 | - [AVOID] [避免过度使用Markdown](documentation.md#3-1)
123 | - [AVOID] [避免使用HTML作为注释](documentation.md#3-2)
124 | - [PREFER] [使用反引号来区分代码块](documentation.md#3-3)
125 | - 书写
126 | - [PREFER] [尽量简短](documentation.md#4-1)
127 | - [AVOID] [尽量避免使用缩略词除非它们很常见](documentation.md#4-2)
128 | - [PREFER] [使用`this`而不是`the`去指代实例本身](documentation.md#4-3)
129 |
130 | ## Effective Dart: Design
131 | > WIP (work in progress)
132 |
133 | - 命名
134 | - [DO] [使用统一的命名策略](design.md#1-1)
135 | - [AVOID] [避免使用缩写](design.md#1-2)
136 | - [PREFER] [将最有意义的描述名词放在最后](design.md#1-3)
137 | - [CONSIDER] [增加代码可读性,使之语义化](design.md#1-4)
138 | - [PREFER] [非布尔值的属性或者变量命名使用名词短语](design.md#1-5)
139 | - [PREFER] [布尔值属性或者变量命名使用非命令性动词](design.md#1-6)
140 | - [CONSIDER] [布尔值命名参数请省略动词](design.md#1-7)
141 | - [PREFER] [布尔值属性或者变量命名时使用正面词汇命名](design.md#1-8)
142 | - [PREFER] [当函数或者方法会产生副作用时用动词短语命名](design.md#1-9)
143 | - [PREFER] [当函数或者方法有返回值时用名词短语命名](design.md#1-10)
144 | - [CONSIDER] [如果你想强调函数工作内容请用动词短语命名](design.md#1-11)
145 | - [AVOID] [避免使用get作为函数方法名字的开头](design.md#1-12)
146 | - [PREFER] [如果是将一个对象状态复制到另一个对象的方法请使用`to_()`格式命名](design.md#1-13)
147 | - [PREFER] [改变对象类型使用`as_()`格式命名](design.md#1-14)
148 | - [AVOID] [不要在函数方法名中出现参数名](design.md#1-15)
149 | - [DO] [当给类型参数命名时请遵循如下的助记符约定](design.md#1-16)
150 | - 库
151 | - [PREFER] [声明为私有](design.md#2-1)
152 | - [CONSIDER] [可以在同一个库里声明多个类](design.md#2-2)
153 | - 类
154 | - [AVOID] [避免定义单成员抽象类使用函数替代](design.md#3-1)
155 | - [AVOID] [避免定义只含一个静态成员的类](design.md#3-2)
156 | - [AVOID] [避免继承不想拥有子类的类](design.md#3-3)
157 | - [DO] [如果你的类支持继承请在文档里说明](design.md#3-4)
158 | - [AVOID] [避免实现不支持作为接口的类](design.md#3-5)
159 | - [DO] [如果你的类支持作为借口请在文档里说明](design.md#3-6)
160 | - [AVOID] [避免混合`mixin`不支持作为`mixin`的类](design.md#3-7)
161 | - [DO] [如果你的类支持作为`mixin`请在文档里说明](design.md#3-8)
162 | - 构造函数
163 | - [PREFER] [建议定义构造函数而不是静态方法去生成实例](design.md#4-1)
164 | - [CONSIDER] [如果类支持请将构造函数声明为`const`](design.md#4-2)
165 | - 类成员
166 | - [PREFER] [将顶级作用域的变量声明为`final`](design.md#4-1)
167 | - [DO] [为可获得的属性添加`getters`](design.md#4-2)
168 | - [DO] [为可设置的属性添加`setters`](design.md#4-3)
169 | - [DON'T] [不要定义`setters`时不定义对应的`getters`](design.md#4-4)
170 | - [AVOID] [避免返回值返回`null`](design.md#4-5)
171 | - [AVOID] [避免直接返回`this`,链式调用请用cascade也就是`..`运算符](design.md#4-6)
172 | - 类型
173 | - [PREFER] [声明顶级作用域变量类型](design.md#5-1)
174 | - [CONSIDER] [声明私有作用域变量类型](design.md#5-2)
175 | - [AVOID] [避免声明初始化本地变量类型](design.md#5-3)
176 | - [AVOID] [避免在闭包中声明推断参数的类型](design.md#5-4)
177 | - [AVOID] [避免在使用泛型时重复冗余的声明类型](design.md#5-5)
178 | - [DO] [当推断类型时确定类型时请声明类型](design.md#5-6)
179 | - [PREFER] [使用`dynamic`声明类型而不是让推断直接fail掉](design.md#5-7)
180 | - [PREFER] [当函数作为参数时请声明函数类型](design.md#5-8)
181 | - [DON'T] [不要为`setter`声明类型](design.md#5-9)
182 | - [DON'T] [不要使用遗留版本的`typedef`语法](design.md#5-10)
183 | - [PREFER] [使用函数类型声明而不是`typedef`](design.md#5-11)
184 | - [CONSIDER] [函数作为参数时使用函数类型语法](design.md#5-12)
185 | - [DO] [当参数可以是任意类型时用`Object`声明而不是`dynamic`](design.md#5-13)
186 | - [DO] [当异步函数不返回值时使用`Future`声明](design.md#5-14)
187 | - [AVOID] [避免使用`FutureOr`作为返回类型](design.md#5-15)
188 | - 参数
189 | - [AVOID] [避免布尔值类型位置参数](design.md#6-1)
190 | - [AVOID] [如果你想省略一些参数请请避免使用位置参数](design.md#6-2)
191 | - [AVOID] [避免强制性参数当参数可以省略时](design.md#6-3)
192 | - [DO] [获取范围时将参数设置为左闭右开](design.md#6-4)
193 | - 相等符
194 | - [DO] [如果你重载`==`请一并重载`hashcode`](design.md#7-1)
195 | - [DO] [请让你的`==`运算符遵守数学上的相等](design.md#7-2)
196 | - [AVOID] [避免为可变类定义常规意义上的相等](design.md#7-3)
197 | - [DON'T] [不需要在重载`==`运算符时判断类型是否为`null`](design.md#7-4)
198 |
199 | #### Find a job?(:office: : Shanghai,China):point_down:
200 | > 上海寻梦科技([拼多多](http://www.pinduoduo.com/social.html)) 高速上升期,招聘算法-前端-客户端-Java开发-Python开发-Golang开发等
201 | > 如果你正在寻找合适的工作,内推请联系我投递简历(E-Mail: eXVubGVpQHBpbmR1b2R1by5jb20=)
202 |
203 |
204 | ### License
205 |
206 | [MIT](http://opensource.org/licenses/MIT)
207 |
208 | Copyright (c) 2018-present, @iChenLei
209 |
--------------------------------------------------------------------------------
/documentation.md:
--------------------------------------------------------------------------------
1 |
2 | ## 🔥 Effective Dart: Documentation 🔥
3 |
4 | 大多数程序员讨厌两件事:1.写注释 2.别人不写注释。这很滑稽,但也说明了注释的重要性,特别是自己在接手别人的代码时。我们需要注释来保证代码的可维护性。
5 |
6 | - 注释
7 | - [DO] [用描述性的语句写注释](#1-1)
8 | - [DON'T] [使用块注释作为文档](#1-2)
9 | - 文档注释
10 | - [DO] [使用`\\\`文档注释去注释成员和类型](#2-1)
11 | - [PREFER] [为公共API写文档注释](#2-2)
12 | - [CONSIDER] [编写库级别的文档注释](#2-3)
13 | - [CONSIDER] [为私有API编写文档注释](#2-4)
14 | - [DO] [使用一句话完成文档总结](#2-5)
15 | - [DO] [将文档注释段落的第一句话分离出来](#2-6)
16 | - [AVOID] [注释避免冗余](#2-7)
17 | - [PREFER] [注释函数或者方法时使用第三人称动词](#2-8)
18 | - [PREFER] [注释变量,getter/setter时使用名词短语](#2-9)
19 | - [PREFER] [注释库或者类型时使用名词短语](#2-10)
20 | - [CONSIDER] [在注释中提供代码例子](#2-11)
21 | - [DO] [在文档注释中使用方括号突出作用域內标识符](#2-12)
22 | - [DO] [使用简介的描述来注释参数,返回值和抛出的异常](#2-13)
23 | - [DO] [将文档注释放在元数据注解`@`之前](#2-14)
24 | - Markdown
25 | - [AVOID] [避免过度使用Markdown](#3-1)
26 | - [AVOID] [避免使用HTML作为注释](#3-2)
27 | - [PREFER] [使用反引号来区分代码块](#3-3)
28 | - 书写
29 | - [PREFER] [尽量简短](#4-1)
30 | - [AVOID] [尽量避免使用缩略词除非它们很常见](#4-2)
31 | - [PREFER] [使用`this`而不是`the`去指代实例本身](#4-3)
32 |
33 | ### 注释
34 | > 下面的注释不会出现在自动生成的文档中
35 |
36 |
37 | #### [DO] 用描述性的语句写注释
38 | ```dart
39 | // Not if there is nothing befort it.
40 | if (_chunks.isEmpty) return false;
41 | ```
42 | > 将第一个单词首字母大写除非是大小写敏感的标识符,这对文档注释,TODO等都是通用的
43 |
44 |
45 | #### [DON'T] 不要使用块注释作为文档
46 | ``` dart
47 | // good
48 | greet(name) {
49 | // Assume we have a valid name
50 | print('Hi $name !');
51 | }
52 |
53 | // bad
54 | greet(name) {
55 | /* Assume we have a valid name */
56 | print('Hi $name');
57 | }
58 | ```
59 | *你可以使用在代码体外使用块注释(/\* ... \*/)作为临时注释,其他情况使用\/\/*
60 |
61 | **[⬆ back to top](#top)**
62 |
63 | ### 文档注释
64 | > 文档注释是很重要的,因为dartdoc可以解析注释来生成文档。dartdoc工具会根据`///`标识符去寻找解析文档注释相关内容。
65 |
66 |
67 | #### [DO] 使用`///`也就是文档注释来方便生成文档
68 | > 使用文档注释代替普通注释可以方便dartdoc工具去解析注释以生成文档
69 | ```dart
70 | // good
71 | /// The number of characters in this chunk when unsplit.
72 | int get length => ...
73 |
74 | // bad
75 | // The number of characters in this chunk when unsplit
76 | int get length => ...
77 | ```
78 | > 因为历史原因dartdoc支持两种风格的文档注释( `///` C# style)和( `/** ... */` JavaDoc Style),我们更建议使用///这种风格,因为相对于JavaDoc风格更加紧凑特别是多行注释的时候。
79 |
80 |
81 | #### [PREFER] 为公共API写文档注释
82 | > 你不需要为每一个顶级变量,库,类型和成员写上注释,但是大多数还是需要文档注释的
83 |
84 |
85 | #### [CONSIDER] 编写库级别的文档注释
86 | > 不同于其他语言比如Java类是一个单独的程序组织(比如JAR包),在Dart语言中库是可以直接被用户使用的(有点类似Nodejs库)。这导致了`library`指令所在的位置成了一个比较好的位置,可以用来写文档来注释说明库的主要用途已经提供了哪些函数供使用。可以包括以下内容:
87 |
88 | - 一句话说明总结库的作用
89 | - 解释库中出现的一些术语
90 | - 一些完整的代码例子
91 | - 指向最重要或者最常用的类和函数的链接
92 | - 指向库中使用的外部引用的链接
93 |
94 | > TIPS: 如果使用的库没有`library`指令你可以自己添加一个
95 |
96 |
97 | #### [CONSIDER] 为私有API编写文档注释
98 | > 文档注释不仅仅用于面向用户的公共API,私有API的文档注释可以帮助用户理解库的功能实现
99 |
100 |
101 | #### [DO] 使用一句话总结开始你的文档注释
102 | > 文档注释使用一句总结性的描述开头,帮助用户快速了解用户最关注的功能
103 |
104 | ```dart
105 | // goods
106 | /// Deletes the file at [path] from the file system.
107 | void delete(String path) {
108 | ...
109 | }
110 |
111 | // bad
112 | /// Depending on the state of the file system and the user's permissions,
113 | /// certain operations may or may not be possible. If there is no file at
114 | /// [path] or it can't be accessed, this function throws either [IOError]
115 | /// or [PermissionError], respectively. Otherwise, this deletes the file.
116 | void delete(String path) {
117 | ...
118 | }
119 | ```
120 |
121 | #### [DO] 将文档注释段落的第一句话分离出来
122 | > 通过增加一行空白行将第一句总结从注释段落中分离出来,如果不止一句解释是有用的,那就将剩余的注释分离出来。这可以帮助你写出简短的句子来总结文档,像`dartdoc`这种工具也会将第一个段落作为一个简短的总结。
123 |
124 | ```dart
125 | // good
126 | /// Deletes the file at [path].
127 | ///
128 | /// Throws an [IOError] if the file could not be found. Throws a
129 | /// [PermissionError] if the file is present but could not be deleted.
130 | void delete(String path) {
131 | ...
132 | }
133 |
134 | // bad
135 | /// Deletes the file at [path]. Throws an [IOError] if the file could not
136 | /// be found. Throws a [PermissionError] if the file is present but could
137 | /// not be deleted.
138 | void delete(String path) {
139 | ...
140 | }
141 | ```
142 | **[⬆ back to top](#top)**
143 |
144 |
145 | #### [AVOID] 避免信息冗余
146 | > 类注释文档的阅读者可以很清楚的看到类名,以及实现的接口。注释文档应该专注于用户不知道的东西而不是将显而易见的东西也罗列出来。
147 |
148 | ```dart
149 | // good
150 | class RadioButtonWidget extends Widget {
151 | /// Sets the tooltip to [lines], which should have been word wrapped using
152 | /// the current font.
153 | void tooltip(List lines) {
154 | ...
155 | }
156 | }
157 |
158 | // bad
159 | class RadioButtonWidget extends Widget {
160 | /// Sets the tooltip for this radio button widget to the list of strings in
161 | /// [lines].
162 | void tooltip(List lines) {
163 | ...
164 | }
165 | }
166 | ```
167 |
168 |
169 | #### [PREFER] 注释函数或者方法时使用第三人称动词
170 | > 文档注释应该专注于代码能做什么
171 |
172 | ```dart
173 | /// Returns `true` if every element satisfies the [predicate].
174 | bool all(bool predicate(T element)) => ...
175 |
176 | /// Starts the stopwatch if not already running.
177 | void start() {
178 | ...
179 | }
180 | ```
181 |
182 |
183 | #### [PREFER] 注释变量,getter/setter时使用名词短语
184 | > 文档属性应当强调属性是什么,比如对于getter来说调用者关心的是处理结果而不是如何处理
185 |
186 | ```dart
187 | /// The current day of the week, where `0` is Sunday.
188 | int weekday;
189 |
190 | /// The number of checked buttons on the page.
191 | int get checkedCount => ...
192 | ```
193 | *避免同时为getter和setter编写文档注释,dartdoc只会解析并展示一个*
194 |
195 |
196 | #### [PREFER] 注释库或者类型时使用名词短语
197 | > 文档注释对于类来说是非常重要的
198 | ```dart
199 | /// A chunk of non-breaking output text terminated by a hard or soft newline.
200 | ///
201 | /// ...
202 | class Chunk { ... }
203 | ```
204 |
205 | #### [CONSIDER] 在注释中提供代码例子
206 | ```dart
207 | /// Returns the lesser of two numbers.
208 | ///
209 | /// ```dart
210 | /// min(5, 3) == 3
211 | /// ```
212 | num min(num a, num b) => ...
213 | ```
214 | *代码例子使API的学习更加高效*
215 |
216 |
217 | #### [DO] 在文档注释中使用方括号突出作用域內标识符
218 | > 如果你将变量,方法或者类名使用方括号包裹,dartdoc可以查询这些名字并生成指向相关API的链接,尽管这是可选的但这让你的文档变得更加清晰
219 |
220 | ```dart
221 | /// Throws a [StateError] if ...
222 | /// similar to [anotherMethod()], but ...
223 | ```
224 | >为了链接类的特定成员变量或者函数,可以用点(dot)号将类和成员连接起来
225 |
226 | ```dart
227 | /// Similar to [Duration.inDays], but handles fractional days.
228 | ```
229 | > dot语法也可以用于命名构造函数,对于默认未命名构造函数可以直接在类名后加上括号
230 | ```dart
231 | /// To create a point, call [Point()] or use [Point.polar()] to ...
232 | ```
233 |
234 | #### [DO] 使用简介的描述来注释参数,返回值和抛出的异常
235 | > 其他语言用很冗长的tag和段落来描述参数和返回值
236 |
237 | ```dart
238 | // bad
239 | /// Defines a flag with the given name and abbreviation.
240 | ///
241 | /// @param name The name of the flag.
242 | /// @param abbr The abbreviation for the flag.
243 | /// @returns The new flag.
244 | /// @throws ArgumentError If there is already an option with
245 | /// the given name or abbreviation.
246 | Flag addFlag(String name, String abbr) => ...
247 | ```
248 | > 在dart中我们通过使用方括号高亮我们的参数返回值等,这让注释变得更加简洁
249 |
250 | ```dart
251 | // good
252 | /// Defines a flag.
253 | ///
254 | /// Throws an [ArgumentError] if there is already an option named [name] or
255 | /// there is already an option using abbreviation [abbr]. Returns the new flag.
256 | Flag addFlag(String name, String abbr) => ...
257 | ```
258 |
259 |
260 | #### [DO] 将文档注释放在元数据注解`@`之前
261 | ```dart
262 | // good
263 | /// A button that can be flipped on and off.
264 | @Component(selector: 'toggle')
265 | class ToggleComponent {}
266 |
267 | // bad
268 | @Component(selector: 'toggle')
269 | /// A button that can be flipped on and off.
270 | class ToggleComponent {}
271 | ```
272 | **[⬆ back to top](#top)**
273 |
274 | ## Markdown
275 | > 你可以通过使用Markdown来注释你的文档,dartdoc通过markdown包来解析Markdown的内容。
276 | ```dart
277 | /// This is a paragraph of regular text.
278 | ///
279 | /// This sentence has *two* _emphasized_ words (italics) and **two**
280 | /// __strong__ ones (bold).
281 | ///
282 | /// A blank line creates a separate paragraph. It has some `inline code`
283 | /// delimited using backticks.
284 | ///
285 | /// * Unordered lists.
286 | /// * Look like ASCII bullet lists.
287 | /// * You can also use `-` or `+`.
288 | ///
289 | /// 1. Numbered lists.
290 | /// 2. Are, well, numbered.
291 | /// 1. But the values don't matter.
292 | ///
293 | /// * You can nest lists too.
294 | /// * They must be indented at least 4 spaces.
295 | /// * (Well, 5 including the space after `///`.)
296 | ///
297 | /// Code blocks are fenced in triple backticks:
298 | ///
299 | /// ```
300 | /// this.code
301 | /// .will
302 | /// .retain(its, formatting);
303 | /// ```
304 | ///
305 | /// The code language (for syntax highlighting) defaults to Dart. You can
306 | /// specify it by putting the name of the language after the opening backticks:
307 | ///
308 | /// ```html
309 | /// HTML is magical!
310 | /// ```
311 | ///
312 | /// Links can be:
313 | ///
314 | /// * http://www.just-a-bare-url.com
315 | /// * [with the URL inline](http://google.com)
316 | /// * [or separated out][ref link]
317 | ///
318 | /// [ref link]: http://google.com
319 | ///
320 | /// # A Header
321 | ///
322 | /// ## A subheader
323 | ///
324 | /// ### A subsubheader
325 | ///
326 | /// #### If you need this many levels of headers, you're doing it wrong
327 | ```
328 |
329 |
330 | #### [AVOID] 避免过度使用Markdown
331 | > 简言之,文字更重要
332 |
333 |
334 | #### [AVOID] 避免使用HTML作为注释
335 | > 维护麻烦,不建议使用
336 |
337 |
338 | #### [PREFER] 使用反引号来区分代码块
339 | > Markdown可以通过两种方式展示代码块,一种是空行一种是反引号,推荐第二种
340 |
341 | ```dart
342 | // good
343 | /// You can use [CodeBlockExample] like this:
344 | ///
345 | /// ```
346 | /// var example = CodeBlockExample();
347 | /// print(example.isItGreat); // "Yes."
348 | /// ```
349 |
350 | // bad
351 | /// You can use [CodeBlockExample] like this:
352 | ///
353 | /// var example = CodeBlockExample();
354 | /// print(example.isItGreat); // "Yes."
355 | ```
356 | **[⬆ back to top](#top)**
357 |
358 | ## 写作
359 | > 我们总是把自己当做编程人员,但是源文件中大多数文字是给人阅读的,所以写作能力很重要,这有一篇指导科技写作的文章[Technical writing style](https://en.wikiversity.org/wiki/Technical_writing_style).
360 |
361 |
362 | #### [PREFER] 尽量简短
363 | >简洁不简单
364 |
365 |
366 | #### [AVOID] 尽量避免使用缩略词除非它们很常见
367 | > 不是所有人都懂`i.e.`,`e.g.`等等类似的缩写
368 |
369 |
370 | #### [PREFER] 使用`this`而不是`the`去指代实例本身
371 | > 写类的相关文档时经常会描述类本身,使用`this`代指`class`,使用`the`会造成困惑。
372 | ```dart
373 | class Box {
374 | /// The value this wraps.
375 | var _value;
376 |
377 | /// True if this box contains a value.
378 | bool get hasValue => _value != null;
379 | }
380 | ```
381 |
382 | **[⬆ back to top](#top)**
--------------------------------------------------------------------------------
/design.md:
--------------------------------------------------------------------------------
1 |
2 | ## 🔥 Effective Dart: Design 🔥
3 | > WIP (work in progress)
4 |
5 | - 命名
6 | - [DO] [使用统一的命名策略](#1-1)
7 | - [AVOID] [避免使用缩写](#1-2)
8 | - [PREFER] [将最有意义的描述名词放在最后](#1-3)
9 | - [CONSIDER] [增加代码可读性,使之语义化](#1-4)
10 | - [PREFER] [非布尔值的属性或者变量命名使用名词短语](#1-5)
11 | - [PREFER] [布尔值属性或者变量命名使用非命令性动词](#1-6)
12 | - [CONSIDER] [布尔值命名参数请省略动词](#1-7)
13 | - [PREFER] [布尔值属性或者变量命名时使用正面词汇命名](#1-8)
14 | - [PREFER] [当函数或者方法会产生副作用时用动词短语命名](#1-9)
15 | - [PREFER] [当函数或者方法有返回值时用名词短语命名](#1-10)
16 | - [CONSIDER] [如果你想强调函数工作内容请用动词短语命名](#1-11)
17 | - [AVOID] [避免使用get作为函数方法名字的开头](#1-12)
18 | - [PREFER] [如果是将一个对象状态复制到另一个对象的方法请使用`to_()`格式命名](#1-13)
19 | - [PREFER] [改变对象类型使用`as_()`格式命名](#1-14)
20 | - [AVOID] [不要在函数方法名中出现参数名](#1-15)
21 | - [DO] [当给类型参数命名时请遵循如下的助记符约定](#1-16)
22 | - 库
23 | - [PREFER] [声明为私有](#2-1)
24 | - [CONSIDER] [可以在同一个库里声明多个类](#2-2)
25 | - 类
26 | - [AVOID] [避免定义单成员抽象类使用函数替代](#3-1)
27 | - [AVOID] [避免定义只含一个静态成员的类](#3-2)
28 | - [AVOID] [避免继承不想拥有子类的类](#3-3)
29 | - [DO] [如果你的类支持继承请在文档里说明](#3-4)
30 | - [AVOID] [避免实现不支持作为接口的类](#3-5)
31 | - [DO] [如果你的类支持作为借口请在文档里说明](#3-6)
32 | - [AVOID] [避免混合`mixin`不支持作为`mixin`的类](#3-7)
33 | - [DO] [如果你的类支持作为`mixin`请在文档里说明](#3-8)
34 | - 构造函数
35 | - [PREFER] [建议定义构造函数而不是静态方法去生成实例](#4-1)
36 | - [CONSIDER] [如果类支持请将构造函数声明为`const`](#4-2)
37 | - 类成员
38 | - [PREFER] [将顶级作用域的变量声明为`final`](#4-1)
39 | - [DO] [为可获得的属性添加`getters`](#4-2)
40 | - [DO] [为可设置的属性添加`setters`](#4-3)
41 | - [DON'T] [不要定义`setters`时不定义对应的`getters`](#4-4)
42 | - [AVOID] [避免返回值返回`null`](#4-5)
43 | - [AVOID] [避免直接返回`this`,链式调用请用cascade也就是`..`运算符](#4-6)
44 | - 类型
45 | - [PREFER] [声明顶级作用域变量类型](#5-1)
46 | - [CONSIDER] [声明私有作用域变量类型](#5-2)
47 | - [AVOID] [避免声明初始化本地变量类型](#5-3)
48 | - [AVOID] [避免在闭包中声明推断参数的类型](#5-4)
49 | - [AVOID] [避免在使用泛型时重复冗余的声明类型](#5-5)
50 | - [DO] [当推断类型时确定类型时请声明类型](#5-6)
51 | - [PREFER] [使用`dynamic`声明类型而不是让推断直接fail掉](#5-7)
52 | - [PREFER] [当函数作为参数时请声明函数类型](#5-8)
53 | - [DON'T] [不要为`setter`声明类型](#5-9)
54 | - [DON'T] [不要使用遗留版本的`typedef`语法](#5-10)
55 | - [PREFER] [使用函数类型声明而不是`typedef`](#5-11)
56 | - [CONSIDER] [函数作为参数时使用函数类型语法](#5-12)
57 | - [DO] [当参数可以是任意类型时用`Object`声明而不是`dynamic`](#5-13)
58 | - [DO] [当异步函数不返回值时使用`Future`声明](#5-14)
59 | - [AVOID] [避免使用`FutureOr`作为返回类型](#5-15)
60 | - 参数
61 | - [AVOID] [避免布尔值类型位置参数](#6-1)
62 | - [AVOID] [如果你想省略一些参数请请避免使用位置参数](#6-2)
63 | - [AVOID] [避免强制性参数,当参数可以省略时](#6-3)
64 | - [DO] [获取范围时将参数设置为左闭右开](#6-4)
65 | - 相等判断
66 | - [DO] [如果你重载`==`请一并重载`hashcode`](#7-1)
67 | - [DO] [请让你的`==`运算符遵守数学上的相等](#7-2)
68 | - [AVOID] [避免为可变类定义常规意义上的相等](#7-3)
69 | - [DON'T] [不需要在重载`==`运算符时判断类型是否为`null`](#7-4)
70 |
71 | ### 命名
72 | > 命名是编写易于阅读的、可维护代码的关键之一。 下面的最佳实践可以帮助你实现这个目标。
73 |
74 | #### [DO] 使用一致的术语
75 | >对于同样的东西要一直使用同样的名字。 如果在你的库之外已经存在一个广为人知的名字了, 请继续使用这个名字。
76 |
77 | ```dart
78 | // good
79 | pageCount // A field.
80 | updatePageCount() // Consistent with pageCount.
81 | toSomething() // Consistent with Iterable's toList().
82 | asSomething() // Consistent with List's asMap().
83 | Point // A familiar concept.
84 |
85 | // bad
86 | renumberPages() // Confusingly different from pageCount.
87 | convertToSomething() // Inconsistent with toX() precedent.
88 | wrappedAsSomething() // Inconsistent with asX() precedent.
89 | Cartesian // Unfamiliar to most users.
90 | ```
91 | >目标是尽量利用用户已知的内容。包括他们所熟知的领域、 核心库的习惯用法、 以及你的 API 的其他部分的使用习惯。在这些熟知的基础之上命名你的代码, 可以减少你的用户使用你的库的学习成本, 提高他们的生产效率。
92 |
93 | #### [AVOID] 避免使用缩写
94 | >只使用广为人知的缩写,对于特有领域的缩写,请进来不要使用。 如果要使用,请 正确的指定首字母大小写。
95 |
96 | ```dart
97 | // good
98 | pageCount
99 | buildRectangles
100 | IOStream
101 | HttpRequest
102 |
103 | // bad
104 | numPages // "num" is an abbreviation of number(of)
105 | buildRects
106 | InputOutputStream
107 | HypertextTransferProtocolRequest
108 | ```
109 | #### [PREFER] 将最有意义的描述名词放在最后
110 | > 最后一个单词应该可以描述所代表的东西。 你可以在之前添加其他前缀来进一步详细描述,例如 其他形容词。
111 |
112 | ```dart
113 | // good
114 | pageCount // A count (of pages).
115 | ConversionSink // A sink for doing conversions.
116 | ChunkedConversionSink // A ConversionSink that's chunked.
117 | CssFontFaceRule // A rule for font faces in CSS.
118 |
119 | // bad
120 | numPages // Not a collection of pages.
121 | CanvasRenderingContext2D // Not a "2D".
122 | RuleFontFaceCss // Not a CSS.
123 | ```
124 | #### [CONSIDER] 增加代码可读性,使之语义化
125 | > 当你不知道如何命名 API 的时候,尝试着用你的 API 写一些代码, 尽量让你写的代码看起来像普通的句子一样。
126 |
127 | ```dart
128 | // good
129 | // "If errors is empty..."
130 | if (errors.isEmpty) ...
131 |
132 | // "Hey, subscription, cancel!"
133 | subscription.cancel();
134 |
135 | // "Get the monsters where the monster has claws."
136 | monsters.where((monster) => monster.hasClaws);
137 |
138 | // bad
139 | // Telling errors to empty itself, or asking if it is?
140 | if (errors.empty) ...
141 |
142 | // Toggle what? To what?
143 | subscription.toggle();
144 |
145 | // Filter the monsters with claws *out* or include *only* those?
146 | monsters.filter((monster) => monster.hasClaws);
147 | ```
148 | >尝试着使用你自己的 API,并且阅读以下写出来的代码,可以帮助你提高命名的技能。 添加其他文学和语法修饰让代码看起来更像语法正确的句子 是不必要的。
149 | ```dart
150 | // bad
151 | if (theCollectionOfErrors.isEmpty) ...
152 |
153 | monsters.producesANewSequenceWhereEach((monster) => monster.hasClaws);
154 | ```
155 | #### [PREFER] 非布尔值的属性或者变量命名使用名词短语
156 | >读者关注属性是什么。如果用户更关心 如何确定一个属性,则很可能应该是一个函数, 并使用动词短语命名该函数。
157 |
158 | ```dart
159 | // good
160 | list.length
161 | context.lineWidth
162 | quest.rampagingSwampBeast
163 |
164 | // bad
165 | list.deleteItems
166 |
167 | ```
168 | #### [PREFER] 布尔值属性或者变量命名使用非命令性动词
169 | > 布尔名称通常用在控制语句中当做条件,所以你需要让他在 控制条件中语感很好。比较下面的两个:
170 | ```dart
171 | if (window.closeable) ... // Adjective.
172 | if (window.canClose) ... // Verb.
173 | ```
174 | >可以使用命令式动词来区分布尔变量名字和函数名字。 一个布尔变量的名字不应该看起来像一个命令,告诉这个对象做什么事情。 原因在于访问一个变量的属性并没有修改对象的状态。 如果这个属性确实修改了对象的状态,则它应该是一个函数。
175 |
176 | ```dart
177 | // good
178 | isEmpty
179 | hasElements
180 | canClose
181 | closesWindow
182 | canShowPopup
183 | hasShownPopup
184 |
185 | // bad
186 | empty // Adjective or verb?
187 | withElements // Sounds like it might hold elements.
188 | closeable // Sounds like an interface.
189 | // "canClose" reads better as a sentence.
190 | closingWindow // Returns a bool or a window?
191 | showPopup // Sounds like it shows the popup.
192 | ```
193 | #### [CONSIDER] 布尔值命名参数请省略动词
194 | > 提炼于上一条规则。对于命名布尔参数,没有动词的 名称通常看起来更加舒服。
195 | ```dart
196 | Isolate.spawn(entryPoint, message, paused: false);
197 | var copy = List.from(elements, growable: true);
198 | var regExp = RegExp(pattern, caseSensitive: false);
199 | ```
200 |
201 | #### [PREFER]布尔值属性或者变量命名时使用正面词汇命名
202 | > 函数通常返回一个结果给调用者,并且执行一些任务或者带有副作用。 在像 Dart 这种命令式语言中,调用函数通常为了实现其副作用: 可能改变了对象的内部状态、 产生一些输出内容、或者和外部世界沟通等。
203 |
204 | >这种类型的成员应该使用命令式动词短语来命名,强调 该成员所执行的任务。
205 |
206 | ```dart
207 | list.add("element");
208 | queue.removeFirst();
209 | window.refresh();
210 | ```
211 | #### [PREFER] 使用名词短语或者非命令式动词短语命名返回数据为主要功能的方法或者函数。
212 | >虽然这些函数可能也有副作用,但是其主要目的是返回一个数据给调用者。 如果该函数无需参数通常应该是一个 getter 。 有时候获取一个属性则需要一些参数,比如, elementAt() 从集合中返回一个数据,但是需要一个 指定返回那个数据的参数。
213 |
214 | >在语法上看这是一个函数,其实严格来说其返回的是集合中的一个属性, 应该使用一个能够表示该函数返回的是什么的词语 来命名。
215 | ```dart
216 | var element = list.elementAt(3);
217 | var first = list.firstWhere(test);
218 | var char = string.codeUnitAt(4);
219 | ```
220 |
221 | ```dart
222 | // good
223 | var table = database.downloadData();
224 | var packageVersions = packageGraph.solveConstraints();
225 | ```
226 | #### [PREFER] 如果是将一个对象状态复制到另一个对象的方法请使用`to_()`格式命名
227 | >一个转换函数返回一个新的对象,里面包含一些原对象的状态, 可能还有稍微的修改。 核心库中很多类似的函数命名为 toXXX 。
228 |
229 | >如果你也定义了一个转换函数,最好也使用同样的命名方式。
230 | ```dart
231 | // good
232 | list.toSet();
233 | stackTrace.toString();
234 | dateTime.toLocal();
235 | ```
236 | #### [PREFER] 改变对象类型使用`as_()`格式命名
237 | >转换函数提供的是“快照功能”。返回的对象有自己的数据副本,修改原来对象的数据不会改变 返回的对象中的数据。另外一种函数返回的是同一份数据的另外一种 表现形式,返回的是一个新的对象,但是其内部引用的数据和原来对象引用的数据一样。 修改原来对象中的数据,新返回的对象中的数据也一起被修改。
238 |
239 | ```dart
240 | // good
241 | var map = table.asMap();
242 | var list = bytes.asFloat32List();
243 | var future = subscription.asFuture();
244 | ```
245 | #### [AVOID] 不要在函数方法名中出现参数名
246 | >在调用代码的时候可以看到参数,所以无需再次显示参数了。
247 | ```dart
248 | // good
249 | list.add(element);
250 | map.remove(key);
251 |
252 | // bad
253 | list.addElement(element)
254 | map.removeKey(key)
255 | ```
256 | >但是,对于具有多个类似的函数的时候,使用参数名字可以消除歧义, 这个时候应该带有参数名字。
257 | ```dart
258 | // good
259 | map.containsKey(key);
260 | map.containsValue(value);
261 | ```
262 | ### 库
263 | >下划线 ( _ ) 表明这个成员只能在库内部访问,是库私有成员。 Dart 工具确保该规则生效。
264 |
265 | #### [PREFER] 声明为私有
266 | >库中的公开声明—顶级定义或者在类中定义—是一种信号, 表示其他库可以并应该访问这些成员。 同时公开声明也是一种你的库需要实现的契约,当 使用这些成员的时候,应该实现其宣称的功能。
267 |
268 | >如果某个成员你不希望公开,则在成员名字之前添加一个`_`即可。 减少公开的接口让你的库更容易维护,也让用户更加容易掌握你的库如何使用。
269 |
270 | >另外,分析工具还可以分析出没有用到的私有成员定义,然后 告诉你可以删除这些无用的代码。 私有成员第三方代码无法调用而你自己在库中也没有使用,所以是无用的代码。
271 | ```dart
272 | // good
273 | class IterableBase {}
274 | class List {}
275 | class HashSet {}
276 | class RedBlackTree {}
277 | ```
278 |
279 | ```dart
280 | // good
281 | class Map {}
282 | class Multimap {}
283 | class MapEntry {}
284 | ```
285 |
286 | ```dart
287 | // good
288 | abstract class ExpressionVisitor {
289 | R visitBinary(BinaryExpression node);
290 | R visitLiteral(LiteralExpression node);
291 | R visitUnary(UnaryExpression node);
292 | }
293 | ```
294 |
295 | ```dart
296 | // good
297 | class Future {
298 | Future then(FutureOr onValue(T value)) => ...
299 | }
300 | ```
301 |
302 | ```dart
303 | // good
304 | class Graph {
305 | final List nodes = [];
306 | final List edges = [];
307 | }
308 |
309 | class Graph {
310 | final List nodes = [];
311 | final List edges = [];
312 | }
313 | ```
314 |
315 | ```dart
316 | // good
317 | typedef Predicate = bool Function(E element);
318 |
319 | // bad
320 | abstract class Predicate {
321 | bool test(E element);
322 | }
323 | ```
324 |
325 | ```dart
326 | // good
327 | DateTime mostRecent(List dates) {
328 | return dates.reduce((a, b) => a.isAfter(b) ? a : b);
329 | }
330 |
331 | const _favoriteMammal = 'weasel';
332 |
333 | // bad
334 | class DateUtils {
335 | static DateTime mostRecent(List dates) {
336 | return dates.reduce((a, b) => a.isAfter(b) ? a : b);
337 | }
338 | }
339 |
340 | class _Favorites {
341 | static const mammal = 'weasel';
342 | }
343 | ```
344 |
345 | ```dart
346 | // good
347 | class Color {
348 | static const red = '#f00';
349 | static const green = '#0f0';
350 | static const blue = '#00f';
351 | static const black = '#000';
352 | static const white = '#fff';
353 | }
354 | ```
355 |
356 | ```dart
357 | // good
358 | class Point {
359 | num x, y;
360 | Point(this.x, this.y);
361 | Point.polar(num theta, num radius)
362 | : x = radius * cos(theta),
363 | y = radius * sin(theta);
364 | }
365 |
366 | // bad
367 | class Point {
368 | num x, y;
369 | Point(this.x, this.y);
370 | static Point polar(num theta, num radius) =>
371 | Point(radius * cos(theta), radius * sin(theta));
372 | }
373 | ```
374 |
375 | ```dart
376 | // bad
377 | connection.nextIncomingMessage; // Does network I/O.
378 | expression.normalForm; // Could be exponential to calculate.
379 | ```
380 |
381 | ```dart
382 | // bad
383 | stdout.newline; // Produces output.
384 | list.clear; // Modifies object.
385 | ```
386 |
387 | ```dart
388 | // bad
389 | DateTime.now; // New result each time.
390 | ```
391 |
392 | ```dart
393 | // good
394 | rectangle.area;
395 | collection.isEmpty;
396 | button.canShow;
397 | dataSet.minimumValue;
398 | ```
399 |
400 | ```dart
401 | // good
402 | rectangle.width = 3;
403 | button.visible = false;
404 | ```
405 |
406 | ```dart
407 | // good
408 | var buffer = StringBuffer()
409 | ..write('one')
410 | ..write('two')
411 | ..write('three');
412 |
413 | // bad
414 | var buffer = StringBuffer()
415 | .write('one')
416 | .write('two')
417 | .write('three');
418 | ```
419 |
420 | ```dart
421 | bool isEmpty(String parameter) {
422 | bool result = parameter.length == 0;
423 | return result;
424 | }
425 | ```
426 |
427 | ```dart
428 | var lists = [1, 2];
429 | lists.addAll(List.filled(3, 4));
430 | lists.cast();
431 | ```
432 |
433 | ```dart
434 | List ints = [1, 2];
435 | ```
436 |
437 | ```dart
438 | install(id, destination) => ...
439 | ```
440 | ```dart
441 | Future install(PackageId id, String destination) => ...
442 | ```
443 | ```dart
444 | const screenWidth = 640; // Inferred as int.
445 | ```
446 |
447 | ```dart
448 | // good
449 | List> possibleDesserts(Set pantry) {
450 | var desserts = >[];
451 | for (var recipe in cookbook) {
452 | if (pantry.containsAll(recipe)) {
453 | desserts.add(recipe);
454 | }
455 | }
456 | return desserts;
457 | }
458 |
459 | // bad
460 | List> possibleDesserts(Set pantry) {
461 | List> desserts = >[];
462 | for (List recipe in cookbook) {
463 | if (pantry.containsAll(recipe)) {
464 | desserts.add(recipe);
465 | }
466 | }
467 | return desserts;
468 | }
469 | ```
470 | ```dart
471 | // good
472 | List parameters;
473 | if (node is Constructor) {
474 | parameters = node.signature;
475 | } else if (node is Method) {
476 | parameters = node.parameters;
477 | }
478 | ```
479 | ```dart
480 | // good
481 | var names = people.map((person) => person.name);
482 |
483 | // bad
484 | var names = people.map((Person person) => person.name);
485 | ```
486 | ```dart
487 | // good
488 | Set things = Set();
489 |
490 | // bad
491 | Set things = Set();
492 | ```
493 | ```dart
494 | // good
495 | var things = Set();
496 |
497 | // bad
498 | var things = Set();
499 | ```
500 | ```dart
501 | // good
502 | num highScore(List scores) {
503 | num highest = 0;
504 | for (var score in scores) {
505 | if (score > highest) highest = score;
506 | }
507 | return highest;
508 | }
509 |
510 | // bad
511 | num highScore(List scores) {
512 | var highest = 0;
513 | for (var score in scores) {
514 | if (score > highest) highest = score;
515 | }
516 | return highest;
517 | }
518 | ```
519 | ```dart
520 | // good
521 | dynamic mergeJson(dynamic original, dynamic changes) => ...
522 |
523 | // bad
524 | mergeJson(original, changes) => ...
525 | ```
526 |
527 | ```dart
528 | // good
529 | bool isValid(String value, bool Function(String) test) => ...
530 |
531 | // bad
532 | bool isValid(String value, Function test) => ...
533 | ```
534 |
535 | ```dart
536 | // good
537 | void handleError(void Function() operation, Function errorHandler) {
538 | try {
539 | operation();
540 | } catch (err, stack) {
541 | if (errorHandler is Function(Object)) {
542 | errorHandler(err);
543 | } else if (errorHandler is Function(Object, StackTrace)) {
544 | errorHandler(err, stack);
545 | } else {
546 | throw ArgumentError("errorHandler has wrong signature.");
547 | }
548 | }
549 | }
550 | ```
551 |
552 | ```dart
553 | // good
554 | set foo(Foo value) { ... }
555 |
556 | // bad
557 | void set foo(Foo value) { ... }
558 | ```
559 |
560 | ```dart
561 | // bad
562 | typedef int Comparison(T a, T b);
563 | ```
564 |
565 | ```dart
566 | // bad
567 | typedef bool TestNumber(num);
568 | ```
569 |
570 | ```dart
571 | // good
572 | typedef Comparison = int Function(T, T);
573 | ```
574 |
575 | ```dart
576 | // good
577 | typedef Comparison = int Function(T a, T b);
578 | ```
579 |
580 | ```dart
581 | // good
582 | class FilteredObservable {
583 | final bool Function(Event) _predicate;
584 | final List _observers;
585 |
586 | FilteredObservable(this._predicate, this._observers);
587 |
588 | void Function(Event) notify(Event event) {
589 | if (!_predicate(event)) return null;
590 |
591 | void Function(Event) last;
592 | for (var observer in _observers) {
593 | observer(event);
594 | last = observer;
595 | }
596 |
597 | return last;
598 | }
599 | }
600 | ```
601 |
602 | ```dart
603 | Iterable where(bool predicate(T element)) => ...
604 | ```
605 |
606 | ```dart
607 | // good
608 | Iterable where(bool Function(T) predicate) => ...
609 | ```
610 |
611 | ```dart
612 | // good
613 | void log(Object object) {
614 | print(object.toString());
615 | }
616 |
617 | /// Returns a Boolean representation for [arg], which must
618 | /// be a String or bool.
619 | bool convertToBool(dynamic arg) {
620 | if (arg is bool) return arg;
621 | if (arg is String) return arg == 'true';
622 | throw ArgumentError('Cannot convert $arg to a bool.');
623 | }
624 | ```
625 |
626 | ```dart
627 | // good
628 | Future triple(FutureOr value) async => (await value) * 3;
629 |
630 | // bad
631 | FutureOr triple(FutureOr value) {
632 | if (value is int) return value * 3;
633 | return (value as Future).then((v) => v * 3);
634 | }
635 | ```
636 |
637 | ```dart
638 | // good
639 | Stream asyncMap(
640 | Iterable iterable, FutureOr Function(T) callback) async* {
641 | for (var element in iterable) {
642 | yield await callback(element);
643 | }
644 | }
645 | ```
646 | ### 参数
647 | >在 Dart 中可选参数可以为命名参数或者位置参数,但是不能同时有这两种类型的参数为可选参数。
648 |
649 | #### [AVOID] 避免布尔值类型位置参数
650 |
651 | >和其他类型不一样的是,布尔值通常使用字面量形式。 其他成员通常都放到一个命名的常量中,但是布尔值我们通常都直接使用 true 和 false 。如果起名不清晰的话,在使用布尔值调用的时候 代码看起来可能非常难懂:
652 | ```dart
653 | // bad
654 | new Task(true);
655 | new Task(false);
656 | new ListBox(false, true, true);
657 | new Button(false);
658 | ```
659 | >考虑使用命名参数或者命名构造函数以及命名常量来清晰 的表明您的意图:
660 | ```dart
661 | // good
662 | Task.oneShot();
663 | Task.repeating();
664 | ListBox(scroll: true, showScrollbars: true);
665 | Button(ButtonState.enabled);
666 | ```
667 | >注意,对于 setter 则没有这个要求,应为 setter 的名字已经明确的 表明了值所代表的意义。
668 | ```dart
669 | // good
670 | listBox.canScroll = true;
671 | button.isEnabled = false;
672 | ```
673 |
674 | #### [AVOID] 如果你想省略一些参数请请避免使用位置参数
675 | >位置可选参数应该把经常使用的参数放到参数列表前面。 如果位置排列的不合理,则用户使用起来将很 麻烦。 对于拿不准的排序,请使用命名参数。
676 | ```dart
677 | // good
678 | String.fromCharCodes(Iterable charCodes, [int start = 0, int end]);
679 |
680 | DateTime(int year,
681 | [int month = 1,
682 | int day = 1,
683 | int hour = 0,
684 | int minute = 0,
685 | int second = 0,
686 | int millisecond = 0,
687 | int microsecond = 0]);
688 |
689 | Duration(
690 | {int days = 0,
691 | int hours = 0,
692 | int minutes = 0,
693 | int seconds = 0,
694 | int milliseconds = 0,
695 | int microseconds = 0});
696 | ```
697 | #### [AVOID] [避免强制性参数,当参数可以省略时
698 | >如果用户可以省略一个参数调用函数,推荐让该参数为可选参数而不是强迫用户 使用 null 来作为参数。空字符串 等类似 的情况也适用这种情况。
699 |
700 | >省略参数看起来更加简洁, 有助于 防止 bug。
701 | ```dart
702 | // good
703 | var rest = string.substring(start);
704 |
705 | // bad
706 | var rest = string.substring(start, null);
707 | ```
708 | #### [DO] 获取范围时将参数设置为左闭右开
709 | > 如果你定义一个函数或者方法让用户从基于位置排序的集合中 选择一些元素,需要一个开始位置索引和结束位置索引分别制定开始 元素的位置以及结束元素的位置。结束位置通常是指 大于最后一个元素的位置的值。
710 |
711 | >核心库就是这样定义的,所以最好和核心库保持一致。
712 | ```dart
713 | // good
714 | [0, 1, 2, 3].sublist(1, 3) // [1, 2]
715 | 'abcd'.substring(1, 3) // 'bc'
716 | ```
717 | >这种类型的参数保持一致是非常重要的,由于这种参数通常是位置参数, 如果你的函数第二个参数所代表的意义为获取元素的个数而不是结束的位置, 在调用的时候用户没法通过代码查看其区别。
718 |
719 | ### 相等判断
720 | > 为类实现自定义的相等判断可能比较麻烦。关于两个对象是否相等, 用户有根深蒂固的直观感受,并且基于哈希的集合要求 里面的对象满足一些微妙 的协议。
721 |
722 | #### [DO] 如果你重载`==`请一并重载`hashcode`
723 | >默认的哈希函数实现了恒等式哈希—两个对象 只有当其是同一个对象的时候哈希值才一样。 否则的话,默认的 == 的行为不满足恒等式要求。
724 | 如果你覆写了`==`,则表明你的对象可能和其他对象相等。 任何相等的两个对象都必须具有同样的哈希值。 否则的话,map 和其他基于哈希的集合将不知道这两个对象是相等的。
725 |
726 | #### [DO] 请让你的`==`运算符遵守数学上的相等
727 | >等价关系应该是这样的:
728 |
729 | - 自反: `a == a` 应该总是 `true`.
730 | - 对称: `a == b` 应该和 `b == a` 是一样的结果。
731 | - 传递: 如果 `a == b` 和 `b == c` 都返回 true,则 a == c 也应该为 true。
732 | >用户以及使用 == 的代码都期望遵守上面的规则。 如果你的类无法满足这些要求,则 == 就不是你想 表达的函数的正确名字。
733 |
734 | #### [AVOID] 避免为可变类定义常规意义上的相等
735 | >如果你定义了 == ,则你还应该定义 hashCode 函数。 这两个函数都应该考虑对象的变量。如果这些变量发生了变化,则 表明该对象的哈希值也应该变化。
736 | 大部分基于哈希的集合并不这样认为—这些集合 认为对象的哈希值应该一直不变,如果不是这样的话,这些集合 可能出现怪异的行为。
737 |
738 | #### [DON'T]不需要在重载`==`运算符时判断类型是否为`null`
739 | > 语言规范表明了这种判断已经自动执行了,你的 == 自定义操作符只有当 右侧对象不为 null 的时候才会执行。
740 | ```dart
741 | // good
742 | class Person {
743 | final String name;
744 | // ···
745 | operator ==(other) => other is Person && name == other.name;
746 |
747 | int get hashCode => name.hashCode;
748 | }
749 |
750 | // bad
751 | class Person {
752 | final String name;
753 | // ···
754 | operator ==(other) => other != null && ...
755 | }
756 | ```
757 | **[⬆ back to top](#top)**
--------------------------------------------------------------------------------
/usage.md:
--------------------------------------------------------------------------------
1 |
2 | ## 🔥 Effective Dart: Usage 🔥
3 |
4 | - 库
5 | - [DO] [`part of`指令之后使用字符串](#1-1)
6 | - [DON'T] [不要在你的库的src目录下引入其他库](#1-2)
7 | - [PREFER] [库lib目录下的引入请使用相对路径](#1-3)
8 | - 字符串
9 | - [DO] [使用`adjacent strings`串联字符串而不是使用`+`号](#2-1)
10 | - [PREFER] [使用模板字符串来拼接值和字符而不是用`+`拼接](#2-2)
11 | - [AVOID] [在不需要使用大括号时省略大括号](#2-3)
12 | - 集合
13 | - [DO] [尽量使用字面量定义集合](#3-1)
14 | - [DON'T] [不要使用`.length`去判断集合是否为空](#3-2)
15 | - [CONSIDER] [使用高阶函数对集合进行转换处理](#3-3)
16 | - [AVOID] [避免在`Iterable.forEach()`里写函数](#3-4)
17 | - [DON'T] [不要使用`List.from()`除非你想转换集合类型](#3-5)
18 | - [DO] [使用`whereType()`去过滤集合类型](#3-6)
19 | - [DON'T] [当其他操作符可以转换类型时不要使用`cast()`](#3-7)
20 | - [AVOID] [避免使用`cast()`](#3-8)
21 | - 函数
22 | - [DO] [直接声明函数而不是将lambda函数赋值给一个变量](#4-1)
23 | - [DON'T] [`lambda`表达式尽量简洁](#4-2)
24 | - 参数
25 | - [DO] [使用`=`为吗命名参数设置默认值](#5-1)
26 | - [DON'T] [不要将默认值显式设置为`null`](#5-2)
27 | - 变量
28 | - [DON'T] [不要将初始化变量显式设置为`null`](#6-1)
29 | - [AVOID] [避免存储你可以计算的值](#6-2)
30 | - 成员
31 | - [DON'T] [不要在不必要的时候设置`getter`和`setter`](#7-1)
32 | - [PREFER] [使用`final`声明一个只读属性](#7-2)
33 | - [CONSIDER] [对于一个简单属性的获取使用`=>`](#7-3)
34 | - [DON'T] [不要在不必要的时候使用`this`](#7-4)
35 | - [DO] [尽量在初始值声明时赋值初始值](#7-5)
36 | - 构造函数
37 | - [DO] [尽量使用简洁的构造函数声明方式](#8-1)
38 | - [DON'T] [不要为构造函数参数声明类型](#8-2)
39 | - [DO] [构造函数body为空时使用`;`而不是`{}`](#8-3)
40 | - [DON'T] [不要使用`new`关键字声明实例](#8-4)
41 | - [DON'T] [不要重复冗余的声明`const`](#8-5)
42 | - 错误处理
43 | - [AVOID] [避免在没有条件控制下捕捉错误](#9-1)
44 | - [DON'T] [不要忽略错误](#9-2)
45 | - [DO] [仅仅在语法错误的情况下抛出实现`Error`的类](#9-3)
46 | - [DON'T] [开发时不要对错误做处理,let's crash](#9-4)
47 | - [DO] [使用`rethrow`关键词重新抛出无法处理的异常](#9-5)
48 | - 异步
49 | - [PREFER] [使用`async/await`优于传统的`Future`](#10-1)
50 | - [DON'T] [不要在`async`没有任何作用时使用它](#10-2)
51 | - [CONSIDER] [使用高阶函数处理转换流`stream`](#10-3)
52 | - [AVOID] [避免直接使用`Completer`类](#10-4)
53 | - [DO] [当参数声明类型为`Future`时候,参数可能为`Object`的情况下请用`Future`做类型判断](#10-5)
54 | ### 库
55 | > 下面的建议可以帮助你以一致的可维护的方式在多个文件中编写程序
56 |
57 |
58 | #### [DO] `part of`指令之后使用字符串
59 | > 许多dart开发者完全不使用`part`,因为他们发现当他们的库源文件是单文件时很容易读懂整个代码。如果你选择使用`part`来拆分你的库文件,Dart要求其他文件需要使用`part of`显式声明所属库。因为遗留原因Dart允许`part of`参数为库名,这让工具很难识别库的主文件,并且容易产生歧义。
60 | > 更建议的是使用URI字符串的方式声明库主文件,就像你在其他诸如`import`指令一样,下面是一个例子:
61 |
62 | ```dart
63 | // good
64 | library my_library;
65 | part "some/other/file.dart";
66 |
67 | // good
68 | // your part file
69 | part of "../../my_library.dart";
70 |
71 | //bad
72 | part of my_library;
73 | ```
74 |
75 | #### [DON'T] 不要引入第三方库`src`目录下的文件
76 | > `lib`目录下的`src`目录所包含的源代码对于库来说是私有实现,包维护者对其包版本应该考虑这种约定,私有实现可以随意更改而不会对包产生破坏性更新。
77 |
78 | > 这意味着如果你引入了其他包的私有库/文件,非破坏性的更新也会破坏你的代码。
79 |
80 |
81 | #### [DO] 库lib目录下的引入请使用相对路径
82 | ```markdown
83 | my_package
84 | └─ lib
85 | ├─src
86 | | └─ utils.dart
87 | └─api.dart
88 | ```
89 | 如果api.dart想要导入utils.dart,那么应该这么做:
90 | ```dart
91 | // good
92 | import 'src/utils.dart';
93 |
94 | // bad
95 | import 'package:my_package/src/utils.dart'
96 | ```
97 | > 其实并没有很特别的理由选择前者,主要是前者描述短一点并且我们希望保持一致
98 |
99 | **[⬆ back to top](#top)**
100 |
101 | ## 字符串
102 | > 下面是一些Dart语言中处理字符串的最佳实践
103 |
104 |
105 | ##### [DO] 使用`adjacent strings`串联字符串而不是使用`+`号
106 | > Dart中你可以使用如下的方式(相邻字符串)串联字符串,这种方式可以很容易将一个超长字符串分割多行且不用一直用`+`
107 | ```dart
108 | // good
109 | show(
110 | 'what happend in the dartlang world'
111 | 'and what can we do now ?');
112 |
113 | // bad
114 |
115 | show('what happend in the dartlang world'+
116 | 'and what can we do now ?');
117 | ```
118 |
119 | ##### [PREFER] 使用模板字符串来拼接值和字符而不是用+拼接
120 | > 如果你有ES6使用经验,相信你一定不会对模板字符串感到陌生,Dart也提供相同的功能
121 |
122 | ```dart
123 | // good
124 | 'Hello , $name ! you are ${year - birth} years old';
125 |
126 | // bad
127 | 'Hello ,'+name+' you are '+(year - birth).toString()+' years old';
128 | ```
129 |
130 | ##### [AVOID] 在不需要使用大括号时省略大括号
131 | ```dart
132 | // goods
133 | 'Hi , $name'
134 | 'Wear your wildest $decade's outfit'
135 |
136 | // bad
137 | 'Hi, ${name}'
138 | "Wear your wildest ${decade}`s outfit"
139 | ```
140 | **[⬆ back to top](#top)**
141 |
142 | ## 集合
143 | > Dart提供开箱即用的集合类型:Maps,Sets,lists and queues,下面是一些最佳实践。
144 |
145 |
146 | ##### [DO] 尽量使用字面量定义集合
147 | > 有两种方式定义一个空数组:`[]`和`List()`,同样的有三种方式定义Linked HashMap:`{}`,`Map()`和`LinkedHashMap()`
148 | > 如果你想生成固定长度集合或者一些自定义类型集合请使用构造器,其他情况使用字面量语法。
149 |
150 | ```dart
151 | // good
152 | var points = [];
153 | var addresses = {};
154 |
155 | // bad
156 | var points = List();
157 | var addresses = Map();
158 | ```
159 | > 必要时你可以申明集合类型
160 | ```dart
161 | // good
162 | var points = [];
163 | var addresses = {};
164 |
165 | // bad
166 | var points = List();
167 | var addresses = Map();
168 | ```
169 | > 注意这些建议不适用于这些类的命名构造函数`List.from()`,`Map.fromIterable()`,这些方法有他们自己的用途。例如如果你想使用`List()`创建已知内容的集合
170 | 你可以使用他们
171 |
172 |
173 | ##### [DON'T] 不要使.length去判断集合是否为空
174 | > 相比于使用`.length`去判定一个集合是否为空,更建议使用阅读性更强的`.isEmpty`和`.isNotEmpty`。
175 | ```dart
176 | // good
177 | if ( list.isEmpty ) return 'this is a empty list';
178 | if ( array.isNotEmpty ) return 'wooo, a non-empty array';
179 |
180 | // bad
181 | if( list.length == 0 ) return 'this is a empty list';
182 | if( !array.isEmpty ) return 'wooo, a non-empty array';
183 | ```
184 |
185 |
186 | ##### [CONSIDER]使用高级函数去转换序列,也就是我们常说的函数式的写法
187 | > 如果你想转换集合生成新集合,请使用诸如`.map()`,`.where()`等基于`Iterable`的函数
188 | > 如果使用`for loop`方式会显得冗余并且容易产生副作用
189 | ```dart
190 | // good
191 | var coolBoy = Boys
192 | .where((boy) => boy.isRich)
193 | .where((boy) => boy.isTall)
194 | .map((boy) => boy.name);
195 | ```
196 |
197 |
198 | ##### [AVOID] 避免在Iterable.forEach()里写函数
199 | > `forEach()`函数在JS中应用广泛,不过在Dart中想要遍历一个对象惯用的方法是使用`for-in`的方式
200 | ```dart
201 | // good
202 | for ( var i in people ) {
203 | // your function here
204 | }
205 |
206 | // bad
207 | people.forEach((i) {
208 | // your function here
209 | });
210 | ```
211 | > 有一种情况例外那就是当我们的处理函数已存在(无需再次申明),并可以接受元素作为参数
212 | ```dart
213 | // good
214 | people.forEach(print);
215 | ```
216 |
217 | ##### [DON'T] 不要使用List.from()除非你想改变集合的类型
218 | > 给你一个`Iterable`,这里有两种方式生成新的`List`(包含一样的子元素)
219 | ```dart
220 | var copy1 = iterable.toList();
221 | var copy2 = List.from(iterable);
222 | ```
223 | > 上面两种方式明显的区别是第一种方式简短一点,重要的不同之处是第一种会保留参数类型
224 | ```dart
225 | // good
226 |
227 | // Creates a List
228 | var iterable = [1,2,3]
229 | // Prints "List"
230 | print(iterable.toList().runtimeType);
231 |
232 | // bad
233 |
234 | // Creates a List
235 | var iterable = [1, 2, 3];
236 | // Prints "List":
237 | print(List.from(iterable).runtimeType);
238 | ```
239 | > 如果你想改变类型,使用List.from()是很有用的
240 | ```dart
241 | var numbers = [1, 2.3, 4]; // List.
242 | numbers.removeAt(1); // Now it only contains integers.
243 | var ints = List.from(numbers); // List
244 | ```
245 |
246 | ##### [DO]使用whereType()去过滤集合类型(Dart 2.X only)
247 | > 如果你的集合包含多种类型,你只想获取int类型,你可以使用`.where()`
248 | ```dart
249 | // bad
250 | var objs = [1, '2', 3, '4'];
251 | var ints = objects.where((e) => e is int);
252 | ```
253 | > 有时候返回的类型可能不是你想要的,你会使用`.cast()`转换类型
254 | ```dart
255 | // bad
256 | var objs = [1, '2', 3, '4'];
257 | var ints = objs.where((e) => e is int).cast();
258 | ```
259 | > 上面的方式虽然解决了问题,却使用了两层处理产生了冗余的运行时判断,幸运的是Dart核心库现在提供了
260 | `whereType()`方法解决这个问题。
261 | ```dart
262 | // good
263 | var objs = [1, '2', 3, '4'];
264 | var ints = objs.whereType();
265 | ```
266 | > 使用`whereType()`很简洁,可以生成自己想要的类型而不用多做一层处理
267 |
268 |
269 | ##### [DON'T] 当其他操作符可以转换类型时不要使用`cast()`
270 | > 我们在处理`iterable`或者`stream`时经常需要做类型转换,经理不要使用`cast()`做类型转换
271 | ```dart
272 | // good
273 | var stuff = [1,2];
274 | var ints = List.from(stuff)
275 |
276 | // bad
277 | var stuff = [1,2];
278 | var ints = stuff.toList().cast();
279 | ```
280 | > 在使用`map()`等方法时也可以省略掉`cast()`的使用
281 | ```dart
282 | // good
283 | var stuff = [1,2];
284 | var re = stuff.map((n) => 1 / n);
285 |
286 | // bad
287 | var stuff = [1,2];
288 | var re = stuff.map((n) => 1 / n).cast();
289 | ```
290 |
291 | ##### [AVOID] 避免使用cast()
292 | > 避免使用`cast()`,用以下方式代替
293 | - *声明正确的类型* 在集合声明时就指定正确的类型
294 | - *在获取元素时转换类型* 如果你在遍历元素,在处理元素之前就使用`as`转换类型
295 | - *使用`List.from()`做转换* 如果你需要获取集合中的大多数元素,请使用`List.from()`
296 |
297 | > *声明正确的类型*的例子
298 | ```dart
299 | // good
300 | List singletonList(int value) {
301 | var list = [];
302 | list.add(value);
303 | return list;
304 | }
305 |
306 | //bad
307 | List singletonList(int value) {
308 | var list = [];
309 | list.add(value);
310 | return list.cast();
311 | }
312 | ```
313 | > *在获取元素时转换类型*的例子
314 | ```dart
315 | // good
316 | void printEvens(List