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.
Avoid Conditionals
We can avoid conditionals and replace it with polymorphism.
For instance, instead of writing:
class Person {
// ...
getData() {
switch (this.type) {
case "student":
return this.getStudentData();
case "employee":
return this.getEmployeeData();
case "manager":
return this.getManagerData();
}
}
}
We can split them into multiple subclasses:
class Person {
// ...
}
class Student extends Person {
// ...
getData() {
return this.getStudentData();
}
}
class Employee extends Person {
// ...
getData() {
return this.getEmployeeData();
}
}
class Manager extends Person {
// ...
getData() {
return this.getManagerData();
}
}
Instead of a big class that does multiple things, we split them into multiple subclasses.
The shared code is in Person
.
Thes rest is in the methods exclusive to the subclasses.
this.getStudentData
, this.getEmployeeData
, and this.getManagerData
can stay in the subclasses.
Don’t Over-Optimize
We shouldn’t over-optimize our code if we have to sacrifice readability.
For instance, we don’t have to cache loop lengths.
It won’t bring much difference in performance in modern browsers, but we make our code harder to read.
Instead of writing:
for (let i = 0, len = items.length; i < len; i++) {
// ...
}
We write:
for (let i = 0; i < items.length; i++) {
// ...
}
or just use the for-of loop:
for (const item of items) {
// ...
}
Remove Dead Code
We should remove dead code since they aren’t used.
For instance, instead of writing
const oldRequest = (params) => {
//...
}
const newRequest = (params) => {
//...
}
newRequest({
//...
});
We write:
const newRequest = (params) => {
//...
}
newRequest({
//...
});
Use Getters and Setters
We should use getters and stress on objects.
They let us do validation before getter and setting data.
And we can encapsulate any logic that’s run before getting.
Logging and error handling is easier, and we can lazy load our stuff in getters.
For instance, instead of writing:
function makeCart() {
// ...
return {
cart: []
// ...
};
}
We write:
function makeCart() {
let cart = [];
function getCart() {
return cart;
}
function setCart(newCart) {
cart = newCart;
}
return {
// ...
getCart,
setCart
};
}
We create a cart with a private cart
and a getCart
getter function and setCart
setter function.
We expose what we want in our code.
Now we can use it by writing:
const cart = makeCart();
cart.setBalance(cartItems);
Make Objects have Private Members
We can use closures to keep private members in objects.
For instance, instead of writing:
const person = {
name: 'james',
age: 20
}
We write:
const person = (() => {
const age = 20;
return {
name: 'james'
}
})();
We return an object with the name
but not the age
.
That’s hidden in the function.
This one of the ways to keep private variables.
Module members are also private unless we export them.
Prefer Class Syntax Over Constructor Functions
The constructor function makes inheritance harder.
To do inheritance, we’ve to inherit the methods and add instance methods to the prototype of the constructor.
For instance, we write:
const Animal = function(name) {
this.name = name;
};
Animal.prototype.move = function() {};
const Dog = function(name) {
Animal.call(this, name);
this.age = age;
};
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {};
We’ve to create the Animal
constructor and add instance methods to its prototype.
Then we create the Dog
constructor and call the Animal
constructor with the arguments.
Then we inherited the methods from the Animal
‘s prototype with Object.create
.
And then we set the constructor
property of its Prototype
to itself to get the right result when we use instanceof
.
Then we add instance methods exclusive to Dog
.
With the class syntax, it’s much easier.
For instance, we write:
class Animal {
constructor(name) {
this.name = name;
}
move() {
/* ... */
}
}
class Dog extends Animal {
constructor(age) {
super(name);
this.age = age;
}
bark() {
/* ... */
}
}
We put the constructor and methods all inside the class.
We call the parent constructor with super
,
And we extends
to indicate inheritance.
Conclusion
The class syntax makes inheritance much easier.
Conditionals can be replaced with polymorphism.
If optimization doesn’t bring much benefit and make our code harder to read, then don’t do it.