Skip to content

Latest commit

 

History

History
518 lines (407 loc) · 14.7 KB

README.md

File metadata and controls

518 lines (407 loc) · 14.7 KB

JavaScript

Contents

Quotes

Strings should be quoted with a single quote.

Right:

let a = 'a';

Wrong:

let a = "a";

Operators

Always use triple equality operators when comparing two operands.

Why? They will allways work the way you would expect. If the two operands are of the same type and have the same value, then === produces true and !== produces false.

Right:

if (0 === '') // false

Wrong:

if (0 == '') // true

Target dom elements with a data- prefixed attribute

Right:

document.querySelectorAll('[data-component]');

Wrong:

document.querySelectorAll('.component');

document.querySelectorAll('[js-component]');

document.querySelectorAll('[component]');

Exceptions

For unit testing dom elements, use [test-id=""] attributes in your HTML. In test assertions, you can target these accordingly.

expect(!!document.querySelector('[test-id="my-element"]')).toBe(true);

Variables should be declared on a separate line

Why? Because maintaining readability and preventing errors is more important than saving extra lines.

Right:

let a = 1;
let b = 2;
let c = 3;
let d = 4;

Wrong:

let a = 1, b = 2, c = 3, d = 4;

let a = 1,
    b = 2,
    c = 3,
    d = 4;

Write variables in camelCase

When const and let are available, use camelCase for both.

Right:

let willChange = 1;
const maxItems = 3;

Wrong:

let will_change = 1;
const MAX_ITEMS = 3;

Fat arrow functions

Use 'fat-arrow' functions when available, especially for non-method functions.

Why? They fit in well with the functional programming pattern, are shorter, less verbose and do not bind to their own this, arguments, super etc.

Right:

getItem().then(value => {
	console.log(value);
});

const roomsWithTV = rooms.filter(room => room.hasTV);

Wrong:

getItem().then(function(value) {
	console.log(value);
});

var roomsWithTV = rooms.filter(function(room) {
	return room.hasTV
});

Use no parentheses when there is only one parameter

Right:

const reverseText = text => text.split().reverse().join('');

Wrong:

const reverseText = (text) => text.split().reverse().join('');

However, if your function or method has no parameters, it requires parentheses nonetheless:

const someFunction = () => someExpression;

Documenting your code with comments and annotations

Write annotations for all properties and methods.

Why? This is not only helpful for new developers joining the team, but is also a useful reminder of how your code works if you re-visit it after a long time. Besides, many IDEs and editors show annotations when you hover annotated method or function wherever it is used in your code, also showing you what types a method uses and why.

Right:

/**
 * Set the loading state.
 * @param {boolean} to - The new state
 * @return {void}
 */
const setIsLoading = to => { // editor now also provides type and description for method, return variable and parameter
	this.isLoading = to;
};

Wrong:

const setLoadingState = to => { // some editors only provide parameter names
	this.isLoading = to;
}

Comments

Write comments that comply to the following format.

Single line comments

// Single line comment

Multiline comments

/**
 * Multiline comment
 * For comments too big for a single line
 */

Method definition comments

/**
 * Method description
 * @param  {} options [description]
 * @return {string} [description]
 */

TODO comments

See a list of available tags in our 'Use special tags to mark comments' section in the general conventions.

// TODO: Single line comment

Functional (declarative) programming versus imperative programming

Use functional programming methods whenever possible

Whenever possible, try to use functional programming methods (such as .map, .filter, .reduce for arrays) and paradigms.

Why? This not only saves lots of lines of code and variable creation bloat, it is also a lot more readable and easier to comprehend at first sight.

Right:

// Just 3 variables (we are likely never to change their values again)
const totalPersonsAmount = rooms.reduce((total, room) => total + room.personsAmount, 0);
const totalAnimalsAmount = rooms.reduce((total, room) => total + room.animalsAmount, 0);
const totalPlantsAmount = rooms.reduce((total, room) => total + room.plantsAmount, 0);

Wrong:

let totalPersonsAmount = 0; // variable 1
let totalAnimalsAmount = 0; // variable 2
let totalPlantsAmount = 0; // variable 3

for (let index = 0; index++; index >= rooms.length) { // variable 4
	totalPersonsAmount = personsCount + rooms[index].personsAmount; // mutations
	totalAnimalsAmount = personsCount + rooms[index].animalsAmount; // mutations
	totalPlantsAmount = personsCount + rooms[index].plantsAmount; // mutations
}

By using Array.prototype.reduce in the example above, we can define a variable and abstractly assign its logic in one go, thus saving 5 lines of code (whilst maintaining readability), an unneeded variable and a headache ahead.

Note that we also assign variables once and never change their values, complying with the functional immutability paradigm (as in: state is immutable, use copies instead), unlike within a for-loop or .forEach clause, which don't have return values and need to re-set other variables.

Another example, using Array.prototype.filter:

Right:

const roomsWithAnimals = rooms.filter(room => room.animalsAmount);

Wrong:

let roomsWithAnimals = [];
for (let index = 0; index++; index >= rooms.length) {
	if (rooms[index].animalsAmount > 0) {
		roomsWithAnimals.push(rooms[index]);
	}
}

Another example, using Array.prototype.find:

const roomWithOnePerson = rooms.find(room => room.personsAmount === 1);

Try to chain these functional methods together, if possible:

const roomWithPlantsAndOnePerson = rooms // A single variable, containing the one object we need
	.filter(room => room.plantsAmount)
	.find(room => room.personsAmount === 1);

Another example, regaring assigning logic to a variable in one go:

Right:

const someValue = someCondition ? 'this' : 'that';

Wrong:

let someValue = '';
if (someCondition) {
	someValue = 'this';
} else {
	someValue = 'that';
}

Always use .map() over .forEach() over for()

Some will argue that, for example using a for-loop is faster (be it only minor), but this is not the case anymore in the latest browsers and JavaScript engines (e.g. Mozilla's SpiderMonkey, which is now used in Firefox). Try to look further beyond micro optimizations, especially as code grows and scales.

Use shorter functions whenever possible

Right:

const roomWithOneBed = rooms.find(room => room.bedsAmount === 1);

Wrong:

const roomWithOneBed = rooms.find(room => {
	return room.bedsAmount === 1;
});

Rather destructure objects and arrays than declare many, many variables

Try to destruct objects and arrays if you need to use their properties or values in new variables, rather than declaring those variables separately in a seemingly ambiguous way.

This mostly comes in handy when a function needs to do something with an object or array it receives:

Right:

function doSomething(room) {
	const { personsAmount, animalsAmount, plantsAmount } = room;
	console.log(personsAmount); // i.e. 3
	// ...
}

Wrong:

function doSomething(room) {
	let personsAmount = room.personsAmount;
	let animalsAmount = room.animalsAmount;
	let plantsAmount = room.plantsAmount;
	console.log(personsAmount); // i.e. 3
	// ...
}

Don't destructure objects and arrays for the sake of it

Keep in mind that (deep level) desctructuring kan work against you if you have lots of variables and risk namespace clashing (e.g. with booker.firstName and guest.firstName within the same block scope), so use it wisely.

Merging arrays using spread operators

Less right:

const boysArray = ['Edgar', 'Raoul', 'Marvin'];
const girlsArray = ['Colinda', 'Sophia', 'Romy'];
const boysAndGirls = boysArray.concat(girlsArray);

Right:

const boysArray = ['Edgar', 'Raoul', 'Marvin'];
const girlsArray = ['Colinda', 'Sophia', 'Romy'];
const boysAndGirls = [...boysArray, ...girlsArray]; // offers more possibilities

// or:

const boysAndGirls = [...boysArray, 'Colinda', 'Sophia', 'Romy'];

const hasAnM = name => name.toLowerCase().indexOf('m') !== -1;
const personsWithAnMInTheName = [
	...boysArray.filter(hasAnM),
	...girlsArray.filter(hasAnM)
];
console.log(personsWithAnMInTheName); // [ ‘Marvin’, ‘Romy' ]

Immutability: once a value, always that value

Always prevent the mutation of previously defined variables and data structures. Or at least keep mutations and value reassignments to an absolute minimum or better: none at all.

Why? Mutating data can produce code that is less predictable, hard to read and error prone down the line. Also, by maintining the immutability pattern, your code becomes more predictable and is easier to (unit) test as well.

  • Assign value to a variable once
  • Prevent assigning new values by reference
  • Create a copy of variable if you need to work with a new value

Right:

const person = { // Do not use ‘let’ here, because we do not intend to change the value
	firstName: 'John',
	lastName: 'Papa'
};

const newPerson = Object.assign({}, person, { // The empty object {} kills the reference
	lastName: 'Rambo'
});

console.log(newPerson === person); // false
console.log(person); // { firstName: ‘John’, lastName: ‘Papa’ }
console.log(newPerson); // { firstName: ‘John’, lastName: ‘Rambo’ }

Wrong:

const person = {
	firstName: 'John',
	lastName: 'Papa'
};

const newPerson = person;
newPerson.lastName = 'Rambo';

console.log(newPerson === person); // true
console.log(person); // { firstName: ‘John’, lastName: 'Rambo' }

As you can see, thanks to the use of Object.prototype.assign, we prevent the mutation of the person object (which would otherwise be mutated by reference, since properties are passed by reference in objects (and arrays).

Keep defining new variables to a minimum

Why? This will keep memory garbage (and the need to clean it up) to a minimum as well. Also, less variables means less chance of errors.

Right:

const arrayWithKeys = Object.keys(someObject);

Wrong:

let arrayWithKeys = [];
for (let key in someObject) {
	arrayWithKeys.push(key);
}

An alternative for Object.prototype.assign: the object spread operator

Using Object.prototype.assign to create a copy of an object with new or updated values like in the examples above is good practice, but its syntax is rather verbose and thus difficult to read (depending on the context). An alternative approach is to use the object spread operator, proposed for newer versions of JavaScript. This lets you use the spread (...) operator for objects, in a similar way to the array spread operator:

// Using Object.prototype.assign:
return Object.assign({}, originalObject, {
	newProperty: 'some value'
});

// Using the ES7 proposed object spread operator
return { ...originalObject, newProperty: 'some value' };

Since the object spread syntax is still a Stage 3 proposal for ECMAScript you'll need to use a transpiler such as Babel to use it in production. If you are using TypeScript for your project, you can already use the object spread operator if you are using version 2.1 or above.

Immutability: the use of const vs let keywords

Try to use const if you intend never to change the value or reference again. Use let if you must reassign references (for example, within a for loop) or better: just don't do that. By the way, block scoping is achieved by both let and const:

if (true) {
	let cheese = 'yellow'; // block scoped, shiny yellow cheese
	const banana = 'yellow';
}

cheese = 'green'; // will throw an exception because 'cheese' doesn't exist here
console.log(banana); // same thing: ReferenceError

Don't take const's immutability for granted

Remember: although const suggests that the variable created will be immutable (a constant, as used in many other programming languages), strictly taken it isn't. The use of const is no guarantee the variable's value can't be changed again:

const someObject = {};
someObject.someProperty = 42;
console.log(someObject.someProperty); // logs 42

In the example above, only the binding is immutable, not the value. Only primitive types values are immutable by defenition:

const someVariable = 42;
someVariable.someValue = 16;
console.log(someVariable.someValue); // logs 'undefined'

To really make an object's value immutable, you can use Object.prototype.freeze:

const someObject = Object.freeze({
	someProperty: 42
});
someObject.someProperty = 16; // throws an exception

Keep in mind that Object.prorotype.freeze (which is widely supported since ES5) only 'freezes' shallow values. Deep values can (but should not) be changed.

Happy Path pattern use: avoid else, return early

Don’t use if-elseif-else and/or if-else loops. We should strive to reduce any unnecessary and/or superfluous nesting:

  • Return as soon as you know a method cannot do any more meaningful work
  • Reduce indentation by using if/return instead of a more toplevel if/else
  • Most important: Try to keep the heavy part of your method's code at the lowest indendation level, and keep special cases together at the top

Right:

if (condition) {
	doStuff();
}

if (!condition) {
	doOtherStuff();
}

Wrong:

if (condition) {
	// ...
} else if (otherCondition) {
	// ...
} else {
	// ...
}

Inside methods, try to return as soon as you know your method cannot do any more meaningful work:

Right:

const someMethod = (error, results) => {
	if (error) {
		handleError(error);
		return;
	}

	doOtherStuff(results);
	doMoreStuff();
	// ...etc.
	// ...
}

Wrong:

const someMethod = (error, results) => {
	if (!error) {
		doOtherStuff(results);
		doMoreStuff();
		// ...etc.
		// ...

	} else {
		handleError(error);
	}
}