Skip to content

Latest commit

 

History

History
1246 lines (922 loc) · 21.6 KB

JAVASCRIPT-STYLE-GUIDE.md

File metadata and controls

1246 lines (922 loc) · 21.6 KB

MotorcycleJS JavaScript Style Guide

Table of Contents

  1. Types
  2. Objects
  3. Arrays
  4. Strings
  5. Functions
  6. Properties
  7. Variables
  8. Requires
  9. Callbacks
  10. Try-catch
  11. Conditional Expressions & Equality
  12. Blocks
  13. Comments
  14. Whitespace
  15. Commas
  16. Semicolons
  17. Type Casting & Coercion
  18. Naming Conventions
  19. Accessors

Types

Primitives

When you access a primitive type, you work directly on its value.

  • string
  • number
  • boolean
  • null
  • undefined
let foo = 1
let bar = foo

bar = 9

console.log(foo, bar) // => 1, 9
Complex

When you access a complex type, you work on a reference to its value.

  • object
  • array
  • function
let foo = [1, 2]
let bar = foo

bar[0] = 9

console.log(foo[0], bar[0]) // => 9, 9

⬆ Back to top

Objects

  • Use the literal syntax for object creation.

    // bad
    let item = new Object()
    
    // good
    let item = {}
  • Use readable synonyms in place of reserved words.

    // bad
    let superman = {
      class: `alien`
    }
    
    // bad
    let superman = {
      klass: `alien`
    }
    
    // good
    let superman = {
      type: `alien`
    }

⬆ Back to top

Arrays

  • Use the literal syntax for array creation.

    // bad
    let items = new Array()
    
    // good
    let items = []
  • If you don't know array length use Array#push.

    let someStack = []
    
    // bad
    someStack[someStack.length] = `abracadabra`
    
    // good
    someStack.push(`abracadabra`)
  • When you need to copy an array use Array#slice. jsPerf

    let len = items.length
    let itemsCopy = []
    let i
    
    // bad
    for (i = 0; i < len; i++) {
      itemsCopy[i] = items[i]
    }
    
    // good
    itemsCopy = items.slice()
  • To convert an array-like object to an array, use Array#slice.

    function trigger() {
      let args = Array.prototype.slice.call(arguments)
      ...
    }

⬆ Back to top

Strings

  • Use backquotes `` for strings

    // bad
    let name = "Bob Parr"
    
    // bad
    let name = 'Bob Parr'
    
    // good
    let name = `Bob Parr`
    
    // bad
    let fullName = "Bob " + lastName
    
    // bad
    let fullName = 'Bob ' + lastName
    
    // good
    let fullName = `Bob ${lastName}`
  • Strings longer than 80 characters should be written across multiple lines.

    // bad
    let errorMessage = `This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.`
    
    // good
    let errorMessage = `This is a super long error that was thrown because
      of Batman. When you stop to think about how Batman had anything to do
      with this, you would get nowhere fast.`
  • When programmatically building up a string, use Array#join instead of string concatenation.

    let items;
    let messages;
    let length;
    let i;
    
    messages = [{
      state: 'success',
      message: 'This one worked.'
    }, {
      state: 'success',
      message: 'This one worked as well.'
    }, {
      state: 'error',
      message: 'This one did not work.'
    }];
    
    length = messages.length;
    
    // bad
    function inbox(messages) {
      items = `<ul>`;
    
      for (i = 0; i < length; i++) {
        items += `<li>${messages[i].message}</li>`;
      }
    
      return `${items}</ul>`;
    }
    
    // good
    function inbox(messages) {
      items = [];
    
      for (i = 0; i < length; i++) {
        items[i] = messages[i].message;
      }
    
      return `<ul><li>${items.join('</li><li>')}</li></ul>`;
    }

⬆ Back to top

Functions

  • Function expressions:

    // anonymous function expression
    let anonymous = () => true
    
    // named function expression
    let named = function named() {
      return true
    };
    
    // immediately-invoked function expression (IIFE)
    (function iife() {
      console.log(`Welcome to the Internet. Please follow me.`)
    })()
  • Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead.

    // bad
    if (currentUser) {
      function test() {
        console.log('Nope.')
      }
    }
    
    // good
    let test;
    if (currentUser) {
      test = () => {
        console.log('Yup.')
      }
    }
  • Never name a parameter arguments, this will take precedence over the arguments object that is given to every function scope.

    // bad
    function nope(name, options, arguments) {
      // ...stuff...
    }
    
    // good
    function yup(name, options, ...args) {
      // ...stuff...
    }

⬆ Back to top

Properties

  • Use dot notation when accessing properties.

    let luke = {
      jedi: true,
      age: 28,
    };
    
    // bad
    let isJedi = luke[`jedi`]
    
    // good
    let isJedi = luke.jedi
  • Use subscript notation [] when accessing properties with a variable.

    let luke = {
      jedi: true,
      age: 28,
    };
    
    function props(prop) {
      return luke[prop]
    }
    
    let isJedi = props(`jedi`)

⬆ Back to top

Variables

  • Always use let or const to declare variables.

    // bad
    var superPower = SuperPower()
    
    // good
    let superPower = SuperPower()
    
    // good
    const superPower = SuperPower()
  • Declare each variable on a newline, with a let before each of them.

    // bad
    let items = Items(),
        goSportsTeam = true,
        dragonball = `z`
    
    // good
    let items = Items()
    let goSportsTeam = true
    let dragonball = `z`
  • Declare unassigned variables last. This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables.

    // bad
    let i
    let items = Items()
    let dragonball
    let goSportsTeam = true
    let len
    
    // good
    let items = Items()
    let goSportsTeam = true
    let dragonball
    let len
    let i
  • Avoid redundant variable names, use Object instead.

    // bad
    let kaleidoscopeName = `..`
    let kaleidoscopeLens = []
    let kaleidoscopeColors = []
    
    // good
    let kaleidoscope = {
      name: `..`,
      lens: [],
      colors: [],
    }
  • Assign variables as close to where they are being used within their scope.

    // bad
    function bad() {
      let name = Name()
      test()
      console.log(`doing stuff...`)
    
      //...other stuff...
    
      if (name === `test`) {
        return false
      }
    
      return name
    }
    
    // good
    function good() {
      test()
      console.log(`doing stuff...`)
    
      //...other stuff...
    
      let name = Name()
      if (name === `test`) {
        return false
      }
    
      return name
    }
    
    // bad
    function bad(...args) {
      let name = Name()
    
      if (!args.length) {
        return false
      }
    
      return true
    }
    
    // good
    function good(...args) {
      if (!args.length) {
        return false
      }
    
      let name = Name();
    
      return true;
    }

Imports

  • Organize your imports in the following order:

    • core modules
    • npm modules
    • others
    // bad
    import Car from './models/Car'
    import async from 'async'
    import http from 'http'
    
    // good
    import http from 'http'
    import fs from 'fs'
    
    import async from 'async'
    import mongoose from 'mongoose'
    
    import Car from './models/Car'
  • Do not use the .js when importing modules

  // bad
  import Batmobil from './models/Car.js'

  // good
  import Batmobil from './models/Car'

⬆ Back to top

Callbacks

  • Always check for errors in callbacks.
//bad
database.get(`pokemons`, (err, pokemons) => {
  console.log(pokemons)
});

//good
database.get(`drabonballs`, (err, drabonballs) => {
  if (err) {
    // handle the error somehow, maybe return with a callback
    return console.log(err)
  }
  console.log(drabonballs)
});
  • Return on callbacks.
//bad
database.get(`drabonballs`, (err, drabonballs) => {
  if (err) {
    // if not return here
    console.log(err)
  }
  // this line will be executed as well
  console.log(drabonballs)
});

//good
database.get(`drabonballs`, (err, drabonballs) => {
  if (err) {
    // handle the error somehow, maybe return with a callback
    return console.log(err)
  }
  console.log(drabonballs)
});
  • Use descriptive arguments in your callback when it is an "interface" for others. It makes your code readable.
// bad
function getAnimals(done) {
  Animal.get(done)
}

// good
function getAnimals(done) {
  Animal.get((err, animals) => {
    if(err) {
      return done(err)
    }

    return done(null, {
      dogs: animals.dogs,
      cats: animals.cats,
    })
  })
}

⬆ Back to top

Try catch

  • Only throw in synchronous functions.

    Try-catch blocks cannot be used to wrap async code. They will bubble up to the top, and bring down the entire process.

    //bad
    function readPackageJson(callback) {
      fs.readFile('package.json', (err, file) => {
        if (err) {
          throw err
        }
        ...
      })
    }
    
    //good
    function readPackageJson(callback) {
      fs.readFile('package.json', (err, file) => {
        if (err) {
          return callback(err)
        }
        ...
      })
    }
  • Catch errors in sync calls.

    //bad
    let data = JSON.parse(jsonAsAString)
    
    //good
    let data
    try {
      data = JSON.parse(jsonAsAString);
    } catch (err) {
      //handle error - hopefully not with a console.log ;)
      console.log(err)
    }

⬆ Back to top

Conditional Expressions & Equality

  • Use === and !== over == and !=.

  • Conditional expressions are evaluated using coercion with the ToBoolean method and always follow these simple rules:

    • Objects evaluate to true
    • Undefined evaluates to false
    • Null evaluates to false
    • Booleans evaluate to the value of the boolean
    • Numbers evaluate to false if +0, -0, or NaN, otherwise true
    • Strings evaluate to false if an empty string '', otherwise true
    if ([0]) {
      // true
      // An array is an object, objects evaluate to true
    }
  • Use shortcuts.

    // bad
    if (name !== ``) {
      // ...stuff...
    }
    
    // good
    if (name) {
      // ...stuff...
    }
    
    // bad
    if (collection.length > 0) {
      // ...stuff...
    }
    
    // good
    if (collection.length) {
      // ...stuff...
    }
  • For more information see Truth Equality and JavaScript by Angus Croll

⬆ Back to top

Blocks

  • Use braces with all multi-line blocks.

    // bad
    if (test)
      return false
    
    // bad
    if (test) return false
    
    // good
    if (test) {
      return false
    }
    
    // bad
    function bad() { return false }
    
    // good
    function good() {
      return false
    }

⬆ Back to top

Comments

  • Use /** ... */ for multiline comments. Include a description, specify types and values for all parameters and return values.

    // bad
    // make() returns a new element
    // based on the passed in tag name.
    //
    // @param <String> tag
    // @return <Element> element
    function make(tag) {
    
      // ...stuff...
    
      return element
    }
    
    // good
    /**
     * make() returns a new element
     * based on the passed in tag name.
     *
     * @param <String> tag
     * @return <Element> element
     */
    function make(tag) {
    
      // ...stuff...
    
      return element
    }
  • Use // for single line comments. Place single line comments on a new line above the subject of the comment. Put an empty line before the comment.

    // bad
    let active = true  // is current tab
    
    // good
    // is current tab
    let active = true
    
    // bad
    function getType(optType) {
      console.log(`fetching type...`)
      // set the default type to `no type`
      let type = optType || `no type`
    
      return type
    }
    
    // good
    function getType(optType) {
      console.log(`fetching type...`)
    
      // set the default type to `no type`
      let type = optType || `no type`
    
      return type;
    }
  • Prefixing your comments with FIXME or TODO helps other developers quickly understand if you're pointing out a problem that needs to be revisited, or if you're suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable. The actions are FIXME -- need to figure this out or TODO -- need to implement.

  • Use // FIXME: to annotate problems.

    function Calculator() {
    
      // FIXME: shouldn't use a global here
      total = 0;
    
      return {
        total,
      }
    }
  • Use // TODO: to annotate solutions to problems

    function Calculator() {
    
      // TODO: total should be configurable by an options param
      let total = 0
    
      return {
        total,
      }
    }

**[⬆ Back to top](#table-of-contents)**



## Whitespace

* Use soft tabs set to two spaces.

  ```javascript
  // bad
  function bad() {
  ∙∙∙∙let name
  }

  // bad
  function bad() {
  ∙let name
  }

  // good
  function good() {
  ∙∙let name
  }
  • Place one space before the leading brace.

    // bad
    function test(){
      console.log(`test`)
    }
    
    // good
    function test() {
      console.log(`test`)
    }
    
    // bad
    dog.set(`attr`,{
      age: `1 year`,
      breed: `Bernese Mountain Dog`,
    })
    
    // good
    dog.set(`attr`, {
      age: `1 year`,
      breed: `Bernese Mountain Dog`,
    })
  • Set off operators with spaces.

    // bad
    let x=y+5
    
    // good
    let x = y + 5
  • End files with a single newline character.

    // bad
    (function iife(global) {
      // ...stuff...
    })(this)
    // bad
    (function iife(global) {
      // ...stuff...
    })(this)
    
    // good
    (function iife(global) {
      // ...stuff...
    })(this)
  • Use indentation when making long method chains.

    // bad
    obj(`item`).find(`selected`).highlight().end().find(`open`).updateCount()
    
    // good
    obj(`item`)
      .find(`selected`)
        .highlight()
        .end()
      .find(`open`)
        .updateCount()
    
    // bad
    let leds = stage.selectAll(`.led`).data(data).enter().append(`svg:svg`).class(`led`, true)
        .attr(`width`,  (radius + margin) * 2).append(`svg:g`)
        .attr(`transform`, `translate(' + (radius + margin) + ',' + (radius + margin) + ')`)
        .call(tron.led)
    
    // good
    let leds = stage.selectAll(`.led`)
        .data(data)
      .enter().append(`svg:svg`)
        .class(`led`, true)
        .attr(`width`,  (radius + margin) * 2)
      .append(`svg:g`)
        .attr(`transform`, `translate(' + (radius + margin) + ',' + (radius + margin) + ')`)
        .call(tron.led)

⬆ Back to top

Commas

  • Leading commas: Nope.

    // bad
    let hero = {
        firstName: `Bob`
      , lastName: `Parr`
      , heroName: `Mr. Incredible`
      , superPower: `strength`
      ,
    }
    
    // good
    let hero = {
      firstName: `Bob`,
      lastName: `Parr`,
      heroName: `Mr. Incredible`,
      superPower: `strength`,
    }
  • Additional trailing comma: Yes.

    // bad
    let hero = {
      firstName: `Kevin`,
      lastName: `Flynn`
    }
    
    let heroes = [
      `Batman`,
      `Superman`
    ]
    
    // good
    let hero = {
      firstName: `Kevin`,
      lastName: `Flynn`,
    }
    
    let heroes = [
      `Batman`,
      `Superman`,
    ]

⬆ Back to top

Semicolons

  • Nope.

    // bad
    function bad() {
      let name = `Skywalker`;
      return name;
    }
    
    // good
    function good() {
      let name = `Skywalker`
      return name
    }

⬆ Back to top

Type Casting & Coercion

  • Perform type coercion at the beginning of the statement.

  • Strings:

    //  => reviewScore = 9;
    
    // bad
    let totalScore = reviewScore + ``
    
    // good
    let totalScore = `` + reviewScore
    
    // best
    let totalScore = `${reviewScore}`
    
    // bad
    let totalScore = `` + reviewScore + ` total score`
    
    // good
    let totalScore = reviewScore + ` total score`
    
    // best
    let totalScore = `${reviewScore} total score`
  • Use parseInt for Numbers and always with a radix for type casting.

    let inputValue = `4`
    
    // bad
    let val = new Number(inputValue)
    
    // bad
    let val = +inputValue
    
    // bad
    let val = inputValue >> 0
    
    // bad
    let val = parseInt(inputValue)
    
    // good
    let val = Number(inputValue)
    
    // good
    let val = parseInt(inputValue, 10)
  • If for whatever reason you are doing something wild and parseInt is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you're doing.

    // good
    /**
     + parseInt was the reason my code was slow.
     + Bitshifting the String to coerce it to a
     + Number made it a lot faster.
     */
    let val = inputValue >> 0
  • Note: Be careful when using bitshift operations. Numbers are represented as 64-bit values, but Bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:

    2147483647 >> 0 //=> 2147483647
    2147483648 >> 0 //=> -2147483648
    2147483649 >> 0 //=> -2147483647
  • Booleans:

    let age = 0
    
    // bad
    let hasAge = new Boolean(age)
    
    // good
    let hasAge = Boolean(age)
    
    // good
    let hasAge = !!age

⬆ Back to top

Naming Conventions

  • Avoid single letter names. Be descriptive with your naming.

    // bad
    function q() {
      // ...stuff...
    }
    
    // good
    function query() {
      // ..stuff..
    }
  • Use camelCase when naming objects, functions, and instances.

    // bad
    let OBJEcttsssss = {}
    let this_is_my_object = {}
    function c() {}
    let u = makeUser({
      name: `Bob Parr`,
    })
    
    // good
    let thisIsMyObject = {}
    function thisIsMyFunction() {}
    let user = makeUser({
      name: `Bob Parr`,
    })
  • Use PascalCase when naming constructors.

    // bad
    function user({name}) {
      return {
        name,
      }
    }
    
    let bad = user({
      name: `nope`,
    })
    
    // good
    function User({name}) {
      return {
        name,
      }
    }
    
    let good = User({
      name: `yup`,
    })
  • Name your functions. This is helpful for stack traces.

    // bad
    let log = function(msg) {
      console.log(msg)
    }
    
    // good
    let log = function log(msg) {
      console.log(msg)
    }

⬆ Back to top

Accessors

  • Accessor functions for properties are not required. If you do make accessor functions, there are certain recommended forms, depending on how the property is expressed:

    • If the property is expressed as a noun, the format is:

      • <type>function noun()
      • <void>function setNoun(<type>aNoun)

      For example:

      let age = dragon.age()
      
      dragon.setAge(42)
    • If the property is expressed as an adjective, the format is:

      • <Boolean>function isAdjective()
      • <void>function setAdjective(<Boolean>flag)

      For example:

      let editable = text.isEditable()
      
      text.setEditable(true)
    • If the property is expressed as a verb, the format is:

      • <Boolean>function verbObject()
      • <void>function setVerbObject(<Boolean>flag)

      For example:

      let showsAlpha = colorPanel.showsAlpha()
      
      colorPanel.setShowsAlpha(true)

      The verb should be simple present tense.

    • Don’t twist a verb into an adjective by using a participle:

      Example Correct?
      <void>setAcceptsSubscribers(<Boolean>flag) Right.
      <Boolean>acceptsSubscribers Right.
      <void>setSubscribersAccepted(<Boolean>flag) Wrong.
      <Boolean>subscribersAccepted Wrong.
    • May use modal verbs (verbs preceded by “can”, “should”, “will”, and so on) to clarify meaning, but don’t use “do” or “does”.

      Example Correct?
      <void>setCanSubscribe(<Boolean>flag) Right.
      <Boolean>canSubscribe Right.
      <void>setShouldCloseStream(<Boolean>flag) Right.
      <Boolean>shouldCloseStream Right.
      <void>setDoesAcceptSubscribers(<Boolean>flag) Wrong.
      <Boolean>doesAcceptSubscribers Wrong.
    • Use “get” only for methods that return objects and values indirectly. Use this form for methods only when multiple items need to be returned.

⬆ Back to top