<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1240251502728721&amp;ev=PageView&amp;noscript=1"> Skip to main content

Tech log: 10 tips to improve readability in Javascript

The Data Handbook

How to use data to improve your customer journey and get better business outcomes in digital sales. Interviews, use cases, and deep-dives.

Get the book
Author avatar

Huy Trinh

Software development

LinkedIn

blog-tech-log-javascript-readability-hero

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")

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

Try other console functions yourself :)

โš ๏ธ Original code

console.log("Error: API key should not be empty")

๐Ÿ‘‰ Refactor

console.error("Error: API key should not be empty")

2. Avoid negative names for boolean variables

๐Ÿ˜• It's hard to read double negatives

isStarted ๐Ÿคœ ๐Ÿค› isNotStarted

โš ๏ธ Original code

const isInvalidApiKey = apiKey === null

if (isInvalidApiKey) {}

๐Ÿ‘‰ Refactor

const isValidApiKey = apiKey != null

if (!isValidApiKey) {}

3. Avoid flag params

๐Ÿ˜• You don't know what the flag params are used for until 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>
}

}

๐Ÿจ Use object params

renderResult({isAuthenticated: true})

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

}

๐Ÿจ Use 2 functions

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

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

isAuthenticated ? renderAuthenticatedApp() : renderUnAuthenticatedApp()

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
}
}
 
if (statusCode === 500) {
// Internal Server Error
}

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

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

// success

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) {}
 
function verifyThatUserHasAddedCreditCard(user) {}

โš ๏ธ Original code

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

๐Ÿ‘‰ Refactor

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

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

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

๐ŸŽ 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')
}

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
 
const LOADING_STATE = 'LOADING_STATE'
const ERROR_STATE = 'ERROR_STATE'

const state = LOADING_STATE

โš ๏ธ 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)
})
}

๐Ÿ‘‰ 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)
})
}

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')
 
function getBox(options) {
const {type, size, price, color} = options
}

getBox({
type: 'carry',
price: 10,
color: 'red'
})

โš ๏ธ Original code

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

getCoffee('cappuccino', 'small', 'finland', true)
}

๐Ÿ‘‰ Refactor

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

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

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
}
 
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
}

โš ๏ธ Original code

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

type = type || 'cappuccino'
size = size || 'small'
country = country || 'finland'
hasIce = hasIce || false
}

๐Ÿ‘‰ 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
} = {}) {
}

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. I decide which one to go with based on my gut feeling.

Check out these 2 blogs to 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'})
 
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'})

โš ๏ธ 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';
}

๐Ÿ‘‰ Refactor

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

const drink = menu[type] || menu['default']

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 that 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";
 
// 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,
};
};

๐Ÿ‘‰๏ธ Abstraction

I dare you to understand the abstraction code without clicking the link. Even after you follow 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)
}

Resource

Github



Enjoyed the blog? Maybe you would be interested in working at Columbia Road! We're constantly looking for nice and skilful people to work with โ€“ and to have some good banter with! If you are interested in reading more about the work we do at Columbia Road, have a look at our client cases.

Join Columbia Road

The Data Handbook

How to use data to improve your customer journey and get better business outcomes in digital sales. Interviews, use cases, and deep-dives.

Get the book