Skip to content

HuzuTech/coffeescript-style-guide

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 

Repository files navigation

CoffeeScript Style Guide

This is a HuzuTech-local version of the canonical CoffeeScript style guide created by polarmobile.

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 120 characters.

### Blank Lines

Separate top-level function and class definitions with a single blank line.

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

SPARINGLY 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)

Use double quotes around module paths, and omit parens where possible.

## Whitespace in Expressions and Statements

Avoid extraneous whitespace in the following situations:

  • Immediately inside parentheses or brackets

       ($ 'body') # Yes
       ( $ 'body' ) # No
       a = [1, 2, 3] # Yes
       a = [ 1, 2, 3 ] # No

    But DO use a single space inside object literals, where braces are necessary:

    {a: 1, b: 2} # No
    { a: 1, b: 2 } # Yes
  • 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: =

      • Note that this also applies when indicating default parameter value(s) in a function declaration

        test: (param = null) -> # Yes
        test: (param=null) -> # No
    • augmented assignment: +=, -=, etc.

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

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

    • Don't align on the operator

         # Yes
         x = 1
         y = 1
         fooBar = 3
         
         # No
         x      = 1
         y      = 1
         fooBar = 3           
## 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.)

Comments should be written as sentences -- so the first word should be capitalised (unless referring to a variable that begins with a lower-case letter), and they should end with a full stop.

We use CODO to autogenerate documentation from comments.

### Block Comments

Block comments should be used sparingly -- generally at the head of a file, to specify a license or other metadata.

  ### 
  This is a block comment. It should introduce a large grouping of code (e.g. a class),
  and should not be used for general commenting unless it's crucial to draw attention to 
  the comment.
  ###

  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.

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. (This style is also commonly referred to as PascalCase, CamelCaps, or CapWords, among other alternatives.)

(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
## 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 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 (i.e., four spaces), with a leading ..

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

When calling functions, choose to omit or include parentheses in such a way that optimizes for readability. Keeping in mind that "readability" can be subjective; the following examples demonstrate cases where parentheses have been omitted or included in a manner that the community deems to be optimal:

baz 12

brush.ellipse x: 10, y: 20 # Braces can also be omitted or included for readability

foo(4).bar(8)

obj.value(10, 20) / obj.value(20, 10)

print inspect value

new Tag(new Value(a, b), new Arg(c))

If a function takes another function as an argument, make that argument the last in the parameter list:

# No
myUglyFunction = (aCallback, anotherArgument) ->
    ...

myUglyFunction (arg) ->
    doSomething()
    doSomethingElse()
, 200

# Yes
myPrettyFunction = (anArgument, aCallback) ->
    ...

myPrettyFunction 200, (arg) ->
    doSomething()
    doSomethingElse()

Similarly, if a function takes an object as an argument, make that argument the last in the parameter list:

# No
myUglyFunction = (anObject, anotherArgument) ->
    ...

myUglyFunction { a: 1, b: 2 }, 200

# Yes
myPrettyFunction = (anArgument, anObject) ->
    ...

myPrettyFunction 200, a: 1, b: 2

In case of conflict in these guidelines, prefer the first.

Avoid hanging parens:

# No
myUglyFunction(
    lengthyArgument: "long!", veryLengthy: "also long!", 
    "another long argument", 12, (callbackArgument) -> doSomething()
)

# Yes
options = lengthyArgument: "long!", veryLengthy: "also long!"
callback = (callbackArgument) -> doSomething()

myLessUglyFunction options, "another long argument", 12, callback
## Strings

Use string interpolation instead of string concatenation:

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

Prefer double quoted strings ("") over single quoted ('') strings.

## Conditionals

Favor unless over if for negative conditions.

Instead of using unless...else, use if...else:

  # Yes
  if true
    ...
  else
    ...

  # No
  unless false
    ...
  else
    ...

Multi-line if/else clauses should use indentation:

  # Yes
  if true
    ...
  else
    ...

  # No
  if true then ...
  else ...
## Looping and Comprehensions

Take advantage of comprehensions whenever possible:

  # Yes
  result = (item.name for item in array)

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

To filter:

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

To iterate over the keys and values of objects:

object = one: 1, two: 2
alert("#{key} = #{value}") for key, value of object

Omit braces in object literals wherever possible:

obj = { a: 1, b: 2 } # No
obj = a: 1, b: 2 # Yes

Declare large object literals on multiple lines, without separating commas:

obj = 
    longValue: "A long value of some kind"
    lengthyValue: "Another lengthy value"
    longLongValue: "Yawn"

Simliarly with nested object literals:

obj = 
    levelOne:
        firstLevelOneValue: 10
        secondLevelOneValue: 25
    levelTwo:
        firstLevelTwoValue:
            anotherObject: 5
        secondLevelTwoValue: 30

Try to keep array literals short. When absolutely necessary, declare them on multiple lines in K&R bracket style:

aLongArrayLiteral = [
    value1,
    value2,
    value3,
    value4
]
## 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.

return @property # Yes
return this.property # No

Avoid return where not required, unless the explicit return increases clarity.

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

console.log args... # Yes

(a, b, c, rest...) -> # Yes
## Gotchas and Best Practices ### Calling Functions

When calling a function with no arguments remember to include the parenthesis.

@function   # Hours of fruitless debugging
@function() # Passing tests
### Fat and Thin Arrows

The 'Fat Arrow' (=>) operator is syntactic sugar to bind this to the entity creating the function (where this would otherwise refer to the function itself). This is very useful when creating anonymous functions in the context of class methods:

example: (otherClass) ->
    otherClass.bind "event", =>
        @anotherMethod

The Fat Arrow should not, in normal circumstances, be used to declare class methods:

example: (otherClass) => # no

example: (otherClass) -> # yes

One extraordinary circumstance may be that the class method in question is intended to be referred to by function name elsewhere:

example: (otherClass) =>
    @anotherMethod()
    
otherExample: (otherClass) ->
    otherClass.bind "event", @example

This should be used very sparingly. There are better alternatives, the most obvious being to declare an anonymous function and bind there:

example: (otherClass) ->
    @anotherMethod()
    
otherExample: (otherClass) ->
    otherClass.bind "event", => @example()

When this is difficult (e.g. when an event must be programmatically unbound), use an ad-hoc named function:

example: (otherClass) ->
    @anotherMethod()
    
otherExample: (otherClass) ->
    namedFunction = => @example()
    otherClass.bind "event", namedFunction
## Closures in Loops

CoffeeScript fixes many of Javascript's scoping issues, but it nevertheless IS effectively Javascript, and inherits Javascript's overall scoping strategy: function scoping.

Unlike, for example, Java, Javascript does not use block scoping:

for i in [1, 2, 3, 4]
    otherClass.bind "event", -> console.log i

otherClass.trigger "event"

In the above example, four events are bound to otherClass. We might expect that each event would log, respectively, 1, 2, 3 and 4 to the console. In fact, all four events will log the number 4. This is because the Javascript scoping rules do not lead to the creation of a new variable i on each iteration of the loop; rather, i is 'hoisted' to the beginning of its parent function, as we can see in the Javascript compiled from the above:

(function() {
  var i, _i, _len, _ref;

  _ref = [1, 2, 3, 4];
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    i = _ref[_i];
    otherClass.bind("event", function() {
      return console.log(i);
    });
  }

  otherClass.trigger("event");

}).call(this);

The variable i is declared at the head of the function, and maintains its scope through that function. When we 'close' over i in the function bound to event, we create a reference to the scope in which i exists. So, when event is triggered, the value of i will be logged to the console; and presuming event is triggered after the last iteration of the loop, the value of i will be 4.

The way to avoid this awkward behaviour is to leverage Javascript's function scoping. Variables passed into a function are given their own scope within that function, so presuming we create and execute a function at each iteration of the loop, we can use that function's independent scope to isolate our variable. CoffeeScript makes it simple to create an immediately-executing function using the do keyword:

for i in [1, 2, 3, 4]
    do (i) ->
        otherClass.bind "event", -> console.log i

otherClass.trigger "event"

This version of the code will result in four separate events being bound, which will log, respectively, 1, 2, 3 and 4. We pass the variable i as an argument to the anonymous function created using the do keyword, resulting in a new scope being created for i on each iteration of the loop. See the compiled Javascript:

(function() {
  var i, _fn, _i, _len, _ref;

  _ref = [1, 2, 3, 4];
  _fn = function(i) {
    return otherClass.bind("event", function() {
      return console.log(i);
    });
  };
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    i = _ref[_i];
    _fn(i);
  }

  otherClass.trigger("event");

}).call(this);

About

Best-practices and coding conventions for the CoffeeScript programming language

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published