Javascript Better Code

Javascript Better Code

Programming is not just about writing complex code in a text editor; it's about creating something special in order to solve a problem or fulfill a need. As well as writing code that works, we also be aware of how to write code that is easy to understand and easy to escalate.

Let's review five key concepts to help you write better code.

1] Don't compress your code, webpack will do that for you

Let's see the following code:

1// * Please don't do this *
2// This is the object to store a office reservation state
3let ofcSts = { ena: true, resBy: '' };
4
5// This function is used to reserve the office sending an user
6const resOfc = (u) => { if(ofcSts.ena) { ofcSts = { ena: false, resBy: u } } else { return; } };
7
8resOfc('Mike');
9console.log(ofcSts);
10

First, they are defining an object using abbreviations in the name and the property names. In order to 'reduce' code. Then, we can see a function with a condition and an object operation all in the same line.

This code works, but we are using useless var names. When I see this kind of code, I just think it was generated automatically by some kind of complex script. Is completely incomprehensible.

You may be thinking that the comments should clarify the code proposal. But, we just get more annoying words in the code. Remember that our code should be clear enough to avoid writing comments.

Let's improve this code:

1// Do this instead
2let officeStatus = { 
3  enabled: true, 
4  reservedByUser: '' 
5};
6
7const reserveOffice = (user) => { 
8  if(officeStatus.enabled) { 
9    officeStatus = { 
10      enabled: false, 
11      reservedByUser: user 
12    } 
13  } else {
14    return;
15  }
16};
17
18reserveOffice('Mike');
19console.log(officeStatus);
20

The code looks longer, but don't worry about that. webpack will compress it for you in build time.

2] Make it easier to return

Let's check the following piece of code:

1// * Please don't *
2const findBook = (bookId) => {
3  if(bookId !== undefined || bookId !== null) {
4    const book = {
5      id: bookId,
6      name: 'Book title'
7    };
8    return book;
9  } else {
10    return null;
11  }
12};
13

You may think that there's nothing wrong with this code, and you're right. The code is easy to understand and works. Nevertheless, this code could be improved by inverting the condition logic.

First, let's check if the input data is wrong. Then let's work with the certainty that the data is correct. Like this:

1// * Do this instead *
2const findBook = (bookId) => {
3  if(!book) {
4    return null;
5  }
6
7  const book = {
8    id: bookId,
9    name: 'Book title'
10  };
11  return book;
12};
13

Writing code is like writing a book. You could say that If the book is not undefined or if the book is not null, return the book's information otherwise return null, or you could say If there isn't a book, return null, otherwise return the book. Both cases do the trick, but which is more easier to read?

3] Please don't repeat yourself

The DRY principle is applicable to all kinds of code. Nevertheless, let's see the following case:

1// * Please don't *
2const invitationList = ['Kevin', 'Sara', 'Steve', 'Karen'];
3const capacity = 10;
4
5const getPartyAccess = (person) => {
6  if(invitationList.length < capacity && invitationList.includes(person.name)) {
7    return 'Access granted';
8  }
9
10  if(invitationList.length < capacity && person.hasMoney()) {
11    return 'Access granted';
12  }
13
14  return 'Access denied';
15};
16
17const verificationBad = getPartyAccess(
18  { 
19    name: 'Andres', 
20    hasMoney: () => { return false } 
21  }
22);
23console.log(verificationBad);
24

In getPartyAccess we can see two things that are repeated: invitationList.length < capacity and return 'Access granted';. Therefore, this code could be improved.

Let's refactor the code to:

1// Do this instead
2const getPartyAccess = (person) => {
3  if(invitationList.length < capacity 
4    && (invitationList.includes(person.name) || person.hasMoney())) {
5    return 'Access granted';
6  }
7
8  return 'Access denied';
9};
10
11const verification = getPartyAccess({ name: 'Andres', hasMoney: () => { return false } });
12console.log(verification);
13

We are reducing the two if conditions to just one single condition. Also, we are integrating the positive return value once.

Remember, if you see duplications in your code, you are doing something wrong.

4] Get used to destructuring

Destructuring is one of the most popular features of ES. This is just syntactic sugar that helps us write better code. Let's see:

1// * Please don't *
2const client = {
3  name: 'Andres',
4  budget: 1000,
5  references: ['Nicolas', 'Ellen']
6}
7
8const verifyCreditBad = (client) => {
9  if(client.budget > 500 && client.references.length === 2) {
10    return {
11      name: client.name,
12      approved: true,
13      mainContact: client.references[0],
14      optionalContact: client.references[1]
15    }
16  } else {
17    return {
18      approved: false
19    }
20  }
21};
22
23console.log(verifyCreditBad(client));
24

Just as the DRY principle explained in the last example, we can see that we are repeating the word client a lot. Let's use destructuring to fix it.

1// Do this instead
2const verifyCredit = (client) => {
3
4  const { budget, references, name } = client;
5  const [ mainReference, optionalReference ] = references;
6
7  if(budget > 500 && references.length === 2) {
8    return {
9      name,
10      approved: true,
11      mainContact: mainReference,
12      optionalContact: optionalReference
13    }
14  } else {
15    return {
16      approved: false
17    }
18  }
19};
20
21console.log(verifyCredit(client));
22

In line 4, we are using object destructuring to get all the values of the object in order to get rid of the word client in the assignment of the values.

Also, we are using array destructuring based on positions to get the two only values of the references array.

And with a single destructuring process, we improve the code readability.

5] The fewer the switches, the better

This final concept is more of an alternative to the switch than a code improvement. Let's see what this is about:

1const executeAction = (action) => {
2  switch(action) {
3    case 'create':
4      return {
5        created: true,
6        message: 'Item created successfully'
7      };
8    case 'delete': {
9      return {
10        deleted: true,
11        message: 'Item deleted successfully'
12      }
13    }
14    case 'update': {
15      return {
16        updated: true,
17        message: 'Item updated successfully'
18      }
19    }
20    default: {
21      return {
22        error: true,
23        message: 'Invalid action'
24      };
25    }
26  }
27};
28
29console.log(executeAction('sd'));
30

Like I just said, there is nothing wrong with this piece of code. Lastly, I'm trying to avoid this kind of code. Instead, we could create an object map. Like this:

1const actionMap = {
2  create: () => {
3    return {
4      created: true,
5      message: 'Item created successfully'
6    }
7  },
8  delete: () => {
9    return {
10      deleted: true,
11      message: 'Item deleted successfully'
12    }
13  },
14  update: () => {
15    return {
16      updated: true,
17      message: 'Item updated successfully'
18    }
19  }
20}
21
22const executeActionMap = (action) => {
23  if(!Object.keys(actionMap).includes(action)) {
24    return {
25      error: true,
26      message: 'Invalid action'
27    };
28  }
29
30  return actionMap[action]();
31}
32
33console.log(executeActionMap('create'));
34

In this code, we are just creating an object with the different options as keys. Then, we can just select the action searching in the object by key.

Summary

This was just a couple of concepts to improve our code, but there is more. Always remember:

  • Don't use abbreviations.
  • Reduce the complexity of your conditions.
  • Don't repeat yourself
  • Use more destructuring

Thanks. You can see the full code in this repository.