How to write maintainable JavaScript code in 2023 — Web or Node.js

Any fool can write code that a computer can understand. Good programmers write code that humans can understand. — Martin Fowler

nodejscode

JavaScript development is still chaotic in 2022. As computers get faster, we get to write code for programmers, not for machines. This is what the world of JavaScript should be about.

Disclaimer: This article is best suited for JS developers (web or Node.js) with at least medium experience. I will assume you have solid experience writing JS already.

Contents of this article:

  • Use TypeScript everywhere
  • Learn your JavaScript and frameworks well
  • Code formatting and style
  • Comments in code and inline documentation
  • Write meaningful and useful tests where applicable
  • Use prototypes and/or MVPs before implementing complex features
  • Bonus: A few extra tips

Use TypeScript everywhere

Don’t be scared of TypeScript. For any reasonably experienced JavaScript developer, the official 5-minute guide “TypeScript for JavaScript Programmers” is enough to get started writing TypeScript today.

TypeScript helps you jump right back in.

If you use an IDE or editor that has autocomplete, suggestions, inline documentation, etc., you can experience The TypeScript™. Consider seeing these 2 examples without any context:

// src/api/posts.js
export default {
  getPosts: () => axios.get("/posts"),
}

Problems with this approach should be immediately obvious to you. What does getPosts return? What if you need to switch from axios to fetch? What if you want to add a function to create a post — how do you define the structure of data in an object? Now take a look at the same code in TypeScript:

// src/api/posts.ts
export interface Post {
  title: string;
  excerpt: string;
}
export default {
  getPosts: function(): Promise<AxiosResponse<Post[]>> {
    return axios.get("/posts");
  },
};

The key difference is transparency. You can understand the code now and your editor or tsc will assist you with it. When you call the TS version ofgetPosts, you will get useful information now.

TypeScript Intellisense in VSCode

VSCode’s IntelliSense for typed returns of functions

You can opt to go all-in with TypeScript. Shared models between a server and a client make sure you enforce the same structure on both ends. Forcing a type of object properties or classes prevents typos in your code. There are lots of instances where you can catch would-be bugs thanks to TypeScript. Not only that, but you can also jump back into that code in 2 years.

Learn your JavaScript and frameworks well
A man is only as good as his tools. — Emmert Wolf

In 2022, you should understand what JavaScript does behind the scenes. If you don’t, take a look at my favorite JavaScript resource —the free Eloquent JavaScript e-book. You should also know that JavaScript offers much more nowadays than just primitive data structures and unreliable browser APIs. For example, you can now use next-gen JavaScript, Keyed Collections, or the already well-supported Internationalization API and save some CPU time or write code less dependent on third parties.

JavaScript everywhere meme

To become a great JS developer, you need to understand asynchronous JavaScript, generator functions, functional and object-oriented paradigms, scoping, performance — and a lot more. Unfortunately, most JS developers lack a deeper understanding of asynchronous JavaScript, which is a core feature in modern apps (think Promises and async / await).

Here’s what you can start doing today without learning anything new.

  1. Work with documentation — be it Next.js, Webpack, React, TypeScript, or any other tool. For example, React has a lot of hooks most of us never have used. Take your time when you develop a major feature or set up a project and dive deep into the documentation of your tools.
  2. Test code performance with profilers, debuggers, time measurements, and overblown tests — why not throw 100k rows of data in your React table? Try to aim for good readability but also performance before releasing a major feature. Test the worst-case scenario to see if your code is a performance bottleneck.
  3. For example, I hope you know about the differences between a for loop and Array.prototype.forEach, because it may come in handy with heavy computational operations or asynchronous JavaScript with big datasets.
  4. Do not rely on NPM for everything — most of the simplest libraries are easy to implement yourself. Remember the left-pad fiasco in 2016? Save yourself from having to update packages, test compatibility, and suffer with a little bit of extra coding.
  5. If you use a library without documentation, read the source code. Optionally, you could stick to well-documented and more popular libraries.
  6. Analyze your bundle size impact. You don’t need the whole lodash library imported for a single _.omit call. Loading a huge bundle slows your app’s initial load significantly. For more info on bundle size, take a look at this great article by Kasra (which focuses on Webpack).

Code formatting and style

This is often an overlooked but very important aspect of development. Regardless of working solo or in a team, it always helps to enforce some rules.

formateted code

  1. Use ESLint and Prettier together and you can focus on what your code does. Imagine never again failing an automated CI test because of a missing trailing comma. Also, by using Prettier’s amazing defaults, you can end code-style arguments once and for all.
  2. Use descriptive naming for variables. You need to understand what something is by just reading the name. Consider buttonDomElem vs el — which is easier to understand? Or loading vs isUserDataLoading. Prefix boolean variables with a verb like is or has and don’t be afraid of longer variable names. IDEs will suggest the name and vim users will quickly copy it.
  3. Make use of spacing. If you use prettier, it will join multi-line breaks. Great. But those single-line breaks it leaves intact you want to use — even inside a single function or object declaration if it helps readability.

Comments in code and inline documentation

You could even avoid using TypeScript altogether by using TSDoc or JSDoc, although I don’t recommend that. Use JSdoc in conjunction with TypeScript and everyone will understand what is going on when executing your call in different places of the app. You can use your keywords or docs format as long as it stays painfully easy to read.

coomments in code

When you write complex parts of code, always aim to write your code for developers you will never meet in a time where you will no longer be available to answer questions. Once you come back to the same project to fix a bug in 2 years, you will understand how everything works without reading the whole source code.

Have you ever seen a setTimeout(someFn, 0) without any explanation as to why it was deferred to the end of the execution cycle? You know what it does, but why is it called in a random place like mapping colors to user avatars in a table? Here, a thoughtful comment would save you the headache of rediscovering an already fixed bug.

Write meaningful and useful tests where applicable

If you don’t like unit testing your product, most likely your customers won’t like to test it either. — Anonymous

You must have heard of the test-driven development (TDD) paradigm. More often than not, it splits developers into two camps — some that enforce TDD and some that despise TDD.

I am neither for nor against TDD. In some places, it makes sense while in other places, it’s a waste of resources. When you are developing a crucial backend feature based on thorough specification, you will benefit from TDD. But if you’re creating a front-end dashboard app, using TDD can slow you down dramatically. Consider whether the pros outweigh the cons.

nodejs backup apps

  1. Unit testing — is the go-to testing method for most use cases. Unit testing your frontend app can evaluate complex components like layouts, modal dialogs, wizards, multi-step client-side validated forms, etc. In backend development, you can verify whether the endpoints handle good and bad cases the way you expect them to.
  2. End-to-end (E2E) testing — the real MVP of tests. Once your frontend application gets big, it makes sense to create a test database and test your actual code in an actual browser every time you want to release it to users. The most comprehensive way to do an E2E test in the front-end is to think like a UX engineer — develop your user flows or collect that data from tools like HotJar and test for what your users do.
You can test a whole lot more things if you want to. A common test script in package.json in bigger projects might look like this - "test": "NODE_ENV=test eslint && tsc --noEmit && jest && cypress run".

More tests !== more quality.

Focus on making your tests useful. Don’t waste time unit testing a simple button or a small utility function. These will break the tests for the features you already have.

Many managers and product owners or even senior developers get hung up on code coverage metrics. Code coverage should be a rule of thumb, not an enforced rule. By enforcing a high percentage of code coverage, you force developers to use 3rd party libraries to save time and we already talked about why that is not ideal.

Use prototypes and/or MVPs before implementing complex features

Setting up a transparent project lifecycle with clear specifications and knowing when each phase is going to be released can give you a lot of opportunities to reflect on the work that had already been done. This is great, because once an MVP is “finished”, you can take your mind off of the usual work and evaluate the result. This is the best time to squeeze in some extra performance testing, minor refactoring, or other improvements.

Clean up and refactor smaller parts after each major feature

This rule only makes sense if your project is going to last longer than a couple of months. Once you finish a major feature, change your mindset and come back to it with a fresh cynical perspective. Find bottlenecks, caveats, and areas for improvement and evaluate what is worth changing and when.

A great, simple example would be when you create a collection of functions, that are properties of an exported JS object. You do not need to import the whole object every time as it prevents JavaScript’s built-in garbage collector to do its job for as long as you keep a reference of it. With a handful of pure functions, you should be fine. However, try 70 impure functions and import them in 6 places — your app gets noticeably slower. Instead, create named exports in smaller files to mitigate the risk of carrying around tens of thousands of lines of unused code.

Use prototypes to test features and avoid endless refactoring.

This is where your ugliest non-maintainable code can be. A separate single-purpose prototype project. For example, if you’re working with WebRTC for the first time, do it in a prototype. After you have verified that it fulfills your requirements, the time to write the actual code comes. At this point, you should have a much clearer vision of how you want your code to look and behave.

Bonus: A few extra tips:

  1. Document the initial setup and deployment instructions for every project you work on, including requirements and their versions, and describe what steps need to be taken in which order. Ideally also why.
  2. Structure your app around an established practice. You could borrow from older MVC approaches, you can try feature splitting or combine multiple approaches. There’s no right or wrong.
  3. Use the best tools your money can buy. Buy hardware you will enjoy using, decorate your office and spend money to make your work enjoyable. After all, you will spend about a third of your days working.
  4. Don’t let yourself be pushed to unrealistic deadlines. If your work environment is toxic, don’t let a single job kill your creativity and passion. You can always move on to another position.
  5. Express your opinion and start discussions. Remember the saying — “There are no stupid questions”? So keep on asking and talking. You can extend your horizon a lot just by doing that.
That is it for this exhaustive list of tips on writing more maintainable code. Happy coding!
Next Post Previous Post
No Comment
Add Comment
comment url