DEV Community

Raza
Raza

Posted on • Edited on

Refactoring Javascript code with Object Literals: A Clean and Readable Approach

This is and slightly modified article i wrote few years back on medium.
link.

Introduction:

When it comes to user authentication or multiple payment types, we often encounter complex conditional statements that can quickly become hard to follow. But fear not! There's a neat trick we can use to make our code more readable and maintainable: object literals. In this article, I'll explore how object literals can simplify user authentication or any related scenarios and make our code shine.

The Initial Code:

Imagine you have a chunk of code that handles user authentication using different methods such as email, social login, and one-time passwords (OTP). Let's take a look at the initial code:

const authenticateUser = (method, credentials) => {
    if (method === 'email') {
        this.authenticateWithEmail(credentials);
    } else if (method === 'social') {
        this.authenticateWithSocial(credentials);
    } else if (method === 'otp') {
        this.authenticateWithOTP(credentials);
    } else {
        throw new Error('Invalid authentication method.');
    }
};

const authenticateWithEmail = (credentials) => {
    // Here's where we authenticate using email credentials
};

const authenticateWithSocial = (credentials) => {
    // Here's where we authenticate using social credentials
};

const authenticateWithOTP = (credentials) => {
    // Here's where we authenticate using OTP credentials
};
Enter fullscreen mode Exit fullscreen mode

Refactoring with Object Literals:

To make our code cleaner and easier to read, we can leverage the power of object literals. Let's rewrite the code using this approach:

const authenticationMethods = {
    email: this.authenticateWithEmail,
    social: this.authenticateWithSocial,
    otp: this.authenticateWithOTP
};

const authenticateUser = (method, credentials) => {
    const authenticate = authenticationMethods[method];

authenticate ? authenticate(credentials) : throw new Error('Invalid authentication method.');
};
Enter fullscreen mode Exit fullscreen mode

Let's Break it Down:

In our refactored code, we create an object literal called authenticationMethods. This handy object maps each authentication method to its corresponding authentication function. By doing this, we can easily access the appropriate authentication function based on the provided method.

Gone are the days of tangled if-else statements or lengthy switch cases. With object literals, our code becomes cleaner and more readable. If a new authentication method pops up in the future, all we need to do is update the authenticationMethods object without touching the authenticateUser function.

Conclusion:

By utilising object literals, we can transform complex conditional statements into elegant and readable code. In this article, we've seen how this technique simplifies user authentication scenarios. Embracing this approach not only enhances the maintainability of our code but also makes it more understandable and enjoyable to work with.

I'd love to hear your thoughts and answer any questions you might have. So go ahead, give it a try, and let me know how object literals elevate your code to new heights!

Alternate views

Suggested by @jonrandy

The object literal is a bit of overkill here, since you effectively already have it on this. So, it can be made even simpler:

const authenticateUser = (method, credentials) => {
  const methodName = `authenticateWith${method[0].toUpperCase() + method.slice(1)}`
  return (this[methodName] || ()=>{ throw new Error('Invalid authentication method.') })(credentials)
}
Enter fullscreen mode Exit fullscreen mode

This way also scales to new authentication methods without having to change the code (providing you stick to the naming convention you provided) - making it more maintainable still.

Top comments (4)

Collapse
 
jonrandy profile image
Jon Randy 🎖️ • Edited

The object literal is a bit of overkill here, since you effectively already have it on this. So, it can be made even simpler:

const authenticateUser = (method, credentials) => {
  const methodName = `authenticateWith${method[0].toUpperCase() + method.slice(1)}`
  return (this[methodName] || ()=>{ throw new Error('Invalid authentication method.') })(credentials)
}
Enter fullscreen mode Exit fullscreen mode

This way also scales to new authentication methods without having to change the code (providing you stick to the naming convention you provided) - making it more maintainable still.

Collapse
 
raza_devv profile image
Raza

Thank you for your comment and suggestion! I appreciate your insight regarding the object literal and its necessity in this specific scenario. You are absolutely correct that considering the functions are already accessible through "this", the object literal may not be required.

I agree that your approach utilising template literals and dynamic method invocation is an excellent way to simplify the code. By dynamically generating the method name based on the method parameter and using it to invoke the corresponding function, we can eliminate the need for the authenticationMethods object literal. This approach offers scalability and flexibility, enabling the addition of new authentication methods without the need to modify the code, as long as the naming convention is adhered to.

I truly appreciate your suggestion as it further enhances the simplicity and maintainability of the code. Thank you for sharing this alternative approach.

futher more i will attach the your comment with the main article.

Collapse
 
ant_f_dev profile image
Anthony Fung

Thanks for sharing.

This is a handy pattern - one use case where it's effective is implementing a command interpreter, where a keyword calls a function.

Collapse
 
raza_devv profile image
Raza

Sure, we can use it for many usecases.
Thank you for your comment.