DEV Community

Cover image for 10 tips to improve readability in Javascript
TrinhDinhHuy
TrinhDinhHuy

Posted on

27 5

10 tips to improve readability in Javascript

1. Log level and semantic methods

๐Ÿ“š Console docs

console.log("hello world")
console.warn("this is a warning")
console.error("this is an error")
console.info("this is info")
console.debug("this is debug")
console.trace("show trace")
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ If you try the console.warn, you will get the trace which means that it is easier to debug the code

Let's try other console functions yourself :)

โš ๏ธ Original code

console.log("Error: API key should not be empty")
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

console.error("Error: API key should not be empty")
Enter fullscreen mode Exit fullscreen mode

2. Avoid negative names for boolean variables

๐Ÿ˜• It is hard to read double negatives

isStarted ๐Ÿคœ ๐Ÿค› isNotStarted

โš ๏ธ Original code

const isInvalidApiKey = apiKey === null

if (isInvalidApiKey) {}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

const isValidApiKey = apiKey != null

if (!isValidApiKey) {}
Enter fullscreen mode Exit fullscreen mode

3. Avoid flag params

๐Ÿ˜• You don't know what the flag params are used for util you have to read the function declaration

โš ๏ธ Original code

renderResult(true)

function renderResult(isAuthenticated) {
    if (isAuthenticated) {
       return <p>App</p>
    } else {
        return <p>Please login</p>
    }

}
Enter fullscreen mode Exit fullscreen mode

๐Ÿจ Use object params

renderResult({isAuthenticated: true})

function renderResult({isAuthenticated}) {
    if (isAuthenticated) {
        return <p>App</p>
    } else {
        return <p>Please login</p>
    }

}
Enter fullscreen mode Exit fullscreen mode

๐Ÿจ Use 2 functions

function renderAuthenticatedApp() {
    return <p>App</p>
}

function renderUnAuthenticatedApp() {
    return <p>Please login</p>
}

isAuthenticated ? renderAuthenticatedApp() : renderUnAuthenticatedApp()
Enter fullscreen mode Exit fullscreen mode

4. Use guard clauses

๐Ÿ˜• Nesting hell

๐Ÿจ Make our code fail fast
๐Ÿจ Natural flow

if (statusCode === 200) {
    // success
} else {
    if (statusCode === 500) {
        // Internal Server Error
    } else if (statusCode === 400) {
        // Not Found
    } else {
        // Other error
    }
}
Enter fullscreen mode Exit fullscreen mode
if (statusCode === 500) {
    // Internal Server Error
}

if (statusCode === 400) {
    // Not Found
}

if (statusCode !== 200) {
    // Other error
}

// success
Enter fullscreen mode Exit fullscreen mode

5. Make code self-explanatory

๐Ÿจ Easy to understand
๐Ÿจ Reusable
๐Ÿจ A long descriptive name is better than a long comment

// verify that user has added a credit card
function verify(user) {}
Enter fullscreen mode Exit fullscreen mode
function verifyThatUserHasAddedCreditCard(user) {}
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Original code

 if (country !== 'finland' &&
    country !== 'germany' &&
    country !== 'vietnam' &&
    country !== 'russia' &&
    type !== '๐Ÿ’ฃ'
) {
    return Promise.reject('Not available')
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

const isInAvailableCountries = (
    country === 'finland' ||
    country === 'germany' ||
    country === 'vietnam' ||
    country === 'russia'
)

const hasBoom = type === '๐Ÿ’ฃ'

if (!isInAvailableCountries || hasBoom) {
    return Promise.reject('Not available')
}
Enter fullscreen mode Exit fullscreen mode

๐ŸŽ Create a better condition

const availableCountries = ['finland', 'germany', 'vietnam', 'russia']
const isInAvailableCountries = availableCountries.includes(country)

const hasBoom = type === '๐Ÿ’ฃ'

if (!isInAvailableCountries || hasBoom) {
    return Promise.reject('Not available')
}
Enter fullscreen mode Exit fullscreen mode

6. Make impossible states impossible

๐Ÿจ Easy to understand

๐Ÿจ Prevent lots of bugs

๐Ÿ“š Stop using isLoading booleans

isLoading: true
isError: false

isLoading: false
isError: true

// imposible states
isLoading: true
isError: true
Enter fullscreen mode Exit fullscreen mode
const LOADING_STATE = 'LOADING_STATE'
const ERROR_STATE = 'ERROR_STATE'

const state = LOADING_STATE
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Original code

const [isLoading, setIsLoading] = React.useState(false)
const [error, setError] = React.useState(null)
const [coffee, setCoffee] = React.useState(null)

function handleButtonClick() {
    setIsLoading(true)
    setError(null)
    setCoffee(null)

    getCoffee('cappuccino', 'small', 'finland', true).then(coffee => {
        setIsLoading(false)
        setError(null)
        setCoffee(coffee)
    }).catch(error => {
        setIsLoading(false)
        setError(error)
    })
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

const state = {
    idle: 'idle',
    loading: 'loading',
    error: 'error',
    success: 'success',
}

const [error, setError] = React.useState(null)
const [coffee, setCoffee] = React.useState(null)
const [status, setStatus] = React.useState(state.idle) 

function handleButtonClick() {
    setStatus(state.loading)

    getCoffee('cappuccino', 'small', 'finland', true).then(coffee => {
        setStatus(state.success)
        setCoffee(coffee)
    }).catch(error => {
        setStatus(state.error)
        setError(error)
    })
}
Enter fullscreen mode Exit fullscreen mode

7. Use objects for long argument lists

๐Ÿจ Params order won't matter

๐Ÿจ Easy to pass optional param

function getBox(type, size, price, color) {}

getBox('carry', undefined, 10, 'red')
Enter fullscreen mode Exit fullscreen mode
function getBox(options) {
    const {type, size, price, color} = options
}

getBox({
    type: 'carry',
    price: 10,
    color: 'red'
})
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Original code

export function getCoffee(type, size, country, hasIce) {

getCoffee('cappuccino', 'small', 'finland', true)
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

function getCoffee(options) {
    const {type, size, country, hasIce} = options
}

getCoffee({
    type: 'cappuccino',
    size: 'small',
    country: 'finland',
    hasIce: true
})
Enter fullscreen mode Exit fullscreen mode

8. Use Object.assign for defaults

function getBox(options) {

    options.type = options.type || 'carry'
    options.size = options.size || 'small'
    options.price = options.price || 10
    options.color = options.color || 'red'

    const {type, size, price, color} = options
}
Enter fullscreen mode Exit fullscreen mode
function getBox(customOptions) {

    const defaults = {
        type: 'carry',
        size: 'small',
        price: 10,
        color: 'red',
    }

    const options = Object.assign(defaults, customOptions)

    const {type, size, price, color} = options
}
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Original code

export function getCoffee(type, size, country, hasIce) {

    type = type || 'cappuccino'
    size = size || 'small'
    country = country || 'finland'
    hasIce = hasIce || false
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

function getCoffee(customOptions) {
    const defaultOptions = {
        type: 'cappuccino',
        size: 'small',
        country: 'finland',
        hasIce: false
    }

    const options = Object.assign(defaultOptions, customOptions)
}

function getCoffee(options = {}) {
    const {
        type = 'cappuccino',
        size = 'small',
        country = 'finland',
        hasIce = false
    } = options
}

function getCoffee({
    type = 'cappuccino', 
    size = 'small',
    country = 'finland',
    hasIce = false
} = {}) {
}
Enter fullscreen mode Exit fullscreen mode

9. Replacing switch statements with Object literals

Honestly, I love switch as well and I don't actually know when to use switch statement vs object literals. My feeling just tells me which one to go.

Check out these 2 blogs and decide which one is better for you

๐Ÿ“š Replacing switch statements with Object literals
๐Ÿ“š Switch is ok

const handleSaveCalculation = ({key}) => {
    switch (key) {
        case 'save-copy': {
            saveCopy()
            break
        }
        case 'override': {
            override()
            break
        }
        default:
            throw Error('Unknown action')
    }
}

handleSaveCalculation({key: 'save-copy'})
Enter fullscreen mode Exit fullscreen mode
const handleSaveCalculation = ({key}) => {
    const actions = {
        'save-copy': saveCopy,
        'override': override,
        'default': () => throw Error('Unknown action')
    }

    const action = key in actions ? actions[key] : actions['default']
    return action();
}

handleSaveCalculation({key: 'save-copy'})
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Original code

let drink
switch(type) {
    case 'cappuccino':
        drink = 'Cappuccino';
        break;
    case 'flatWhite':
        drink = 'Flat White';
        break;
    case 'espresso':
        drink = 'Espresso';
        break;
    default:
        drink = 'Unknown drink';
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ Refactor

const menu = {
    'cappuccino': 'Cappuccino',
    'flatWhite': 'Flat White',
    'espresso': 'Espresso',
    'default': 'Unknown drink'
}

const drink = menu[type] || menu['default']
Enter fullscreen mode Exit fullscreen mode

10. Avoid Hasty Abstractions

๐Ÿ˜• I don't know how to create a good abstraction but I've created many bad ones

๐Ÿจ prefer duplication over the wrong abstraction

๐Ÿจ Nothing is free. The code trades the ability to change requirements for reduced duplication, and it is not a good trade - Dan Abramov

๐Ÿ“š AHA Programming

๐Ÿ“š Goodbye, Clean Code

โš ๏ธ My React Boilerplate

The code below is used to fetch an order and I am using Redux for the state management. What a boilerplate!!! Let's make an abstraction which I will regret later

Fetch an order

// Action Type
const FETCH_ORDERS_START = "FETCH_ORDERS_START";
const FETCH_ORDERS_SUCCESS = "FETCH_ORDERS_SUCCESS";
const FETCH_ORDERS_FAILED = "FETCH_ORDERS_FAILED";
Enter fullscreen mode Exit fullscreen mode
// Action
export const fetchOrder = (token) => {
    return dispatch => {
        dispatch(fetchOrdersStart);
        axios.get('/orders.json?auth=' + token).then(res => {
            dispatch(fetchOrdersSuccess(res));
        }).catch(err => {
            dispatch(fetchOrdersFailed(err));
        });
    };

}

export const fetchOrdersSuccess = (orders) => {
    return {
        type: FETCH_ORDERS_SUCCESS,
        orders: orders,
    };
};

export const fetchOrdersFailed = (error) => {
    return {
        type: FETCH_ORDERS_FAILED,
        error: error,
    };
};

export const fetchOrdersStart = () => {
    return {
        type: FETCH_ORDERS_START,
    };
};
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰๏ธ Abstraction

I dare you understand the abstraction code without clicking the link. And even go to that link, you have to read all the code to understand that abstraction.

If you want to take a deep look into this, checkout AHA Programming and Goodbye, Clean Code

// Action
const moduleName = 'order'
const path = '/order'

const {moduleActionTypes, moduleActions} = useModuleActions(moduleName, path)

function fetchOrder() {
    moduleActionTypes.getModel()    
}

function updateOrder(data) {
    moduleActionTypes.updateModel(data)
}
Enter fullscreen mode Exit fullscreen mode

Resource

Github

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (7)

Collapse
 
bytebodger profile image
Adam Nathaniel Davis โ€ข

Appreciated this post. I will only add that I personally hate if/else statements when the return could make them obsolete. In practice, this means that I would convert this:

renderResult({isAuthenticated: true})

function renderResult({isAuthenticated}) {
    if (isAuthenticated) {
        return <p>App</p>
    } else {
        return <p>Please login</p>
    }

}
Enter fullscreen mode Exit fullscreen mode

To this:

renderResult({isAuthenticated: true})

function renderResult({isAuthenticated}) {
    if (isAuthenticated) {
        return <p>App</p>
    return <p>Please login</p>
}
Enter fullscreen mode Exit fullscreen mode

And if I'm being really nitpicky, I would personally write it as this:

renderResult({isAuthenticated: true})

function renderResult({isAuthenticated}) {
    return isAuthenticated ? <p>App</p> : <p>Please login</p>;
}
Enter fullscreen mode Exit fullscreen mode

And if I'm really feeling concise, I'd write it as this:

renderResult({isAuthenticated: true})

const renderResult = ({isAuthenticated}) => isAuthenticated ? <p>App</p> : <p>Please login</p>;
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jodator profile image
Maciej โ€ข

@bytebodger The simple if-else usually don't need else so I personally would stick to if + return (first refactor) when the if or else blocks have a few lines. The second and third only if the cases are simple ๐Ÿ‘.

@dinhhuyams nice summary :)

Collapse
 
macsikora profile image
Pragmatic Maciej โ€ข

Points are mostly valid, outside of this one with switch. Switch is ok

Collapse
 
dinhhuyams profile image
TrinhDinhHuy โ€ข โ€ข Edited

Thanks for sharing. To be honest, I love to use switch a lot. Sometimes I prefer Object literals :) I don't know when it is better to use Switch or Object literals. My feeling just tells me which one to go. I added your blog to the Switch section.

Collapse
 
alekseiberezkin profile image
Aleksei Berezkin โ€ข

11.TypeScript ๐Ÿ˜‰

Collapse
 
rconr007 profile image
rconr007 โ€ข โ€ข Edited

This is awesome. Thanks for sharing.

Collapse
 
vimkestech profile image
vimkestech โ€ข

nice

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

๐Ÿ‘‹ Kindness is contagious

Please leave a โค๏ธ or a friendly comment on this post if you found it helpful!

Okay