├── README.md └── coffeelint.json /README.md: -------------------------------------------------------------------------------- 1 | # CoffeeScript 编码风格指南 2 | 3 | Sponsor 4 | 5 | 这份指南阐述了一些 [CoffeeScript][coffeescript] 的最佳实践和编码惯例。 6 | 7 | 这份指南是社群驱动的,非常鼓励大家来贡献内容。 8 | 9 | 请注意这还是一份正在完善的指南:仍有很多地方可以改进,有些已制定的准则也不一定是社区惯用的(基于此,在适当的情况下,这些有待斟酌的准则将有可能被修改或删除。) 10 | 11 | ## 灵感 12 | 13 | 本指南中的很多细节受到了几份现有的风格指南和其他资源的启发。特别是: 14 | 15 | - [PEP-8][pep8]: Style Guide for Python Code 16 | - Bozhidar Batsov's [Ruby Style Guide][ruby-style-guide] 17 | - [Google's JavaScript Style Guide][google-js-styleguide] 18 | - [Common CoffeeScript Idioms][common-coffeescript-idioms] 19 | - Thomas Reynolds' [CoffeeScript-specific Style Guide][coffeescript-specific-style-guide] 20 | - Jeremy Ashkenas' [code review][spine-js-code-review] of [Spine][spine-js] 21 | - The [CoffeeScript FAQ][coffeescript-faq] 22 | 23 | ## 目录 24 | 25 | * [CoffeeScript 风格指南](#guide) 26 | * [代码布局(Code Layout)](#code_layout) 27 | * [Tab 还是 空格?(Tabs or Spaces?)](#tabs_or_spaces) 28 | * [最大行宽(Maximum Line Length)](#maximum_line_length) 29 | * [空行(Blank Lines)](#blank_lines) 30 | * [结尾空白(Trailing Whitespace)](#trailing_whitespace) 31 | * [可选的逗号(Optional Commas)](#optional_commas) 32 | * [编码(Encoding)](#encoding) 33 | * [模块导入(Module Imports)](#module_imports) 34 | * [表达式和语句中的空白(Whitespace in Expressions and Statements)](#whitespace) 35 | * [注释(Comments)](#comments) 36 | * [块注释(Block Comments)](#block_comments) 37 | * [行内注释(Inline Comments)](#inline_comments) 38 | * [命名规范(Naming Conventions)](#naming_conventions) 39 | * [函数(Functions)](#functions) 40 | * [字符串(Strings)](#strings) 41 | * [条件判断(Conditionals)](#conditionals) 42 | * [循环和列表解析(Looping and Comprehensions)](#looping_and_comprehensions) 43 | * [扩展本地对象(Extending Native Objects)](#extending_native_objects) 44 | * [异常(Exceptions)](#exceptions) 45 | * [注解(Annotations)](#annotations) 46 | * [其他(Miscellaneous)](#miscellaneous) 47 | 48 | 49 | ## 代码布局(Code Layout) 50 | 51 | 52 | ### Tab 还是 空格?(Tabs or Spaces?) 53 | 54 | 只用 **空格**,每级缩进均为 **2 个空格**。切勿混用 Tab 和空格。 55 | 56 | 57 | ### 最大行宽(Maximum Line Length) 58 | 59 | 限制每行最多 79 个字符。 60 | 61 | 62 | ### 空行(Blank Lines) 63 | 64 | 顶级函数和类的定义用一个空行分开。 65 | 66 | 类内部的函数定义也用一个空行分开。 67 | 68 | 对于每个函数体内,只在为了提高可读性的情况下才使用一个空行(例如:为了达到划分逻辑的目的)。 69 | 70 | 71 | ### 结尾空白(Trailing Whitespace) 72 | 73 | 不要在任何一行保留行尾空白。 74 | 75 | 76 | ### 可选的逗号(Optional Commas) 77 | 78 | 当对象(或数组)的属性(或元素)作为单独一行列出时,避免在换行符前使用逗号。如下: 79 | 80 | ```coffeescript 81 | # 好 82 | foo = [ 83 | 'some' 84 | 'string' 85 | 'values' 86 | ] 87 | bar: 88 | label: 'test' 89 | value: 87 90 | 91 | # 差 92 | foo = [ 93 | 'some', 94 | 'string', 95 | 'values' 96 | ] 97 | bar: 98 | label: 'test', 99 | value: 87 100 | ``` 101 | 102 | 103 | ### 编码(Encoding) 104 | 105 | UTF-8 是首选的源文件编码。 106 | 107 | 108 | ## 模块导入(Module Imports) 109 | 110 | 如果需要导入模块 (CommonJS 模块,AMD,等等.), `require` 语句应该单独作为一行。如下: 111 | 112 | ```coffeescript 113 | require 'lib/setup' 114 | Backbone = require 'backbone' 115 | ``` 116 | 117 | 这些语句应该按以下顺序去分组: 118 | 119 | 1. 标准库的导入 _(如果标准库存在)_ 120 | 2. 第三方库的导入 121 | 3. 本地导入 _(导入这个应用程序的或库的具体依赖)_ 122 | 123 | 124 | ## 表达式和语句中的空白(Whitespace in Expressions and Statements) 125 | 126 | 下列情况应该避免多余的空格: 127 | 128 | - 紧贴着圆括号、方括号和大括号内部 129 | 130 | ```coffeescript 131 | ($ 'body') # 好 132 | ( $ 'body' ) # 差 133 | ``` 134 | 135 | - 紧贴在逗号前 136 | 137 | ```coffeescript 138 | console.log x, y # 好 139 | console.log x , y # 差 140 | ``` 141 | 142 | 额外建议: 143 | 144 | - 在下列二元操作符的左右两边都保留 **一个空格** 145 | 146 | - 赋值运算符: `=` 147 | 148 | - _注意这同样适用于函数定义中的默认参数_ 149 | 150 | ```coffeescript 151 | test: (param = null) -> # 好 152 | test: (param=null) -> # 差 153 | ``` 154 | 155 | - 自增运算符: `+=`, `-=`, 等等。 156 | - 比较运算符: `==`, `<`, `>`, `<=`, `>=`, `unless`, 等等。 157 | - 算术运算符: `+`, `-`, `*`, `/`, 等等。 158 | 159 | - _(这些操作符两边的空格不要多于一个)_ 160 | 161 | ```coffeescript 162 | # 好 163 | x = 1 164 | y = 1 165 | fooBar = 3 166 | 167 | # 差 168 | x = 1 169 | y = 1 170 | fooBar = 3 171 | ``` 172 | 173 | 174 | ## 注释(Comments) 175 | 176 | 如果你修改了一段已有注释说明的代码,则也要更新它对应的注释。(理想状态是,重构这段代码直到它不需要注释说明,然后再把之前的注释全删掉。) 177 | 178 | 注释的首字母要大写,除非第一个单词是以小写字母开头的标识符。 179 | 180 | 如果注释很短,可以省略末尾的句号。 181 | 182 | 183 | ### 块注释(Block Comments) 184 | 185 | 注释块通常应用于尾随其后的一段代码。 186 | 187 | 每一行注释都以 `#` 加一个空格开头,而且和被注释的代码有相同的缩进层次。 188 | 189 | 注释块内的段落以仅含单个 `#` 的行分割。 190 | 191 | ```coffeescript 192 | # 这是一个块注释。请注意假如这是一段块注释, 193 | # 则它描述的就应该是接下来的这段代码。 194 | # 195 | # 这是块注释的第二段。 196 | # 请注意这段是由上一行带有 # 号的空行分开的。(P.S. 最好用英文写注释) 197 | 198 | init() 199 | start() 200 | stop() 201 | ``` 202 | 203 | 204 | ### 行内注释(Inline Comments) 205 | 206 | 行内注释紧贴在被描述的代码的上一行,如果行内注释足够短,则可以处在同一行行尾(由一个空格隔开)。 207 | 208 | 所有行内注释都以 `#` 加一个空格开头。 209 | 210 | 应该限制行内注释的使用,因为它们的存在通常是一个代码异味的标志。 211 | 212 | 不要给显而易见的情况作行内注释: 213 | 214 | ```coffeescript 215 | # 差 216 | x = x + 1 # x 自增 217 | ``` 218 | 219 | 然而,行内注释在某些情况下是有用的: 220 | 221 | ```coffeescript 222 | # 好 223 | x = x + 1 # 边界补足 224 | ``` 225 | 226 | 227 | ## 命名规范(Naming Conventions) 228 | 229 | 使用 `小驼峰命名法` (第一个词的首字母小写,后面每个词的首字母大写)来命名所有的变量、方法和对象属性。 230 | 231 | 使用 `大驼峰命名法` (第一个词的首字母,以及后面每个词的首字母都大写)来命名所有的类 _(在[其他类似的命名法][camel-case-variations]中,这种风格通常也被称为 `帕斯卡命名法(PascalCase)`、 `大写驼峰命名法(CamelCaps)` 或 `首字母大写命名法(CapWords)`。)_ 232 | 233 | _(CoffeeScript **官方** 约定是用驼峰命名法,因为这可以简化与 JavaScript 的相互转化,想了解更多,请看[这里][coffeescript-issue-425].)_ 234 | 235 | 对于常量,单词全部大写,用下划线隔开即可: 236 | 237 | ```coffeescript 238 | CONSTANT_LIKE_THIS 239 | ``` 240 | 241 | 私有函数和私有变量都应该在前面加一个下划线: 242 | 243 | ```coffeescript 244 | _privateMethod: -> 245 | ``` 246 | 247 | 248 | ## 函数(Functions) 249 | 250 | _(以下这些准则同样适用于类中的方法。)_ 251 | 252 | 当声明一个带参函数时,应在参数列表的右圆括号后空出一个空格: 253 | 254 | ```coffeescript 255 | foo = (arg1, arg2) -> # 好 256 | foo = (arg1, arg2)-> # 差 257 | ``` 258 | 259 | 无参函数不要用圆括号: 260 | 261 | ```coffeescript 262 | bar = -> # 好 263 | bar = () -> # 差 264 | ``` 265 | 266 | 当函数链式调用,却在一行放不下时,则把每个函数调用都另起一行,且都缩进一级(即在 `.` 前加两个空格)。 267 | 268 | ```coffeescript 269 | [1..3] 270 | .map((x) -> x * x) 271 | .concat([10..12]) 272 | .filter((x) -> x < 11) 273 | .reduce((x, y) -> x + y) 274 | ``` 275 | 276 | 当调用函数时,我们应该为了提高可读性而去掉圆括号。请记住,「可读性」是我们主观臆断的。只有类似下面几个例子的情况才被社区认为是最佳的: 277 | 278 | ```coffeescript 279 | baz 12 280 | 281 | brush.ellipse x: 10, y: 20 # 大括号在适当的时候也可以去掉 282 | 283 | foo(4).bar(8) 284 | 285 | obj.value(10, 20) / obj.value(20, 10) 286 | 287 | print inspect value 288 | 289 | new Tag(new Value(a, b), new Arg(c)) 290 | ``` 291 | 292 | 有时候你会发现圆括号用来包裹的是函数体(而不是函数的参数)。请看下面的例子(以下简称为「函数体风格」): 293 | 294 | ```coffeescript 295 | ($ '#selektor').addClass 'klass' 296 | 297 | (foo 4).bar 8 298 | ``` 299 | 300 | 这段代码会编译为: 301 | 302 | ```coffeescript 303 | $('#selektor').addClass 'klass' 304 | 305 | foo(4).bar 8 306 | ``` 307 | 308 | 一些习惯链式调用的人会巧用「函数体风格」进行单独初始化: 309 | 310 | ```coffeescript 311 | ($ '#selektor').addClass('klass').hide() # 单独初始化调用 312 | (($ '#selektor').addClass 'klass').hide() # 全部调用 313 | ``` 314 | 315 | 「函数体风格」并不得到推荐。但是, **当它适应一些特殊的项目需求时,还是得用它。** 316 | 317 | 318 | ## 字符串(Strings) 319 | 320 | 用字符串插值代替字符串连接符: 321 | 322 | ```coffeescript 323 | 'this is an #{adjective} string' # 好 324 | 'this is an ' + adjective + ' string' # 差 325 | ``` 326 | 327 | 最好用单引号 (`''`) 而不是双引号 (`""`) 。除非是插入到另一段现有的字符串中(类似字符串插值)。 328 | 329 | 330 | ## 条件判断(Conditionals) 331 | 332 | 用 `unless` 来代替 `if` 的否定情况。 333 | 334 | 不要用 `unless...else`, 而用 `if...else`: 335 | 336 | ```coffeescript 337 | # 好 338 | if true 339 | ... 340 | else 341 | ... 342 | 343 | # 差 344 | unless false 345 | ... 346 | else 347 | ... 348 | ``` 349 | 350 | 多行的 if/else 语句应该缩进: 351 | 352 | ```coffeescript 353 | # 好 354 | if true 355 | ... 356 | else 357 | ... 358 | 359 | # 差 360 | if true then ... 361 | else ... 362 | ``` 363 | 364 | 365 | ## 循环和列表解析(Looping and Comprehensions) 366 | 367 | 尽可能的使用列表解析: 368 | 369 | ```coffeescript 370 | # 好 371 | result = (item.name for item in array) 372 | 373 | # 差 374 | results = [] 375 | for item in array 376 | results.push item.name 377 | ``` 378 | 379 | 还可以过滤结果: 380 | 381 | ```coffeescript 382 | result = (item for item in array when item.name is "test") 383 | ``` 384 | 385 | 遍历对象的键值: 386 | 387 | ```coffeescript 388 | object = one: 1, two: 2 389 | alert("#{key} = #{value}") for key, value of object 390 | ``` 391 | 392 | 393 | ## 扩展本地对象(Extending Native Objects) 394 | 395 | 不要修改本地对象。 396 | 397 | 比如,不要给 `Array.prototype` 引入 `Array#forEach` 。 398 | 399 | 400 | ## 异常(Exceptions) 401 | 402 | 不要抑制异常抛出。 403 | 404 | 405 | ## 注解(Annotations) 406 | 407 | 必要的时候应该写注解,来指明接下来的代码块具体将干什么。 408 | 409 | 注解应紧贴在被描述代码的上一行。 410 | 411 | 注解关键字后面应该跟一个冒号加一个空格,加一个描述性的注释。 412 | 413 | ```coffeescript 414 | # FIXME: The client's current state should *not* affect payload processing. 415 | resetClientState() 416 | processPayload() 417 | ``` 418 | 419 | 如果注解不止一行,则下一行缩进两个空格。 420 | 421 | ```coffeescript 422 | # TODO: Ensure that the value returned by this call falls within a certain 423 | # range, or throw an exception. 424 | analyze() 425 | ``` 426 | 427 | 注解有以下几类: 428 | 429 | - `TODO`: 描述缺失的功能,以便日后加入 430 | - `FIXME`: 描述需要修复的代码 431 | - `OPTIMIZE`: 描述性能低下,或难以优化的代码 432 | - `HACK`: 描述一段值得质疑(或很巧妙)的代码 433 | - `REVIEW`: 描述需要确认其编码意图是否正确的代码 434 | 435 | 如果你必须自定义一个新的注解类型,则应该把这个注解类型记录在项目的 README 里面。 436 | 437 | 438 | ## 其他(Miscellaneous) 439 | 440 | `and` 更优于 `&&`. 441 | 442 | `or` 更优于 `||`. 443 | 444 | `is` 更优于 `==`. 445 | 446 | `not` 更优于 `!`. 447 | 448 | `or=` 应在可能的情况下使用: 449 | 450 | ```coffeescript 451 | temp or= {} # 好 452 | temp = temp || {} # 差 453 | ``` 454 | 455 | 最好用 (`::`) 访问对象的原型: 456 | 457 | ```coffeescript 458 | Array::slice # 好 459 | Array.prototype.slice # 差 460 | ``` 461 | 462 | 最好用 `@property` 而不是 `this.property`. 463 | 464 | ```coffeescript 465 | return @property # 好 466 | return this.property # 差 467 | ``` 468 | 469 | 但是,避免使用 **单独的** `@`: 470 | 471 | ```coffeescript 472 | return this # 好 473 | return @ # 差 474 | ``` 475 | 476 | 没有返回值的时候避免使用 `return` ,其他情况则需要显示 return 。 477 | 478 | 当函数需要接收可变数量的参数时,使用 splats (`...`)。 479 | 480 | ```coffeescript 481 | console.log args... # 好 482 | 483 | (a, b, c, rest...) -> # 好 484 | ``` 485 | 486 | [coffeescript]: http://jashkenas.github.com/coffee-script/ 487 | [coffeescript-issue-425]: https://github.com/jashkenas/coffee-script/issues/425 488 | [spine-js]: http://spinejs.com/ 489 | [spine-js-code-review]: https://gist.github.com/1005723 490 | [pep8]: http://www.python.org/dev/peps/pep-0008/ 491 | [ruby-style-guide]: https://github.com/bbatsov/ruby-style-guide 492 | [google-js-styleguide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml 493 | [common-coffeescript-idioms]: http://arcturo.github.com/library/coffeescript/04_idioms.html 494 | [coffeescript-specific-style-guide]: http://awardwinningfjords.com/2011/05/13/coffeescript-specific-style-guide.html 495 | [coffeescript-faq]: https://github.com/jashkenas/coffee-script/wiki/FAQ 496 | [camel-case-variations]: http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms 497 | 498 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "coffeescript_error": { 3 | "level": "error" 4 | }, 5 | "arrow_spacing": { 6 | "name": "arrow_spacing", 7 | "level": "warn" 8 | }, 9 | "no_tabs": { 10 | "name": "no_tabs", 11 | "level": "error" 12 | }, 13 | "no_trailing_whitespace": { 14 | "name": "no_trailing_whitespace", 15 | "level": "warn", 16 | "allowed_in_comments": false, 17 | "allowed_in_empty_lines": true 18 | }, 19 | "max_line_length": { 20 | "name": "max_line_length", 21 | "value": 80, 22 | "level": "warn", 23 | "limitComments": true 24 | }, 25 | "line_endings": { 26 | "name": "line_endings", 27 | "level": "ignore", 28 | "value": "unix" 29 | }, 30 | "no_trailing_semicolons": { 31 | "name": "no_trailing_semicolons", 32 | "level": "error" 33 | }, 34 | "indentation": { 35 | "name": "indentation", 36 | "value": 2, 37 | "level": "error" 38 | }, 39 | "camel_case_classes": { 40 | "name": "camel_case_classes", 41 | "level": "error" 42 | }, 43 | "colon_assignment_spacing": { 44 | "name": "colon_assignment_spacing", 45 | "level": "warn", 46 | "spacing": { 47 | "left": 0, 48 | "right": 1 49 | } 50 | }, 51 | "no_implicit_braces": { 52 | "name": "no_implicit_braces", 53 | "level": "ignore", 54 | "strict": true 55 | }, 56 | "no_plusplus": { 57 | "name": "no_plusplus", 58 | "level": "ignore" 59 | }, 60 | "no_throwing_strings": { 61 | "name": "no_throwing_strings", 62 | "level": "error" 63 | }, 64 | "no_backticks": { 65 | "name": "no_backticks", 66 | "level": "error" 67 | }, 68 | "no_implicit_parens": { 69 | "name": "no_implicit_parens", 70 | "level": "ignore" 71 | }, 72 | "no_empty_param_list": { 73 | "name": "no_empty_param_list", 74 | "level": "warn" 75 | }, 76 | "no_stand_alone_at": { 77 | "name": "no_stand_alone_at", 78 | "level": "ignore" 79 | }, 80 | "space_operators": { 81 | "name": "space_operators", 82 | "level": "warn" 83 | }, 84 | "duplicate_key": { 85 | "name": "duplicate_key", 86 | "level": "error" 87 | }, 88 | "empty_constructor_needs_parens": { 89 | "name": "empty_constructor_needs_parens", 90 | "level": "ignore" 91 | }, 92 | "cyclomatic_complexity": { 93 | "name": "cyclomatic_complexity", 94 | "value": 10, 95 | "level": "ignore" 96 | }, 97 | "newlines_after_classes": { 98 | "name": "newlines_after_classes", 99 | "value": 3, 100 | "level": "ignore" 101 | }, 102 | "no_unnecessary_fat_arrows": { 103 | "name": "no_unnecessary_fat_arrows", 104 | "level": "warn" 105 | }, 106 | "missing_fat_arrows": { 107 | "name": "missing_fat_arrows", 108 | "level": "ignore" 109 | }, 110 | "non_empty_constructor_needs_parens": { 111 | "name": "non_empty_constructor_needs_parens", 112 | "level": "ignore" 113 | } 114 | } 115 | --------------------------------------------------------------------------------