Skip to content

Instantly share code, notes, and snippets.

@ilfroloff
Last active February 12, 2017 21:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ilfroloff/01f421a062b1714918c9eb5a37202d2f to your computer and use it in GitHub Desktop.
Save ilfroloff/01f421a062b1714918c9eb5a37202d2f to your computer and use it in GitHub Desktop.
My Node.JS Style Guide

My Node.JS Style Guide

Main rules of a node.js code-writing

Keywords var, let, const

  • const: use everywhere except loops and reassigning variables
  • let: only in loops or redefined variables
  • var: don't use

Reasons

Each variable must have a single sense, but a sense can be changed for a reassigned variable and code understanding will be decreased. const prevents reassign possibility of variable.

Note ℹ️️: const prevents only direct reassigning. Properties and methods of variable can be changed in any way!!! Read more here.

require/exports rules

All usage of require/module.exports/exports must be in top-level of file

Wrong πŸ”₯:

class MyClass {
    constructor() {
        const EmailSender = require('email-sender');
        
        exports.EmailSender = EmailSender;
        
        this.email_sender = new EmailSender;
    }
}

exports.default = MyClass;

Right πŸ‘Œ:

const EmailSender = require('email-sender');

class MyClass {
    constructor() {
        this.email_sender = new EmailSender;
    }
}

exports.EmailSender = EmailSender;
exports.default = MyClass;

Reasons

require:

  1. Prevents situation when module wasn't installed but used in some function or class method. Script will be crashed only after invoking in other way.
  2. require "eat" process time - every invoke of require resolving of path to module and can decrease capacity of web-server by 20-25%(!) (related article (russian))

module.exports/exports:

  1. Prevents incontrolled exporting of data. module.exports can be changed asynchronously from any point of script. Also require-d data will be changed also (check node.js docs). Check example:
Example
// o.js
const o = { a: 1 };
process.nextTick(() => o.a = 2);
module.exports = o;
// broke_mind.js
const o = require('./o');
console.log(o) // -> {a:1}
process.nextTick(() => console.log(o)) // -> {a:2}

All names of imported modules or classes must be capitalized by sense parts

const FS = require('fs');
const ChildProcess = require('child_process');
const GulpSASS = require('gulp-sass');
const MyPrettyClass = require('./MyPrettyClass');

All imported methods must be in CamelCase

const defaults = require('lodash/defaults');
const deepClone = require('lodash/deepClone');
const myMethod = require('./helper/myMethod');

All classes and functions must be exported after definition

Wrong πŸ”₯:

module.exports = class MyPrettyClass {
};
module.exports = function myfunction() {
};

Right πŸ‘Œ:

class MyPrettyClass {
}
module.exports = MyPrettyClass;
function myFunction() {
}
module.exports = myFunction;

Reasons

By direct exporting will be created "anonymous" class/function. So you can export class/function without name:

module.exports = class {};
module.exports = function {};

Both defitions are correct.

It's not a real problem but some IDEs (for example, WebStorm) incorrecly highlight class's instance methods and properties and you cannot go to the class definition. Same problem with function also.

Privitives

TODO

Functions

Use function definition when need to create named function (name in CamelCase)

Wrong πŸ”₯:

const myFunction = () => { /* do smt */ };

Right πŸ‘Œ:

function myFunction() {
    /* do smt */
}

Used arrow functions must be without name and use only for singular processing data

Wrong πŸ”₯:

const addOne = item => item + 1;
[1, 2, 3].map(addOne);

Right πŸ‘Œ:

[1, 2, 3].map(item => item + 1);

Reasons

Arrow function doesn't have own context and arguments value so they direct place of usage is only internal of some other function defined by function or method of class. In other places using of arrow functions don't have much sense.

Based on the above definition here is the answer to question "where must to be used function and where "arrow function":

Named functions must be used only for reusing in two and more places or for code simplification.

Simplification means that this is arrow function, used only in one place but includes many code lines. Count of code lines are subjective (code complete said that count cannot be more then 200 lines).

Use brackets only for several incoming parameters in arrow function

list.map(singular => doSmt(singular));
list.reduce((several, parameters) => doSmt(several, parameters));

Use single line arrow function if need only one line for realizing a logic:

[1, 2, 3].reduce((sum, item) => sum += item, 0);
[1, 2, 3].map(id => ({ id })); // creates array of objects with "id" key

Use multiplied line arrow function if need several lines for realizing a logic:

[1, 2, 3].map(item => {
    const result1 = doSmt1(item);
    const result2 = doSmt2(item, result1);
    const result3 = doSmt3(item, result2);
    
    return concatResults(result1, result2, result3);
};

Promises

For singular .then without .catch handle writes in current code-line:

const my_promise = new Promise((resolve, reject) => /* do smt */);

// ...

my_promise.then(results => {
    /* do smt */
});
function postponePromiseInitialize(options) {
    return new Promise(/* do smt */);
}

postponePromiseInitialize({
    options: '',
    in: '',
    several: '',
    lines: ''
}).then(result => /* do smt */

Reasons

TODO

For singular .then with .catch, or several .then/.catch handle writes code in next lines:

const my_first_promise = new Promise((resolve, reject) => /* do smt */);
const my_second_promise = new Promise((resolve, reject) => /* do smt */);
const my_third_promise = new Promise((resolve, reject) => /* do smt */);

// ...

my_first_promise
    .then(results => /* do smt */)
    .catch(err => /* catch error */);
    
my_second_promise
    .then(results => /* do smt */)
    .then(results => /* do smt */);

my_third_promise
    .then(results => /* do smt */)
    .then(results => /* do smt */)
    .catch(err => /* catch error */);

Classes and instances

  • Classes names in CamelCase
  • Instances names in snake_case

Example:

class MyPrettyClass {}

const my_pretty_object = new MyPrettyClass;

Class definition:

static methods and properties definition:

static's always defined firstly in class definition

  • For static methods use CamelCase:
class Example {
    static staticMethodNameOnlyInCamelCaseName() { }
}
  • For static get constats use UPPER_CASE:
class Example {
    static get MY_PUBLIC_CONSTANT() { return 'same variable' }
}
  • static set: don't use
  • Private/protected: need adds _ character to name of method/property names:
class Example {
    static get _MY_PRIVATE_CONST() { return PRIVATE_CONST }
    static _myPrivateMethod() { /* do smt */ }
}
  • All static get property must be before static methods

Wrong πŸ”₯:

class MyPrettyClass {
    static myPrettyMethod() {}
    static get MY_PRETTY_PROP() {}
};

Right πŸ‘Œ:

class MyPrettyClass {
    static get MY_PRETTY_PROP() {}
    static myPrettyMethod() {}
};

plain methods and properties definition:

Plain's always defined secondary in class definition

  • For plain methods use snake_case:
class Example {
    my_plain_method() {}
}
  • For plain get/set properties use snake_case:
class Example {
    get value() {}
    set value(new_value) {}
}
  • All plain get/set properties must be before plain methods

Example:

class ExcelReader {
    static get EXCEL_FILES_FOLDER() {
        return 'path/to/excels';
    }
    
    get reader() {
        return this._reader;
    }
    set reader(reader) {
        this._reader = reader;
    }
    
    constructor(reader) {
        this.reader = reader;
    }
    
    read_excel_by_filename(filename) {
        return this.reader.read(`${this.constructor.EXCEL_FILES_FOLDER}/${filename}`);
    }
}

const excel_reader = new ExcelReader(new ExternalExcelFileReader());

excel_reader.read_excel_by_filename('excel_filename.xlsx').then(/* do smt */)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment