├── 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 objects) { 317 | for (var n in objects) { 318 | if((n as int).isEven) print(n); 319 | } 320 | } 321 | 322 | // bad 323 | void printEvens(List objects) { 324 | for (var n in objects.cast()) { 325 | if (n.isEven) print(n); 326 | } 327 | } 328 | ``` 329 | > *使用`List.from()`做转换*的例子 330 | ```dart 331 | // good 332 | int median(List objects) { 333 | var ints = List.from(objects); 334 | ints.sort(); 335 | return ints[ints.length ~/ 2]; 336 | } 337 | 338 | // bad 339 | int median(List objects) { 340 | var ints = objects.cast(); 341 | inst.sort(); 342 | return ints[ints.length ~/ 2]; 343 | } 344 | ``` 345 | > 有时候`cast()`也是正确选择,但是考虑到这个方法使用有一定风险-操作可能会很慢且有时候会在运行时失败,不建议使用 346 | 347 | **[⬆ back to top](#top)** 348 | 349 | ## 函数 350 | > 在Dart中函数也是对象(Object) 351 | 352 | 353 | ##### [DO]直接声明函数而不是将lambda函数赋值给一个变量 354 | > 现代语言都会提到嵌套函数和闭包的重要性,在一个函数中定义另一个函数是很常见的,在很多实例中这种类型的函数会被作为回调函数立即使用 355 | 且声明时不用命名。但是请直接声明函数而不是将lambda函数赋值给一个变量。 356 | ```dart 357 | // good 358 | void main() { 359 | localFunction() { 360 | // ... 361 | } 362 | } 363 | 364 | // bad 365 | void main() { 366 | var localFunction = () { 367 | ... 368 | }; 369 | } 370 | ``` 371 | 372 | ##### [DON'T] `lambda`表达式尽量简洁 373 | > 使用已有功能的函数作为closure,而不是再一次去重复实现该功能 374 | ```dart 375 | // good 376 | names.forEach(print); 377 | 378 | // bad 379 | names.forEach((name) { 380 | print(name); 381 | }) 382 | ``` 383 | **[⬆ back to top](#top)** 384 | ## 参数 385 | 386 | 387 | ##### [DO]使用=符号为命名参数设置默认值 388 | > 因为历史遗留原因,Dart允许`:`和`=`为命名参数,为了和可选位置参数保持一致,请使用`=` 389 | ```dart 390 | // good 391 | void insert(Object item, {int at = 0}) { ... } 392 | 393 | // bad 394 | void insert(Object item, {int at: 0}) { ... } 395 | ``` 396 | 397 | 398 | ##### [DON'T] 不要将默认值显式设置为`null` 399 | > 如果你创建了一个可选参数但是没有给予默认值,Dart会为你的参数设置默认值为`null`,所以你不用再做处理 400 | ```dart 401 | // good 402 | void error([String message]) { 403 | stderr.write(message ?? '\n'); 404 | } 405 | 406 | // bad 407 | void error([String messgae = null]) { 408 | stderr.write(messgae ?? '\n'); 409 | } 410 | ``` 411 | **[⬆ back to top](#top)** 412 | 413 | ## 变量 414 | > 下面是一些在Dart中如何使用变量的最佳实践 415 | 416 | 417 | ##### [DON'T] 不要将初始值设置为null 418 | > 在Dart中未赋值的变量都会被初始化为`null`,所以添加`= null`是多余不必要的。 419 | ```dart 420 | // good 421 | int _nextId; 422 | 423 | class LazyId { 424 | int _id; 425 | 426 | int get id { 427 | if (_nextId == null) _nextId = 0; 428 | if (_id == null) _id = _nextId++; 429 | 430 | return _id; 431 | } 432 | } 433 | 434 | // bad 435 | int _nextId = null; 436 | 437 | class LazyId { 438 | int _id = null; 439 | 440 | int get id { 441 | if (_nextId == null) _nextId = 0; 442 | if (_id == null) _id = _nextId++; 443 | 444 | return _id; 445 | } 446 | } 447 | ``` 448 | 449 | #### [AVOID] 不要存储你可以计算的变量 450 | >当设计一个类时,你可能经常会在初始化时计算所有的属性并存储它们 451 | 452 | ```dart 453 | // bad 454 | class Circle { 455 | num radius; 456 | num area; 457 | num circumference; 458 | 459 | Circle(num radius) 460 | : radius = radius, 461 | area = pi * radius * radius, 462 | circumference = pi * 2.0 * radius; 463 | } 464 | ``` 465 | > 上面的代码有两个糟糕的地方:首先这很消耗内存,更糟糕的是Circle类的`radius`是可变的,当改变`radius`值后我们获取的`area`和`circumference` 466 | 还是之前的计算值,这就会导致错误。为了保证准确性我们可能会像下面这样做: 467 | ```dart 468 | // bad 469 | class Circle { 470 | num _radius; 471 | num get radius => _radius; 472 | set radius(num value) { 473 | _radius = value; 474 | _recalculate(); 475 | } 476 | 477 | num _area; 478 | num get area => _area; 479 | 480 | num _circumference; 481 | num get circumference => _circumference; 482 | 483 | Circle(this._radius) { 484 | _recalculate(); 485 | } 486 | 487 | void _recalculate() { 488 | _area = pi * _radius * _radius; 489 | _circumference = pi * 2.0 * _radius; 490 | } 491 | } 492 | ``` 493 | > 上面的解决方法难以阅读,表达性极差,取而代之的实现方式应该如下: 494 | 495 | ```dart 496 | // good 497 | class Circle { 498 | num radius; 499 | 500 | Circle(this.radius); 501 | 502 | num get area => pi * radius * radius; 503 | num get circumference => pi * 2.0 * radius; 504 | } 505 | ``` 506 | > 这样代码就显得很简洁,更少的内存占用,更少的错误产生。它只存储了必要的数据。在一些案例里,你可能需要 507 | 存储一些运算量较大的值,请谨慎这么做并写上注释解释为什么需要这么做优化。 508 | 509 | **[⬆ back to top](#top)** 510 | 511 | ##成员 512 | > 在Dart中Object可以有函数(方法)和数据(实例变量)类型的成员,下面是一些最佳实践 513 | 514 | 515 | #### [DON'T] 不要在不必要的时候设置`getter`和`setter` 516 | > 在Java或者C#中,成员变量通常是隐藏在`getter`和`setter`之后的,你需要写很多get/set的样板代码。Dart没有这样的限制, 517 | 声明的变量会自动设置`getter`和`setter`。 518 | ```dart 519 | // good 520 | class Box { 521 | var contents; 522 | } 523 | 524 | // bad 525 | class Box { 526 | var _contents; 527 | get contents => _contents; 528 | set contents(value) { 529 | _contents = value; 530 | } 531 | } 532 | ``` 533 | 534 | #### [PREFER] 使用`final`声明一个只读属性 535 | > 如果你想设置一个只读属性变量,一个简单的解决办法是使用`final`标识变量 536 | ```dart 537 | // good 538 | class Box { 539 | final contents = []; 540 | } 541 | 542 | // bad 543 | class Box { 544 | var _contents; 545 | get contents => _contents; 546 | } 547 | ``` 548 | > 当然你或许需要在类的构造器外部去设置变量值,你可能需要`private field public getter`设计模式,这种情况下 549 | 第二种方法更合适你 550 | 551 | 552 | #### [CONSIDER] 对于一个简单属性的获取使用`=>` 553 | > 当计算表达式足够简单时使用`=>`,这种做法非常适合获取只需要简单计算的成员变量 554 | ```dart 555 | // good 556 | double get area => (right - left) * (bottom - top); 557 | 558 | bool isReady(num time) => minTime == null || minTime <= time; 559 | 560 | String capitalize(String name) => 561 | '${name[0].toUpperCase()}${name.substring(1)}'; 562 | ``` 563 | 564 | > 当我们的处理较为复杂的时候,不建议使用箭头函数这会让代码难以阅读。 565 | ```dart 566 | // good 567 | Treasure openChest(Chest chest, Point where) { 568 | if (_opened.containsKey(chest)) return null; 569 | 570 | var treasure = Treasure(where); 571 | treasure.addAll(chest.contents); 572 | _opened[chest] = treasure; 573 | return treasure; 574 | } 575 | 576 | // bad 577 | Treasure openChest(Chest chest, Point where) => 578 | _opened.containsKey(chest) ? null : _opened[chest] = Treasure(where) 579 | ..addAll(chest.contents); 580 | ``` 581 | > 你也可以在设置`setter`时使用`=>` 582 | ```dart 583 | // good 584 | num get x => center.x; 585 | set x(num value) => center = Point(value, center.y); 586 | ``` 587 | 588 | 589 | #### [DON'T] 不要在不必要的时候使用`this` 590 | > JS必须使用`this`来指向类去获取成员,不过Dart和Java C++等语言一样没有这种限制 591 | > 唯一需要使用`this`的情况是你在成员函数里需要获取成员变量的时候 592 | 593 | ```dart 594 | // good 595 | class Box { 596 | var value; 597 | 598 | void clear() { 599 | update(null); 600 | } 601 | 602 | void update(value) { 603 | this.value = value; 604 | } 605 | } 606 | 607 | // bad 608 | class Box { 609 | var value; 610 | 611 | void clear() { 612 | this.update(null); 613 | } 614 | 615 | void update(value) { 616 | this.value = value; 617 | } 618 | } 619 | ``` 620 | > 注意构造初始化时参数赋值是不需要`this`的 621 | ```dart 622 | // good 623 | class Box extends BaseBox { 624 | var value; 625 | 626 | Box(value) 627 | : value = value, 628 | super(value); 629 | } 630 | ``` 631 | > 这看起来很意外,但是这种语法是可以正常工作的。 632 | 633 | 634 | #### [DO] 尽量在初始值声明时赋值初始值 635 | > 如果成员变量不依赖构造函数,那么它可以且应该在声明时初始化,这会让代码更加简洁并且避免了在多构造函数情况下忘记初始化 636 | ```dart 637 | // good 638 | class Folder { 639 | final String name; 640 | final List contents = []; 641 | 642 | Folder(this.name); 643 | Folder.temp() : name = 'temporary'; 644 | } 645 | 646 | // bad 647 | class Folder { 648 | final String name; 649 | final List contents; 650 | 651 | Folder(this.name) : contents = []; 652 | Folder.temp() : name = 'temporary'; // Oops! Forgot contents. 653 | } 654 | ``` 655 | > 当然如果成员变量依赖构造函数参数,上面所说的就不适用了。 656 | 657 | **[⬆ back to top](#top)** 658 | 659 | ## 构造函数 660 | 661 | 662 | #### [DO] 尽量使用简洁的构造函数声明方式 663 | > 成员变量初始化可以直接利用构造参数 664 | ```dart 665 | // good 666 | class Point { 667 | num x, y; 668 | Point(this.x, this.y); 669 | } 670 | 671 | // bad 672 | class Point { 673 | num x, y; 674 | Point(num x, num y) { 675 | this.x = x; 676 | this.y = y; 677 | } 678 | } 679 | ``` 680 | 681 | 682 | ##### [DON'T] 不要为构造函数参数声明类型 683 | > 如果构造函数参数使用了`this`去初始化成员变量,参数类型会自动匹配成员变量类型 684 | ```dart 685 | // good 686 | class Point { 687 | int x, y; 688 | Point(this.x, this.y); 689 | } 690 | 691 | // bad 692 | class Point { 693 | int x, y; 694 | Point(int this.x, int this.y); 695 | } 696 | ``` 697 | 698 | #### [DO] 构造函数body为空时使用`;`而不是`{}` 699 | > 在Dart中,如果构造函数体为空请用冒号结尾 700 | ```dart 701 | // good 702 | class Point { 703 | int x, y; 704 | Point(this.x, this.y); 705 | } 706 | 707 | // bad 708 | class Point { 709 | int x, y; 710 | Point(this.x, this.y) {} 711 | } 712 | ``` 713 | 714 | 715 | #### [DON'T] 不要使用`new`关键字声明实例 716 | > Dart2让`new`关键词成为可选项,这样让代码更加简洁 717 | ```dart 718 | // good 719 | Widget build(BuildContext context) { 720 | return Row( 721 | children: [ 722 | RaisedButton( 723 | child: Text('Increment'), 724 | ), 725 | Text('Click!'), 726 | ], 727 | ); 728 | } 729 | 730 | // bad 731 | Widget build(BuildContext context) { 732 | return new Row( 733 | children: [ 734 | new RaisedButton( 735 | child: new Text('Increment'), 736 | ), 737 | new Text('Click!'), 738 | ], 739 | ); 740 | } 741 | ``` 742 | 743 | 744 | #### [DON'T] 不要重复冗余的声明`const` 745 | > 在状态为`const`的上下文环境中,`const`是隐式的可以省略 746 | - `const`状态的集合 747 | - `const`状态的构造函数 748 | - 元数据注解 749 | - `const`变量的初始化构造方法 750 | - `switch`表达式`case`和`:`之间的区域 751 | ```dart 752 | // good 753 | const primaryColors = [ 754 | Color("red", [255, 0, 0]), 755 | Color("green", [0, 255, 0]), 756 | Color("blue", [0, 0, 255]), 757 | ]; 758 | 759 | // bad 760 | const primaryColors = const [ 761 | const Color("red", const [255, 0, 0]), 762 | const Color("green", const [0, 255, 0]), 763 | const Color("blue", const [0, 0, 255]), 764 | ]; 765 | ``` 766 | **[⬆ back to top](#top)** 767 | ## 错误处理 768 | > Dart使用异常描述你的程序错误,下面是一些捕获和处理异常的最佳实践 769 | 770 | 771 | #### [AVOID] 避免在没有条件控制下捕捉错误 772 | > [TODO] 773 | 774 | 775 | #### [DON'T] 不要忽略错误 776 | > [TODO] 777 | 778 | 779 | #### [DO] 仅仅在语法错误的情况下抛出实现`Error`的类 780 | > [TODO] 781 | 782 | 783 | #### [DON'T] 开发时不要对错误做处理,let's crash 784 | > [TODO] 785 | 786 | 787 | #### [DO] 使用`rethrow`关键词重新抛出无法处理的异常 788 | > 当捕捉到的异常无法处理想要重新抛出异常时,请使用`rethrow`关键词而不是`throw`,因为`rethrow`会提供完整的异常调用栈。 789 | 而`throw`只提供抛出位置的调用栈。 790 | ```dart 791 | // good 792 | try { 793 | somethingRisky(); 794 | } catch (e) { 795 | if (!canHandle(e)) rethrow; 796 | handle(e); 797 | } 798 | 799 | // bad 800 | try { 801 | somethingRisky(); 802 | } catch (e) { 803 | if (!canHandle(e)) throw e; 804 | handle(e); 805 | } 806 | ``` 807 | **[⬆ back to top](#top)** 808 | #### 异步 809 | > Dart原生支持异步编程,一下是一些异步编程的最佳实践 810 | 811 | 812 | #### [PREFER] 使用`async/await`优于传统的`Future` 813 | > 异步代码是众所周知的难以调试,即使是使用了一些比较好的抽象例如`Future`。使用`async/await`语法可以提高代码的阅读性, 814 | 作用类似于JS中`async/await`之余`Promise`。 815 | 816 | ```dart 817 | // good 818 | Future countActivePlayers(String teamName) async { 819 | try { 820 | var team = await downloadTeam(teamName); 821 | if (team == null) return 0; 822 | 823 | var players = await team.roster; 824 | return players.where((player) => player.isActive).length; 825 | } catch (e) { 826 | log.error(e); 827 | return 0; 828 | } 829 | } 830 | 831 | // bad 832 | Future countActivePlayers(String teamName) { 833 | return downloadTeam(teamName).then((team) { 834 | if (team == null) return Future.value(0); 835 | 836 | return team.roster.then((players) { 837 | return players.where((player) => player.isActive).length; 838 | }); 839 | }).catchError((e) { 840 | log.error(e); 841 | return 0; 842 | }); 843 | } 844 | ``` 845 | 846 | 847 | #### [DON'T] 不要在`async`没有任何作用时使用它 848 | > 任何函数都可以用`async`标识使之拥有异步能力,不过不能滥用`async`,在不需要的时候不要使用它 849 | ```dart 850 | // good 851 | Future afterTwoThings(Future first, Future second) { 852 | return Future.wait([first, second]); 853 | } 854 | 855 | // bad 856 | Future afterTwoThings(Future first, Future second) async { 857 | return Future.wait([first, second]); 858 | } 859 | ``` 860 | > `async`在以下几种情况下是很有用的: 861 | - 你需要使用`await` 862 | - 你需要返回一个异步错误,`async`加`throw`要比`return Future.error(...)`简洁 863 | - 你需要返回值且值被隐式的包装为`Future`,`async`要比`Future.value(...)`简洁 864 | 865 | ```dart 866 | // good 867 | Future usesAwait(Future later) async { 868 | print(await later); 869 | } 870 | 871 | Future asyncError() async { 872 | throw 'Error!'; 873 | } 874 | 875 | Future asyncValue() async => 'value'; 876 | ``` 877 | 878 | 879 | #### [CONSIDER] 使用高阶函数处理转换流`stream` 880 | > 和对`Iterable`的处理建议一样,`stream`也提供很多同样的方法,并且可以准确处理传输失败,关闭等事件 881 | 882 | 883 | #### [AVOID] 避免直接使用`Completer`类 884 | > 许多异步编程新手想要创建`Future`,Future构造函数貌似不能满足他们的需求,他们最终使用`Cpmpleter`来完成任务。 885 | ```dart 886 | // bad 887 | Future fileContainsBear(String path) { 888 | var completer = Completer(); 889 | 890 | File(path).readAsString().then((contents) { 891 | completer.complete(contents.contains('bear')); 892 | }); 893 | 894 | return completer.future; 895 | } 896 | ``` 897 | > 相较于使用`Future.then()`或者`async/await`,Completer看起来更加复杂和难以处理 898 | ```dart 899 | // good 900 | Future fileContainsBear(String path) { 901 | return File(path).readAsString().then((contents) { 902 | return contents.contains('bear'); 903 | }); 904 | } 905 | ``` 906 | 907 | ```dart 908 | // good 909 | Future fileContainsBear(String path) async { 910 | var contents = await File(path).readAsString(); 911 | return contents.contains('bear'); 912 | } 913 | ``` 914 | 915 | 916 | #### [DO] 当参数声明类型为`FutureOr`时候,参数可能为`Object`的情况下请用`Future`做类型判断 917 | > [TODO] 918 | ```dart 919 | // good 920 | Future logValue(FutureOr value) async { 921 | if (value is Future) { 922 | var result = await value; 923 | print(result); 924 | return result; 925 | } else { 926 | print(value); 927 | return value as T; 928 | } 929 | } 930 | 931 | // bad 932 | Future logValue(FutureOr value) async { 933 | if (value is T) { 934 | print(value); 935 | return value; 936 | } else { 937 | var result = await value; 938 | print(result); 939 | return result; 940 | } 941 | } 942 | ``` 943 | **[⬆ back to top](#top)** --------------------------------------------------------------------------------