To make code easy to read and maintain, we should follow some best practices.
In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.
Named Function
Expressions
Names in function expressions aren’t very useful.
If we set the function to a variable, we can’t use it with that name.
We also don’t need it much in IIFEs.
So we can write:
Foo.prototype.bar = function() {};
instead of:
Foo.prototype.bar = function bar() {};
Also, we can write:
(function() {
// ...
}())
instead of:
(function bar() {
// ...
}())
Unless we need the name inside the function, it’s not that useful.
Consistent Use of Either Function
Declarations or Expressions
We can keep to one function style for consistency.
So either we stick with:
function doWork() {
// ...
}
or:
const doWork = function() {
// ...
};
Arrow functions can only use the second style.
Line Breaks Between Arguments of a Function Call
We only need line breaks for arguments and function calls are only needed for long expressions or argument lists.
For instance, we can write:
foo(
"foo",
"bar",
"three"
);
or:
foo("one")
We just keep line length around 100 characters or less.
Consistent Line Breaks Inside Function Parentheses
We should have consistent line breaks inside function parentheses.
For instance, we can write:
function foo(
bar,
baz
) {}
or:
const foo = function(
bar, baz
) {};
For short functions, we can keep the signature all in one line.
Other than that, we break it into multiple lines.
Spacing Around the * in Generator Functions
We should keep the spacing around the *
in generator functions.
For instance, we can write:
function* generator() {
yield "foo";
yield "bar";
}
or:
function * generator() {
yield "foo";
yield "bar";
}
or:
function *generator() {
yield "foo";
yield "bar";
}
We just have to stick with one.
Return Statement Should be Present in Property Getters
The whole point of getters is to return something.
Therefore, we should make sure that’s done in getters.
For instance, we should write:
const obj = {
get name() {
return "james";
}
};
or:
Object.defineProperty(obj, "age", {
get() {
return 100;
}
});
instead of:
const obj = {
get name() {
return;
}
};
or:
Object.defineProperty(obj, "age", {
get() {
}
});
or:
class P {
get name() {
}
}
Put require() on the Top-Level Module Scope
We should put our require
calls at the top level of the module scope.
For instance, we should write:
const fs = require("fs");
instead of:
function foo() {
if (condition) {
const fs = require("fs");
}
}
Dynamically requiring things is confusing,
So we shouldn’t do it.
Require Grouped Accessor Pairs in Object Literals and Classes
Having setters that don’t have getters isn’t very useful since we can’t get the value that’s set.
Therefore, we should have getters for any setters.
For instance, we should write:
const obj = {
get a() {
return this.val;
},
set a(value) {
this.val = value;
},
b: 1
};
instead of:
const obj = {
set a(value) {
this.val = value;
},
b: 1
};
We can get the value of a
in obj
with the getter.
Guarding for for-in
We should check for inherited properties if we loop with for-in since it enumerates inherited properties.
Instead of writing:
for (key in foo) {
doWork(key);
}
We write:
for (key in foo) {
if (Object.prototype.hasOwnProperty.call(foo, key)) {
doWork(key);
}
}
or:
for (key in foo) {
if ({}.hasOwnProperty.call(foo, key)) {
doSomething(key);
}
}
We use Object.prototype.hasOwnProperty.call
or {}.hasOwnProperty.call
instead of foo.hasOwnProperty
so that it still works if hasOwnProperty
has been overwritten.
hasOwnProperty
checks if a property is a noninherited property.
Callback Error Handling
We should check for errors in callbacks if they exist.
For instance, we can write:
function foo(err, data) {
if (err) {
console.log(err.stack);
}
doWork();
}
If it’s a Node style callback, err
will be populated with the error, which we should handle in case it’s thrown.
Conclusion
We should handle errors in callback.
Naming function expressions is redundant.
Line breaks and spacing should be consistent.