Categories
JavaScript Best Practices

JavaScript Best Practices — Early Returns, Casing, and Spacing

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.

Return After Callback

If we call a callback early, we should add a return to it so that we don’t run the code below it.

It doesn’t matter what the callback returns.

For instance, instead of writing:

const foo = (err, callback) => {
  if (err) {
    callback(err);
  }
  callback();
}

We write:

const foo = (err, callback) => {
  if (err) {
    return callback(err);
  }
  callback();
}

The return ends the execution of foo so the code outside the if won’t run.

Require CamelCase

Camel case is used for most identifiers.

Variable, property, and function names should be camel case.

For instance, instead of writing:

function do_work() {
  // ...
}

or:

function foo({ no_camelcased }) {
  // ...
};

or:

const { category_id = 1 } = query;

We write:

function doWork() {
  // ...
}

or:

function foo({ camelCased }) {
  // ...
};

or:

const { categoryId = 1 } = query;

Capitalization of the First Letter of a Comment

Comments can be sentences or short clauses.

They are both fine.

We don’t always need to write complete sentences.

For instance, we can write:

// Capitalized comment

or:

/* eslint semi:off */

We can write anything in our comments.

Enforce that Class Methods Use this

If we define instance methods, we should reference this .

For instance, we can write:

class A {
  constructor() {
    this.a = "foo";
  }

  print() {
    console.log(this.a);
  }

  sayHi() {
    console.log("hi");
  }
}

instead of:

class A {
  constructor() {
    this.a = "foo";
  }

  sayHi() {
    console.log("hi");
  }
}

We should make them static if we don’t reference this :

class A {
  constructor() {
    this.a = "foo";
  }

  static sayHi() {
    console.log("hi");
  }
}

Brace Style

The most common JavaScript brace style is like this:

if (foo) {
  bar();
} else {
  baz();
}

We have the braces as the same line as the keywords.

Trailing Commas

Trailing commas make moving objects entries easier and make diffs cleaner.

So we can write:

const obj = {
  bar: "foo",
  qux: "baz",
};

Spacing Around Commas

Spacing around commas makes items easy to read.

For instance, we can write:

const foo = 1, bar = 2;

or:

const arr = [1, 2];

or:

const obj = {"foo": "bar", "baz": "qur"};

instead of:

const foo = 1,bar = 2;

or:

const arr = [1,2];

or:

const obj = {"foo": "bar","baz": "qur"};

A space character makes reading things easier.

Comma Style

We put commas after an entry.

For instance, instead of writing;

const foo = ["apples"
           , "oranges"];

or:

const foo = 1
  , bar = 2;

or:

const foo = 1
,
bar = 2;

We write:

const foo = 1,
  bar = 2;

const foo = 1,
  bar = 2;

const foo = ["apples",
  "oranges"
];

const obj =  {
    "a": 1,
    "b": 2
  };
}

We keep the commas after an item.

Limit Cyclomatic Complexity

To make our code easier to understand, we can limit the paths of the code.

For instance, we can limit the number of paths to 2.

We instead of writing:

function a(x) {
  if (true) {
    return x;
  } else if (false) {
    return x + 1;
  } else {
    return 2;
  }
}

We write:

function a(x) {
  if (true) {
    return x;
  } else if (false) {
    return x + 1;
  }
}

We reduce the number of branches in our if statements.

Spaces Inside of Computed Properties

Computed properties don’t need spaces.

For instance, we write:

const a = "prop";
const x = obj[a];

or:

const obj = {
  [a]: "value"
};

instead of:

const a = "prop";
const x = obj[ a ];

or:

const obj = {
  [a ]: "value"
};

Conclusion

We should have spaces where it’s appropriate.

Also, we should return early in our functions to stop the code below it from running.

If we have instance methods, we should reference this in it.

Comments can have any case.

Categories
JavaScript Best Practices

JavaScript Best Practices — Objects, Functions, and Arrays

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.

Getter and Setter Pairs in Objects and Classes

We should have getter and setter pairs in our objects.

Without getters, properties can’t be read, so it’s not used.

For instance, instead of writing:

const obj = {
  set a(value) {
    this.val = value;
  }
};

We write:

const obj = {
  set a(value) {
    this.val = value;
  },

  get a() {
    return this.val;
  }
};

Line Breaks After Opening and Before Closing Array Brackets

We may put line breaks before the opening and closing brackets.

It’s clearer to have them for long arrays

For instance, instead of writing:

const arr= [1, 2, 3, 4, 5];

We write:

const arr = [
  1, 2, 3, 4, 5
];

Spaces Inside of Brackets

We can line without spaces inside brackets.

So we can write:

const arr = ['foo', 'bar'];

and it’s clear to read.

Return Statements in Callbacks of Array Methods

We always need to return something in array methods.

Methods like reduce , Array.from , filter , map , some , every , etc. return things in methods.

For instance, we shouldn’t write things like:

const indexes = arr.reduce((foo, item, index) => {
  foo[item] = index;
}, {});

or:

const foo = Array.from(elements, (element) => {
  if (element.tagName === "DIV") {
    return true;
  }
});

Instead, we should write:

const sum = arr.reduce((a, b) => a + b, 0);

We return the result of the combination of the values with reduce .

Or we can write:

const foo = Array.from(elements, (element) => {
  if (element.tagName === "DIV") {
    return true;
  }
  return false;
});

Some methods like forEach don’t need to return something since we can’t do much with the return value.

But we can return early to move to the next iteration:

arr.forEach((item) => {
  if (item < 0) {
    return;
  }
  doSomething(item);
});

Line Breaks Between Array Elements

We may put long expressions in our array.

For instance, we can write:

const arr = [
  function foo() {
    doWork();
  },
  function bar() {
    doWork();
  }
];

We have 2 functions which are long, so we put them in their own line.

Braces in Arrow Function Body

Braces in the arrow function body can help with increasing clarity.

For instance, we can write:

const foo = () => {
  return 0;
};

instead of writing:

const foo = () => 0;

We know we’re returning something for sure with the return keyword.

Parentheses in Arrow Function Arguments

Parentheses are useful for delimiting parameters.

For instance, we can write:

(a) => {}

to make reading parameters easier.

A Space Before or After Arrow Function’s Arrow

We should definitely put spaces between the arrow of an arrow function.

For instance, instead of writing:

(a)=>{}

or:

a=> a;

We write:

(a) => {}

Treat var as Block Scoped

If we use var to declare variables, they should be used as block-scoped variables.

For instance, we shouldn’t write something like:

function doIf() {
  if (true) {
    var build = true;
  }

  console.log(build);
}

or:

function doIfElse() {
  if (true) {
    var build = true;
  } else {
    var build = false;
  }
}

Instead, we write:

const doIf = () => {
  var build;

  if (true) {
    build = true;
  }

  console.log(build);
}

or:

const doIfElse = () => {
  var build;

  if (true) {
    build = true;
  } else {
    build = false;
  }
}

We shouldn’t use the non-block-scope features of var like hoisting or accessing var variables outside a block in a function.

They just trick lots of people.

Spaces Inside of Blocks After Opening Block and Before Closing Block

It’s good to have spaces inside blocks to make them readable.

For instance, instead of writing:

function foo() {return false;}

We write:

function foo() { return false; }

It’s just much clearer to have the spaces at the opening and closing.

Conclusion

Single spaces and extra rows are good for readability.

We should use array methods in the way they’re intended.

Categories
JavaScript Best Practices

JavaScript Best Practices — Mutations and Security

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at why and how to avoid mutations and complexity.

Also, we look at security issues.

Don’t use Proxy

Proxy adds side effects to object operation, so we may want to avoid them to reduce complexity.

For instance, instead of writing:

const handler = {
  get(target, key) {
    return Math.max(target[key], Infinity);
  }
};
const object = new Proxy(variable, handler);
object.a;

Instead, we create a function:

const bigProperty = (target, key) => {
  return Math.min(target[key], Infinity);
}
positiveProperty(object, 'a');

Reduce the use of Rest Parameters

Rest parameters are handy because we can pass in any number of arguments into a function.

However, it’s better to have explicit parameters since they’re easier to deal with.

A function with rest parameters doesn’t work well with currying since there is an indefinite number of parameters.

Therefore, instead of using:

const sum = (...nums) => {
  return nums.reduce((a, b) => a + b);
}

We write:

const sum = (nums) => {
  return nums.reduce((a, b) => a + b);
}

nums is an array in the 2nd example.

Having one fixed parameter is better than an indefinite number of arguments in an array.

Reduce the use of this

The use of this should be reduced since the use of this means we have a mutating internal state.

Also, this is confusing in JavaScript.

Therefore, it’ll be a mess if we use it. The value of this changes according to scope.

The use of throw

We shouldn’t use throw too much. It should be reserved for issues that we can’t resolve in our app.

It also should be used for the most critical errors so that everyone can see them right away.

In other cases, we may want to return data instead of in our function.

So instead of writing:

const throwAnError = () => {
  throw new Error('error');
}

We may write:

const returnAnError() {
  return new Error('error');
}

No Unused Expressions

We shouldn’t have any unused expressions in our code.

So things like:

1 + 2

shouldn’t be in our code since it’s useless.

Instead, we should assign expressions to a variable or pass them in as arguments of functions to use them.

So we can write:

const sum = 1 + 2;

No valueOf Fields

valueOf fields are useless since it just convert a value stored in an object into a primitive value.

This is a roundabout and confusing way to create primitive values.

Instead, we should just create primitive values instead.

So instead of writing:

const obj = {
  value: 200,
  valueOf() { return this.value; }
};

We just write:

const a = 200;

Always use new with Error

Error is a constructor, so we should use new with it.

In JavaScript, it’s easy to skip new if the class syntax isn’t used.

We’ll get unexpected results if we forgot it in other places.

So to be consistent, we should use new everywhere, including when we’re creating Error objects.

For instance, instead of writing:

throw Error('foo');

We write:

throw new Error('foo');

Security Risks

There are security risks with things like regular expressions.

We should be careful when using them so that they don’t break our apps.

Regular Expression DoS and Node.js

Node.js has issues with big regular expressions which are checked against long strings.

These big expressions block the event loop and so our app will be stalled until the code finishes running.

Since Node.js is mostly single-threaded, we’ll have issues with big regex checks.

Regex with some attributes like grouping with repetition creates more issues than others.

So if we have an email regex like:

/^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/

which has many groups, then Node.js will be slow in checking strings against that regex.

Conclusion

We shouldn’t use proxies most of the time to simplify the code.

The use of this should always be reduced so as to reduce confusion and the chance of errors.

Also, we should check if a regex won’t stall our Node.js program before using it.

Categories
JavaScript Best Practices

JavaScript Best Practices — Functions and Mutations

Like any kind of apps, JavaScript apps also have to be written well. Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices for working with arrow functions. Also, we look at why and how to avoid mutations.

Don’t Use Getters and Setters

Since getters and setters introduce side effects, we shouldn’t use them if we want to make functions that are easy to work with.

For instance, we shouldn’t write:

const person = {
  name: 'james',
  get age() {
    return this._age;
  },
  set age(n) {
    if (n < 0) {
      this._age = 0;
    } else if (n > 200) {
      this._age = 200;
    } else {
      this._age = n;
    }
  }
};

Also, we should call __defineGetter__ or __defineSetter__ :

person.__defineGetter__('name', () => {
  return this.name || 'james';
});

or:

person.__defineSetter__('name', (name) => {
  this.name = name.trim();
});

Instead, we create pure functions:

const restrictAge = (n, min, max) => {
  if (n <= min) {
    return min;
  }
  if (n >= max) {
    return max;
  }
  return n;
}

const setAge = (age, person) => {
  return Object.assign({}, person, { age: restrictAge(age, 0, 120 )});
}

This way, we have all pure functions, and it’s more versatile than our setter implementation since we can change the min and max .

Reduce the Use of Loops

Most loops in JavaScript code can be rewritten with array methods.

This is because there are already many methods like map , filter , and reduce methods that can let us work with arrays in a shorter way.

Therefore, instead of writing:

for (const a of arr) {
  result.push(a * 10);
}

We can write:

const result = arr.map(a => a  * 10);

It’s much easier to use map then a for-of loop.

Don’t Use Object.assign with a Variable as the First Argument

Object.assign mutates the first argument.

Therefore, to use it in a way that doesn’t mutate existing data, we should pass in an empty object as the first argument.

Instead of writing:

const a = { foo: 1, bar: 2 };
const b = { baz: 3 };

Object.assign(a, b);

we write:

const c = Object.assign({}, a, b);

Reduce the Use of Mutating Array Methods

Since we have the spread operator and alternative array methods, we can avoid the use of mutating array methods in our code.

For example, instead of writing:

arr.pop();

We can write:

const arr = arr.slice(0, arr.length - 1);

The only one that’s harder to replace is sort . In this case, we can make a copy of it with the spread operator and then call sort on that array.

So we can write:

const copy = [...arr];
copy.sort();

Instead of splice , we may consider using filter or slice .

If we want to remove one element with the given index, we can write:

const removed = arr.filter((a, i) => i !== index);

Then we remove the item at index index and assigned it to a new variable.

Reduce the Use of Mutating Operators

It’s easy to mutate data accidentally.

So we may want to avoid using mutating operators.

For instance, we may want to avoid using the += , /= , -= , %= or *= operators.

Instead, we can use non-mutating array methods or assigning values to a new variable.

We can write:

const b = a + 10;

instead of:

a += 10;

If we have to do update a variable with new values by adding repeatedly, we can use reduce :

const total = arr.reduce((total, a) => total + a, 0);

Before Careful with null or undefined

We should always be careful with null or undefined values since they are a source of many errors.

Therefore, we should make sure a variable or property isn’t null or undefined before working with them.

To check both, we can write:

if (val === null || val === undefined){
  //...
}

or:

if (val === null || typeof val === 'undefined'){
  //...
}

Conclusion

We should avoid mutating data so that we won’t accidentally change them.

It’s also easier to test function that doesn’t mutate data.

Also, we should be careful of null or undefined .

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrow Functions

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices for working with arrow functions.

Placement of Arrow Functions

The placement of arrow functions is all about preferences.

We can put them at the top level.

Also, we can place them inside objects or functions.

This is all up to us.

So we can write:

const person = {
    getData: (id) => ajax(URL,{ id })
};

or:

export default id => person.getData(id)

or:

export const getPerson = id => person.getData(id)

or:

const getPerson = id => People.getData(id,onData)

We can place them at the top level, as exports or inside objects.

Returning Data

We should make sure that we write our return syntax properly.,

For instance, we can write:

const fn = prop => ( val => { return { [prop]: val }; } );

or:

const fn = (x, y) => {
  return (
    x > 3 ? x : y
  );
};

If we have an expression, we should put them in parentheses.

Also, we should add the return keyword unless our function only has one statement and only returns a short expression.

For instance, we can also write:

const cube = x => x ** 3;

This is also readable.

If we have arrow functions longer than that, then we should add parentheses, curly braces, and the return keyword.

The Use of this

We shouldn’t use this in callbacks that don’t need them.

For instance, if we have:

const squared = arr.map(a => a ** 2, this);

then we don’t need the this since it does nothing.

Arrow functions don’t have their own this , so this references whatever value of this is outside the function.

Therefore, this shouldn’t be at the top level since it’s going to be undefined under strict mode.

So having something like:

a => this.bar(a);

at the top level wouldn’t work.

Don’t Use the arguments Keyword

We should never use the arguments keyword.

It’s used to get all the arguments passed into a traditional function.

It doesn’t with arrow functions.

Also, it’s an array-like iterable object so it doesn’t have any array methods.

Therefore, instead of using:

function sum() {
  const numbers = [...arguments];
  return numbers.reduce((a, b) => a + b);
}

We can use the rest operator instead by writing:

const sum = (...numbers) => {
  return numbers.reduce((a, b) => a + b);
}

numbers would be an array, so we can call reduce on it.

The rest operator saves us typing and headaches.

Use of class

We can use class for constructors.

Ideally, we minimize the use of them since they hold internal state and have methods that commit side effects.

Therefore, whenever we can, we use pure functions.

So we can write:

const rectangle = (height, width) => {
  return {
    height: height,
    width: width
  };
}

Instead of:

class Rectangle{
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

Don’t Use the delete Keyword

The delete keyword removes a property from an object.

This means the object is mutated.

Therefore, it’s better to make a copy of an object without a property that we want.

Instead of writing:

delete foo.bar;

We can use something like Lodash to make a new object without a property:

const _ = require('lodash/fp');

const fooWithoutBar = _.omit('bar', foo);

Don’t Use the events Module

If we want to stick with functional programming, then we shouldn’t use the Node’s events module.

It lets us make side effects by emitting and listening to events.

Instead, we can create pure functions to pass around data.

Conclusion

Writing pure functions makes our lives easier since they don’t commit side effects.

Also, we should be careful about how we return data in arrow functions.