This post was originally published at kais.blog.
Let's move your learning forward together! Follow me on Twitter for your daily dose of developer tips. Thanks for reading my content!
As a developer, you'll spend much more time reading code than writing it. That's why it's important to write code that's quick to grasp and easy to maintain. In this post, I want to introduce you to 7 tips that will help you create a clean and robust codebase.
Please note that these are opinionated guidelines. Some of you may not agree with me. In general, these tips will not be universal. Also, this list is not exhaustive at all.
1. Use meaningful names
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
Yeah, naming things is hard. However, meaningless names will trigger chaos in the long run. Whenever you have to choose a name - be it for a variable, a class, a function or anything else - please use meaningful names. The name should tell you the purpose and context.
❌ Bad
function su(e, pw) {
const u = new U(e, pw);
// What the §*{$ is this?
}
✔️ Good
function signup(email, password) {
const user = new User(email, password);
// Ah, now I understand!
}
2. Replace magic numbers with constants
What is a magic number? A magic number is a hard coded numeric value. It's an anti-pattern and obscures the developer's intent. Thus, it should be replaced with a constant that describes its purpose. See, you can instantly apply your knowledge from the first tip.
❌ Bad
for (let i = 0; i < 52; i++) {
// ...um, why again do we use `52` here?
}
✔️ Good
const DECK_SIZE = 52;
for (let i = 0; i < DECK_SIZE; i++) {
// It's about a deck of playing cards.
}
Here, you may ask why i = 0
is okay. Well, I'd count this as acceptable use. The intent here is clear - using i
and initializing it with 0
is widely known among developers.
3. Do not use boolean flags to determine behavior
Often, you encounter a function that has two very similar behaviors. To switch between those, you might be tempted to simply add a boolean flag. However, this makes your code less readable and harder to understand. Try to split the function into two functions without the flag instead.
❌ Bad
function loadSchema(schema, sync = false) {
//
}
// One eternity later…
loadSchema("…", true);
// Wait, what is `true` here? Sync? Async?
// Something else? I'm so forgetful.
✔️ Good
function loadSchema(schema) {
//
}
function loadSchemaSync(schema) {
//
}
// One eternity later…
loadSchemaSync("…");
// Ah, it's the synchronous variant.
4. Reduce nesting in your code
Nesting makes code harder to read and especially harder to understand. With some simple tricks you can reduce nesting to a minimum.
❌ Bad
async function handle(request) {
if (request.user) {
if (request.user.can("CREATE_POST")) {
// Wow, this is deep.
// Handle request here.
} else {
// User is not authorized.
return new Response({ status: 403 });
}
} else {
// User is not authenticated.
return new Response({ status: 401 });
}
}
✔️ Good
async function handle(request) {
if (!request.user) {
// User is not authenticated.
return new Response({ status: 401 });
}
if (!request.user.can("CREATE_POST")) {
// User is not authorized.
return new Response({ status: 403 });
}
// We can safely assume the user
// is authenticated and authorized.
// Handle request here.
}
5. Make use of newer language features
JavaScript is constantly changing. This brings you awesome new features that can improve your codebase. You can use destructuring, classes, the async-await syntax, the numeric separator and much more. My favorites are probably the spread-Operator (...
), the optional-chaining operator (?.
) and nullish-coalescing (??
).
❌ Bad
// Assigning a default value should be easier...
const port = typeof config.port !== "undefined" ? config.port : 3000;
// Did I mess up? It's nine zeros, right?
const oneBillion = 1000000000;
// Deep properties and nesting...urghs
if (user.team) {
if (user.team.subscription) {
if (user.team.subscription.invoices) {
//
}
}
}
✔️ Good
// Let's use nullish-coalescing (`??`).
const port = config.port ?? 3000;
// The numeric separator makes it easy to tell.
const oneBillion = 1_000_000_000;
// Here, we can use optional-chaining.
if (user.team?.subscription?.invoices) {
//
}
Note that you cannot use optional-chaining on a non-existent root object. So if user
could be undefined
, we'd have to check with something like typeof user !== "undefined"
first.
6. Make your code easy to refactor
Refactoring is the restructuring of your code without changing the observable behavior. To make this easy, you should consider writing automated tests. Therefore, you can use testing frameworks like Jest. If you are using automated tests you can verify that your code is behaving like you'd expect.
Then, you are ready for refactoring. You can change your code however you want. As long as your tests are passing, everything is fine. This should enable you to be confident about your codebase. No more fear that you are accidentally breaking something.
Unfortunately, setting up a testing framework like Jest is beyond the scope of this article. If you want, I can create a post about testing (and refactoring) your JavaScript code.
7. Use ESLint
This is the final tip of this post. Use this awesome tool called ESLint. It's free and easy to use and surely will improve your codebase. It detects and fixes common problems. Also, you can install useful presets and plugins to detect even more and reformat your code according to a style guide.
I use ESLint with plugins for standard and prettier. Besides, if I'm working with Vue, I'll add eslint-plugin-vue. Unfortunately, explaining the installation and configuration of ESLint is also beyond the scope of this article. Tell me, if you'd like to hear more about this.
Bonus: Consider using TypeScript
If you've read any of my posts in the past, you might know that I'm using TypeScript, a superset of JavaScript. It's basically JavaScript on steroids and helps you writing more robust and maintainable code. If you are still undecided, take a look at these 6 Reasons Why You Should Learn TypeScript in 2021 .
Conclusion
There's so much more you can do to create a clean and maintainable codebase. With my post, you should have a overview about small things you can do to improve your code. In the future, I'll publish more content to make you a better programmer.
Let's move your learning forward together! Follow me on Twitter for your daily dose of developer tips. Thanks for reading my content!
This post was originally published at kais.blog.
Top comments (12)
Sometimes it is easier to use a boolean flag as a function argument, if splitting the function would result in two almost identical functions. But rather than pass in a boolean, I like to give a unioned string option in TypeScript which is descriptive of what the flag means:
Yeah, nothing is set in stone. If you feel like this is appropriate, go for it. In my opinion it's much better than a boolen flag already. If you are using TypeScript this is a good alternative. However, for JavaScript I won't recommend this, as you could do the following:
At first glance it's not obvious that
"fast"
is an invalid argument here.Thank you for sharing this knowledge. While I was reading I recognized a couple of mistakes/bad practices I've been seeing in the work I'm collaborating on these days... I'll make sure to try and correct them from now on!
Thanks!
An easier fix here:
and that's not even a "new" language feature. And to answer the question: 12. It's 12 zeroes. You can literally read it as a number.
Thanks for your feedback. I really appreciate it. I'd like to add some thoughts to this, though:
First, one billion is 9 zeros in American and British English nowadays (short scale definition). This is sometimes confusing as it's 12 zeros in some other countries (long scale definition). For example, in Germany, we have Million (6 zeros), Milliarde (9 zeros) and Billion (12 zeros). As we have an english-speaking audience here, I've chosen to ignore my country's default definition and used 9 zeros. Also, you usually use English for code.
Second, you are right, you could use the
e
-notation. In this case, it would've been:However, it's important to note that you are just adding zeros here. It's not so useful for other numbers.
Third, I feel like the numeric separator is still a very useful thing. As I said, the
e
-notation can be used, if you have to add zeros. Then, maybe it's easier. Nevertheless, the numeric separator has other uses as well:So, it might not be the right thing to use everytime. Yet, I'd like to highlight that nothing is set in stone and there are always exceptions and different opinions on how to structure and write clean code.
I totally agree that
_
has its uses and can make code much more reasonable in cases wheree
-notation wouldn't do so. But for the specific case of having lots of0
s, I'd always prefer thee
-notation, because you can just read the number of zeroes. Something like1e32
would be difficult to count even with_
-notation.true
. I agree. Maybe next time, I should choose a different example. Thanks for your input, though.Not a big deal and not important for the purpose of the article, just you can't use optional-chaining in a root developer.mozilla.org/en-US/docs/W...
Thanks for mentioning this. I've just tried to come up with a code example fast and have overlooked that in the process. I'll fix it in the article.
thank you for sharing this its very useful content
Thanks. I'm glad I could help.