Fire tips tagged “readability

Removing duplication with ternary operators

One use of the ternary operator is to reduce duplication. If we’d call the same function with a different parameter in each case, we can move the ternary into the function call.

Watch out for readability: the shortest version isn’t always the best option.

// we want to wish people a happy weekend/workweek depending on the day
const isWeekend = day === 'Saturday' || day === 'Sunday'

// we can call `console.log` with the respective message like this
isWeekend ? console.log('Happy weekend!') : console.log('Happy workweek!')

// `console.log` happens in any case, so we can move the ternary inside it
console.log(isWeekend ? 'Happy weekend!' : 'Happy workweek!')

// we can extract the duplicate “Happy ” and “!” to only write them once
console.log(`Happy ${isWeekend ? 'weekend' : 'workweek'}!`)

// don’t go crazy and extract EVERY duplication; the code becomes shorter
// if we extract the duplicate “w”, but also much harder to read
console.log(`Happy w${isWeekend ? 'eekend' : 'orkweek'}!`)

Redundant comparisons to true or false

If an expression already returns a Boolean value, comparing that result to true or false is redundant. Leave it out to make your code shorter and more readable.

// before: comparing the Boolean result to `true` or `false` is redundant
if (number < 10 === true) { /* code */ }

// after: using the comparison’s result directly is shorter and more readable
if (number < 10) { /* code */ }


// before: the ternary operator is redundant in this assignment as well
const isHello = string === 'Hello' ? true : false

// after: the comparison is already `true` or `false`, which we can use directly
const isHello = string === 'Hello'


// before: this if-else-block only returns the result of a conditional chain
const isTheWeekend = day => {
  if (day === 'Saturday' || day === 'Sunday') {
    return true
  } else {
    return false
  }
}

// after: our function can return the value of the conditional chain directly
const isTheWeekend = day => day === 'Saturday' || day === 'Sunday'

Assignment operators shorthand

JavaScript has many different assignment operators that are shorthand for longer versions. += is one of the more common ones. Most other calculations also have an assignment operator.

// regular assignment assigns the right value to the variable on the left
a = b

// we can shorten the code if the variable is itself part of the calculation
a = a + 5    // is the same as:
a += 5

// these are some of the available shorthand assignment operators
a += b    // a = a + b       addition assignment
a -= b    // a = a - b       subtraction assignment
a *= b    // a = a * b       multiplication assignment
a /= b    // a = a / b       division assignment
a %= b    // a = a % b       remainder assignment

a &&= b   // a = a && b      logical AND assignment
a ||= b   // a = a || b      logical OR assignment

Extracting complex logic to named functions

If a piece of code looks cryptic and is difficult to understand, put it in a function. By giving that function a descriptive name, your code becomes much more readable. A good name can make a long comment unnecessary.

// We can get today’s date in a specific timezone with `.toLocaleString()`.
// The code looks complicated, and we might not remember what it does two
// months from now.

const now = new Date()
const nowAsString = now.toLocaleString('en-US', {
  timeZone: 'America/Los_Angeles'
})
nowAsString.match(/\d{1,2}\/\d{1,2}\/\d{4}/)[0]
// ⇒ '8/15/2020'

// Putting this code into a function makes it easily reusable. We don’t need
// to write a comment for it because the name already explains what it does.
// It’s okay to condense the code and skip a few variables in this case.

const getDateInLosAngeles = () => (new Date()).toLocaleString('en-US', {
  timeZone: 'America/Los_Angeles'
}).match(/\d{1,2}\/\d{1,2}\/\d{4}/)[0]

// Getting the date with one function call is more convenient and readable.
getDateInLosAngeles()
// ⇒ '8/15/2020'

Composing functions to run in sequence

If you need to nest functions, or store their output only to pass it to another function, you can instead compose a function that runs an input through many functions in sequence.

// these functions all take a string and do something with it
const reverse = name => [...name].reverse().join('')
const upcase = name => name.toUpperCase()
const greet = name => `Hi, ${name}!`

// we could store each result and pass it to the next function
const reversed = reverse('john')
const upcased = upcase(reversed)
greet(upcased)
// ⇒ 'Hi, NHOJ!'

// if we nest them like this instead, they are run from inside to outside
greet(upcase(reverse('john')))
// ⇒ 'Hi, NHOJ!'

// this takes several functions and runs an input through them in sequence
const compose = (...fns) => input => {
  return fns.reduce((result, fn) => fn(result), input)
}

// with our `compose`, we can list the functions and call them like this
compose(reverse, upcase, greet)('john')
// ⇒ 'Hi, NHOJ!'

// the order of functions defines the order the steps are executed in
compose(greet, reverse, upcase)('john')
// ⇒ '!NHOJ ,IH'

// we can also assign our composed function to a variable and reuse it
const sequence = compose(upcase, greet, reverse)
sequence('john')  // ⇒ '!NHOJ ,iH'
sequence('paul')  // ⇒ '!LUAP ,iH'

The optional-chaining operator

We can get nested values from objects by chaining their keys. We run into an error when we try to get a value from something that does not exist. With optional chaining, these calls return undefined instead of throwing errors.

// this person’s address is nested in the outer object as another object
const mario = {
  occupation: 'Plumber',
  address: {
    street: 'Rainbow Road',
    zip: '81664'
  }
}

// we can get the full address, or drill into it to get the street
mario.address         // ⇒ { street: 'Rainbow Road': zip: '81664' }
mario.address.street  // ⇒ 'Rainbow Road'

// we don’t have a nested address for this person
const toad = {
  occupation: 'Explorer'
}

// if we try to get the street, JavaScript will throw an error
toad.address         // ⇒ undefined
toad.address.street  // TypeError: undefined is not an object

// the ?. operator returns `undefined` if it hits any undefined value
toad?.address          // ⇒ undefined (same as without the questionmark)
toad?.address?.street  // ⇒ undefined (no error!)

Plucking properties from object arrays

One of the more common uses for Array.prototype.map() is extracting properties from objects. Instead of using individual arrow functions, we can create a reusable function that does the plucking for us.

const countries = [
  { name: 'France', capital: 'Paris'  },
  { name: 'Spain',  capital: 'Madrid' },
  { name: 'Italy',  capital: 'Rome'   }
]

// we can extract the attributes with individual arrow functions
countries.map(country => country.name)     // ⇒ ['France', 'Spain', 'Italy']
countries.map(country => country.capital)  // ⇒ ['Paris', 'Madrid', 'Rome']

// this function allows us to write that arrow function shorter
const pluck = property => element => element[property]

countries.map(pluck('name'))     // ⇒ ['France', 'Spain', 'Italy']
countries.map(pluck('capital'))  // ⇒ ['Paris', 'Madrid', 'Rome']

Array methods shorthand

JavaScript’s array methods pass their values through to the functions given to them. If you would only forward those to another function, you can skip the arrow function and name that function directly.

const numbers = [4, 8, 15, 16, 23, 42]
const isEven = n => n % 2 === 0

numbers.filter(number => isEven(number))  // ⇒ [4, 8, 16, 42]
numbers.filter(isEven)                    // ⇒ [4, 8, 16, 42]

Destructuring in assignment

If the name of your variable is the exact same as the property you would extract from an object, you can use destructuring instead. That way, you don’t have to type the same word twice.

const person = {
  name: 'Bob',
  age: 35
}

// without destructuring
const age = person.age

// with destructuring
const { age } = person

Rewriting switch statements to lookup objects

Some switch-statements can be replaced with lookup-tables in JavaScript. If every case immediately returns a value, this pattern achieves the same in fewer lines of code.

// before: switching through the values to return a number requires a lot of code
const getFeetForAnimal = animal => {
  switch (animal) {
    case 'bird':
      return 2
    case 'dog':
      return 4
    case 'fish':
      return 0
    case 'spider':
      return 8
  }
}

// after: a lookup object and implicit `return` do the same in fewer lines
const getFeetForAnimal = animal => ({
  bird: 2,
  dog: 4,
  fish: 0,
  spider: 8
})[animal]

getFeetForAnimal('dog')     // ⇒ 4
getFeetForAnimal('gorilla') // ⇒ undefined (does not exist in lookup table)

Removing duplication with higher-order functions

By splitting a list of parameters into groups, we end up writing less, more readable code. We can also use this to quickly create many similar functions.

// We could write a function that gives us the correct pluralization of a
// word for a given amount like this:
const pluralize = (singular, plural, count) => {
  return count === 1 ? singular : plural
}

// We would call it like this, but we would have to repeat a lot of values:
pluralize('dog', 'dogs', 0) // ⇒ 'dogs'
pluralize('dog', 'dogs', 1) // ⇒ 'dog'
pluralize('dog', 'dogs', 2) // ⇒ 'dogs'


// Let’s split the parameters into two groups, adding an arrow between them:
const pluralize = (singular, plural) => count => {
  return count === 1 ? singular : plural
}

// Because we have two arrows now, we also need two sets of parentheses.
// This works exactly as before, but so far looks like _more_ work for us:
pluralize('dog', 'dogs')(0) // ⇒ 'dogs'
pluralize('dog', 'dogs')(1) // ⇒ 'dog'
pluralize('dog', 'dogs')(2) // ⇒ 'dogs'

// Because this is really two function calls, we can extract the first one:
const pluralizeDog = pluralize('dog', 'dogs')

// With this, we don’t have to repeat 'dog' and 'dogs' every time:
pluralizeDog(0) // ⇒ 'dogs'
pluralizeDog(1) // ⇒ 'dog'
pluralizeDog(2) // ⇒ 'dogs'

// We can use this to make all kinds of pluralization-functions:
const pluralizeTable = pluralize('table', 'tables')
const pluralizeHouse = pluralize('house', 'houses')
const pluralizeSheep = pluralize('sheep', 'sheep')

Omitting optional else-branches

Sometimes, the else in an if-else is optional. When the if-condition makes the function end, we don’t have to nest the alternative case in else at all.

const getWeekdayMood = isMonday => {
  if (isMonday) {
    // If the condition is `true`, the function returns a value and ends.
    return 'Yes, a new week!'
  } else {
    // This code only happens if the condition is `false`.
    return 'Time for another Monday soon!'
  }
}


const getWeekdayMood = isMonday => {
  if (isMonday) {
    // Same deal: if this returns, the function ends. Nothing else happens.
    return 'Yes, a new week!'
  }

  // Anything here also only happens if the condition is `false`. We don’t
  // need to nest our code in an `else` for that.

  // Look ma, no `else`!
  return 'Time for another Monday soon!'
}

Passing parameters as a single object

We can make a function more readable by passing it a single object instead of independent parameters. Every time we call this function, we can see what each parameter means by its key on that object.

// This function takes five parameters. In the body of the function, they
// can be accessed with names that describe what each value represents.
const hostGuest = (age, hoursAwake, isHungry, isTired, name) => {
  if (isTired) {
    // let them rest
  } else if (isHungry) {
    // give them food
  }
}

// When using that function, that descriptiveness is lost. Without looking
// at the function, we have to remember what each value means. Is John 30 or
// 19? Are they hungry or tired?
hostGuest(30, 19, false, true, 'John')


// By wrapping the parameters in curly brackets, the function now accepts a
// single object instead of five independent values. We can still access
// them exactly as we did before.
const hostGuest = ({ age, hoursAwake, isHungry, isTired, name }) => {
  if (isTired) {
    // let them rest
  } else if (isHungry) {
    // give them food
  }
}

// When using _this_ function, we have to pass it an object. The keys of
// that object help us describe what attribute each value represents.
hostGuest({
  age: 30,
  hoursAwake: 19,
  isHungry: false,
  isTired: true,
  name: 'John'
})

Hiding repetition with helper functions

Have to use a function over and over, but one or more of its parameters are always the same? Improve readability by creating a function that hides that repetition.

// Repeating `en_US` every time is tedious and prone to typos.
const title = translate('app.title', 'en_US')
const slogan = translate('app.slogan', 'en_US')
const action = translate('app.action', 'en_US')

// While `translate` takes two parameters, this function only takes one. It
// returns the result we would get if we called `translate` with `en_US`.
const translateEN = key => translate(key, 'en_US')

// We can call our function as before, but no longer need to write `en_US`.
const title = translateEN('app.title')
const slogan = translateEN('app.slogan')
const action = translateEN('app.action')

Namespacing styled-components

If you keep your styled-components in a separate file, you can import them under a namespace like “ui”. If a component’s name then begins with ui., you can then tell it is one of your styled-components.

/* ./MyComponent/styles.js */
import styled from 'styled-components'

export const Title = styled.h1`
  font-size: 2rem;
  line-height: 1.35;
`

export const Description = styled.p`
  font-size: 1.6rem;
  line-height: 1.5;
`
/* ./MyComponent/index.js */
import React from 'react'
import SomeOtherComponent from '../SomeOtherComponent'
import * as ui from './styles'

const MyComponent = ({ title, description }) => {
  return (
    <div>
      <ui.Title>
        {title}
      </ui.Title>

      <ui.Description>
        {description}
      </ui.Description>

      <SomeOtherComponent />
    </div>
  )
}

export default MyComponent

Replacing complex conditions with descriptive variables

A few well-named variables go a long way towards making code more self-explanatory. Readability is worth a few extra lines; your future self will thank you!

// before: it’s hard to see what this combination of conditions does
if (!(currentDay === 'Saturday' || currentDay === 'Sunday') &&
    currentHour >= 9 && currentHour <= 18) {
  return 'The store is open!'
}


// after: by assigning the conditions to variables, we can use their names
// to explain the logic behind our code in plain language
const isSaturday = currentDay === 'Saturday'
const isSunday = currentDay === 'Sunday'

const isWeekend = isSaturday || isSunday

const isDuringBusinessHours = currentHour >= 9 && currentHour <= 18

if (!isWeekend && isDuringBusinessHours) {
  return 'The store is open!'
}

Using Boolean prefixes for variables

Prefixes like “is”, “has”, or “does” help emphasize that a variable holds a Boolean value.

if (monday) {
  // `monday` could be the date of next Monday. You wouldn’t expect this to
  // be `true` or `false`.
}

if (isMonday) {
  // This reads like the question “is Monday?”, which you would answer with
  // “yes” or “no”. The name tells you that this has to be Boolean.
}


if (children) {
  // `children` could be a list of names or whether a person has children or
  // not. It’s difficult to tell if this is an array or a Boolean.
}

if (hasChildren) {
  // You can only read this as “has children?”, which is either `true` or
  // `false`. Ideally.
}


if (rocks) {
  // Could be the name of your favorite rocks, like “The” or “30”.
}

if (doesRock) {
  // This does rock, `true`. Note how it’s not called `doesRocks`, which
  // would read a little awkward.
}