HomeAboutContact
Programming
Simple Tips For Writing Clean Code
May 22, 2021
5 min

Writing clean code is not hard, you just have to care. Here are some practical tips to keep in mind that are guaranteed to improve the quality and readability of your code, with not that much effort either, dare I say.

1. Use Meaningful variables names

// bad
const d = new Date(2021, 12, 25)

If your variable name is one character, unless you’re keeping track of indices, you should probably rethink it. In this case, d doesn’t really tell us anything about what the variable is and its purpose. I want you to imagine coming across this d variable in another file that maybe it’s being imported, or even a few lines lower in the code, you’d probably have no idea what it is. Sure, maybe you can derive the fact that it’s a date from some methods that might be being used on the variable itself d.getTime() but that’s not clean code, that’s barely code, and we want our code to speak for itself, hell, we want our code to sing.

A better way to write this would be if you gave that variable a more distinct and recognizable name like christmas in this case.

// good
const christmas = new Date(2021, 12, 25)

This is arguably better because there’s no question about what this variable is, christmas is obviously a date, however, I think we can do even better than this. christmas does tell us more about what it is than d did, but it still doesn’t tell us why it exists. And this is how I want you guys to start thinking of your variables from now on, what is it, and why it exists. Why is this date important in our app, what does it signify? Maybe in our case, that’s when we’re planning on starting our Christmas sale. That’s it.

// best
const dateOfSaleStart = new Date(2021, 12, 25)

Now we know what that variable is date... and why it exists (to keep track ...OfSaleStart). Also, don’t be afraid of longer variable names, if they’re more descriptive and they help convey the information, just go for it!

I know I might be spending a bit longer than I should in just variable naming, but I can’t stress this enough, good variable naming is what separates code that just works, from good, even great code.

2. Don’t Use Magic Numbers

// bad
setTimeout(blastOff, 3600000)

If you find yourself using an unintuitive number, even if it’s just a one-off, take the time to actually give that number a name, because in this case, 3.600.000 ms doesn’t tell me anything.

// good
const MILLISECONDS_IN_AN_HOUR = 1000 * 60 * 60

setTimeout(blastOff, MILLISECONDS_IN_AN_HOUR)

Giving that number a name makes the code more readable and makes the number itself easier to search in the future! Bonus tip: Always declare your true constants in capitalized snake_case, it’s a good and common practice.

3. Don’t use Magic Strings

Similar to not using Magic Numbers, avoid Magic Strings!

// bad
fetch('/products')
// ...
const shouldDelete = action === 'delete'

Whether it’s a URL or any other string where mistyping a single character would ‘cause unpredictable outputs, store that in a variable.

// good
const DELETE_ACTION = 'delete'

fetch(getProductsRoute)
// ...
const shouldDelete = action === DELETE_ACTION

One issue with Magic Strings is that if a magic string is being written in multiple places then you have to change all of them without any safety (such as a compile-time error, for example). If we’re only declaring it in one place and we just reuse the variable, then that’s no longer an issue! Also, like I previously mentioned, typos can ‘cause some pretty nasty bugs, and trying to hunt down a typo, especially when you don’t even know it’s there, is not fun. Also, strings are rarely self-documenting, so by giving it a descriptive variable name as we discussed, we make our code readable and cleaner too!

4. Prefix booleans with is/has/should

Those are just some of the prefixes you could use, but the general idea here is that you want a boolean’s variable name to invoke a question with a binary answer, true, or false.

// bad
const fileMounted = false // maybe an object ???

fileMounted doesn’t really give us any hints on whether this is a boolean or not, it could just as easily be intepreted to be an object, so an easy way to keep your booleans easily recognizable is to prefix them with is, has or should.

// good
const isFileMounted = false

5. Use Default Parameters

// bad
function isOverMinLength(password, min) {
  const minLength = min || 8
  return password.length > minLength
}

Default parameters are one of the many gifts of ES6! They are cleaner than short-circuiting and there’s a lot less mental mapping involved. Short-circuiting can cause unpredictable outcomes if we pass in a falsy value for that parameter, which in this example if we were to pass in 0 as the second argument to the isOverMinlength function, the minLength would default to 8, which is not what we want.

// good
function isOverMinLength(password, minLength = 8) {
  return password.length > minLength
}

Instead, we can just use a default parameter and we’re guaranteed that the minLength will only be 8 if the user does not pass a second argument to the function.

6. Functions should have 3 or fewer arguments (ideally)

// bad
function initializeScreen(title, subtitle, body, buttonText, callback) {
  // ...
}

Functions with many parameters are hard to reason about. Having fewer arguments means that your functions will be easier to read and understand. Uncle Bob says that 3 are the maximum acceptable arguments and although I agree with the affirmation, I understand that it’s idealistic. The point is that functions should not be doing too many different things, and the fewer parameters we have, the fewer responsibilities a function has. Another issue that many parameters cause is that we can easily lose track of the order that those parameters are supposed to go in. There are actually two things we can do to combat this problem.

  • The no-pain, easy-gain solution is turning those different arguments into a single object, also known as the Parameter Object pattern. This at least takes care of the parameter order issue, since in this case, we’re being explicit about which property is of what value, but that this function is still doing too many things still exists.
// good
function initializeScreen({ title, subtitle, body, buttonText, callback }) {
  // ...
}
  • The best, but more time-consuming solution is to have our function adhere to the Single Responsibility Principle by refactoring the function, making it smaller, which would also reduce the number of arguments. We can achieve this by using the Extract Method technique that basically tells us that we can extract chunks of logic into their own individual functions that handle only one thing.
// best
function createTitle(title) {}
function createSubtitle(subitile) {}
function createBody(body) {}
function createButton(text, callback) {}

7. Use Async/Await

We’ve come a long way since the time of callbacks, and boy do we not miss them at all. Not using callbacks in the time of promises is a no-brainer. Callbacks were just, plain ugly. It made our code harder to read, because of the many levels of nesting, and harder to reason about. The more levels of indentation you have, the harder it is to understand what a given piece of code does.

// bad
api(function (result) {
  api2(function (result2) {
    api3(function (result3) {
      // ...
    })
  })
})

That’s when promises came along and all was right in the world. Promises opened up a whole new world of possibilities, and even though things were good, we knew that things could be even better.

// good
api
    .then(result => api2())
    .then(result2 => api3())
    .then(result3 => /* ... */)
    .catch(err => handleError(err);

And that’s when async/await came into our lives. Although async/await is really nothing more than just syntactic sugar, it still makes our code cleaner since there’s now even less nesting involved, and gives us the illusion that our code is synchronous, which means we can read it “top-down”.

// best
async function callApis() {
  try {
    let result = await api()
    let result2 = await api2(api1Result)
    let result3 = await api3(api2Result)
    return result3
  } catch (err) {
    handleError(err)
  }
}

One could argue that having to wrap async/await in a try/catch block is not pretty, since we’re introducing a level of nesting. Personally, I think that the benefit of “synchronous-looking” code still outweighs that one level of indentation, I still have a bonus tip for you that I actually don’t see a lot out there, but it’s still perfectly valid javascript. You can combine await and .catch().

// bester??
async function callApis() {
  let result = await api().catch(err => handleApiError(err))
  let result2 = await api2(api1Result).catch(err => handleApi2Error(err))
  let result3 = await api3(api2Result).catch(err => handleApi3Error(err))
  return result3
}

You can just chain a .catch() after your await-ed promise and that will catch any rejections that the promise might throw. In fact, I recommend using await like this because this gives us the ability to handle different errors for different promises accordingly. In any case, try/catch or .catch(), both are perfectly valid.

And those are the 7 tips I had for you, that are guaranteed to elevate the quality of your code. See you next time!


Related Posts

The Complete Guide on Becoming a Developer in 2022
January 12, 2022
13 min

Matheo Dodi

© 2022, All Rights Reserved.

Quick Links

HomeAboutContact

Social Media