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.
// 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.
// 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.
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!
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
// 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.
// 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.
// good function initializeScreen({ title, subtitle, body, buttonText, callback }) { // ... }
// best function createTitle(title) {} function createSubtitle(subitile) {} function createBody(body) {} function createButton(text, callback) {}
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!