Best-practices and coding conventions for the CoffeeScript programming language
Switch branches/tags
Nothing to show
Clone or download
Pull request Compare This branch is 10 commits ahead, 18 commits behind polarmobile:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
README.md

README.md

CoffeeScript Style Guide

This guide presents a collection of best-practices and coding conventions for the CoffeeScript programming language.

This guide is intended to be community-driven, and contributions are highly encouraged.

Please note that this is a work-in-progress: there is much more that can be specified, and some of the guidelines that have been specified may not be deemed to be idiomatic by the community (in which case, these offending guidelines will be modified or removed, as appropriate).

Inspiration

The details in this guide have been very heavily inspired by several existing style guides and other resources. In particular:

Table of Contents

## Code layout ### Tabs or Spaces?

Use spaces only, with 4 spaces per indentation level. Never mix tabs and spaces.

### Maximum Line Length

Limit all lines to a maximum of 79 characters.

### Blank Lines

Separate top-level function and class definitions with two blank lines.

Separate method definitions inside of a class with a single blank line.

Use a single blank line within the bodies of methods or functions in cases where this improves readability (e.g., for the purpose of delineating logical sections).

### Trailing Whitespace

Do not include trailing whitespace on any lines.

### Encoding

UTF-8 is the preferred source file encoding.

## Module Imports

If using a module system (CommonJS Modules, AMD, etc.), require statements should be placed on separate lines.

require('lib/setup')
Backbone = require('backbone')

These statements should be grouped in the following order:

  1. Standard library imports (if a standard library exists)
  2. Third party library imports
  3. Local imports (imports specific to this application or library)
## Whitespace in Expressions and Statements

Avoid extraneous whitespace in the following situations:

  • Immediately inside parentheses, brackets or braces
$('body') # Yes
$( 'body' ) # No
  • Immediately before a comma
console.log(x, y) # Yes
console.log(x , y) # No

Additional recommendations:

  • Always surround these binary operators with a single space on either side

    • assignment: =

    • augmented assignment: +=, -=, etc.

    • comparisons: ==, <, >, <=, >=, unless, etc.

    • arithmetic operators: +, -, *, /, etc.

    • (An exception may be made to align operands)

# Yes
x = 1
y = 1
fooBar = 3

# Also ok
x      = 1
y      = 1
fooBar = 3
  • Do not use spaces around the = sign when used to indicate a default parameter value
test: (param=null) -> # Yes
test: (param = null) -> # No
## Comments

If modifying code that is described by an existing comment, update the comment such that it accurately reflects the new code. (Ideally, improve the code to obviate the need for the comment, and delete the comment entirely.)

The first word of the comment should be capitalized, unless the first word is an identifier that begins with a lower-case letter.

If a comment is short, the period at the end can be omitted.

### Block Comments

Block comments apply to the block of code that follows them.

Each line of a block comment starts with a # and a single space, and should be indented at the same level of the code that it describes.

Paragraphs inside of block comments are separated by a line containing a single #.

# This is a block comment. Note that if this were a real block
# comment, we would actually be describing the proceeding code.
#
# This is the second paragraph of the same block comment. Note
# that this paragraph was separated from the previous paragraph
# by a line containing a single comment character.

init()
start()
stop()
### Inline Comments

Inline comments are placed on the line immediately above the statement that they are describing. If the inline comment is sufficiently short, it can be placed on the same line as the statement (separated by a single space from the end of the statement).

All inline comments should start with a # and a single space.

The use of inline comments should be limited, because their existence is typically a sign of a code smell.

Do not use inline comments when they state the obvious:

# No
x = x + 1 # Increment x

However, inline comments can be useful in certain scenarios:

# Yes
x = x + 1 # Compensate for border
## Naming Conventions

Use camelCase (with a leading lowercase character) to name all variables, methods, and object properties.

Use CamelCase (with a leading uppercase character) to name all classes.

(The official CoffeeScript convention is camelcase, because this simplifies interoperability with JavaScript. For more on this decision, see here.)

For constants, use all uppercase with underscores:

CONSTANT_LIKE_THIS

Methods and variables that are intended to be "private" should begin with a leading underscore:

_privateMethod: ->
## Functions

(These guidelines also apply to the methods of a class.)

When declaring a function that takes arguments, always use a single space after the closing parenthesis of the arguments list:

foo = (arg1, arg2) -> # Yes
foo = (arg1, arg2)-> # No

Do not use parentheses when declaring procedures (functions that take no arguments):

bar = -> # Yes
bar = () -> # No

In cases where method calls are being chained and the code does not fit on a single line, each call should be placed on a separate line and indented by one level, with a leading ..

[1..3]
    .map((x) -> x * x)
    .concat([10..12])
    .filter((x) -> x < 11)
    .reduce((x, y) -> x + y)

When calling functions, never omit the parentheses:

baz(4) # Yes
baz 12 # No

Use only the boolean values true and false:

good = true and false # Yes
bad = yes and no or on and off # No
## Strings

Use string interpolation instead of string concatenation:

"this is an #{adjective} string" # Yes
"this is an " + adjective + " string" # No

Single quoted strings ('') are preffered for keys, symbols and identifiers. Double quoted strings ("") are used in all other cases, including user messages, paths and templates.

object['key'] = value # Yes
object["key"] = value # No

confirm("Would you like fries with that?") # Yes
confirm('Are you sure you want to launch the rockets?') # No
## Conditionals

Don't use unless for conditionals or until in loops.

Multi-line if/else clauses should use indentation:

# Yes
if true
    ...
else
    ...

# No
if true then ...
else ...

Branches in control flow should stay with the indentation level from which they branch. Only use postfix conditionals when this branching remains visible at the indentation level:

# Yes
break    if finished
return   if not valid
continue if not important

# Yes
if singing
    mood = greatlyImproved

# No
mood = greatlyImproved if singing

Conditional assignments, should only be used if there is also an else clause. Never prefixed the value of the expression before the conditional:

# Yes
result = if ... then value1 else value2
if ... 
    result = value

# No
result = value if ...
result = if ... then value

# Big, No No, as it evaluates to `value1(... ? void 0 : value2)`
result = value1 if ... else value2
## Looping and Comprehensions

Take advantage of comprehensions whenever possible:

# Yes
result = for item in array then item.name

# No
results = []
for item in array
    results.push(item.name)

To filter:

result = for item in array when item.name is "test" then item

Always use braces in object literals and class definitions:

object = { one: 1, two: 2 } # Yes
object = one: 1, two: 2 # No

To iterate over the keys and values of objects:

for key, value of object then alert("#{key} = #{value}")
## Extending Native Objects

Do not modify native objects.

For example, do not modify Array.prototype to introduce Array#forEach.

## Exceptions

Do not suppress exceptions.

## Annotations

Use annotations when necessary to describe a specific action that must be taken against the indicated block of code.

Write the annotation on the line immediately above the code that the annotation is describing.

The annotation keyword should be followed by a colon and a space, and a descriptive note.

# FIXME: The client's current state should *not* affect payload processing.
resetClientState()
processPayload()

If multiple lines are required by the description, indent subsequent lines with two spaces:

# TODO: Ensure that the value returned by this call falls within a certain
#   range, or throw an exception.
analyze()

Annotation types:

  • TODO: describe missing functionality that should be added at a later date
  • FIXME: describe broken code that must be fixed
  • OPTIMIZE: describe code that is inefficient and may become a bottleneck
  • HACK: describe the use of a questionable (or ingenious) coding practice
  • REVIEW: describe code that should be reviewed to confirm implementation

If a custom annotation is required, the annotation should be documented in the project's README.

## Miscellaneous

and is preferred over &&.

or is preferred over ||.

is is preferred over ==.

not is preferred over !.

or= should be used when possible:

temp or= {} # Yes
temp = temp || {} # No

Prefer shorthand notation (::) for accessing an object's prototype:

Array::slice # Yes
Array.prototype.slice # No

Prefer @property over this.property.

Ommit return only in short lambda functions. In other cases return should always be used:

_.map(arr, (n) -> n * n ) # Yes
_.map(arr, (n) -> return n * n ) # No

# Yes
calculate = (arg) ->
    # computation of result
    return result
    # Yes

# No
calculate = (arg) ->
    # computation of result
    result

If a function isn't intended to return a result, or the last expression is not the intended result, an empty return is absolutly required:

# Yes
process = (items) ->
    for item in list
        item.doSomething()
    return
    
# No (look at the javascript it produces to see why)
process = (items) ->
    for item in list
        item.doSomething()

Use splats (...) when working with functions that accept variable numbers of arguments:

console.log(args...) # Yes

(a, b, c, rest...) -> # Yes