JavaScript comes from the early web. At first, simple scripts were written on it that “animated” the pages of the sites. Now JS has become a full-fledged programming language that can be used even for developing server projects.
Modern web applications rely heavily on JavaScript. This is especially true for single-page applications (SPA). With the advent of libraries and frameworks such as React, Angular and Vue, JavaScript has become one of the main building blocks of web applications.

')
Scaling up of such applications, whether they are client or server parts, can be a very difficult task. If the basis of such applications is poorly thought-out architecture, then their developers sooner or later face certain limitations. They drown in a sea of unpleasant surprises.
The author of the article, the translation of which we are publishing today, wants to share tips on writing pure JavaScript code. He says that the article is designed for JS-programmers with any level of training. But it will be especially useful for those who are familiar with JavaScript, at least at the middle level.
1. Code isolation
In order to keep the code base of the project clean, so that the code would be easy to read, it is recommended to allocate the code fragments into separate blocks based on their purpose. Functions are usually used as such blocks. I consider this recommendation the most important one I can give. If you are writing a function, then you need to immediately focus on ensuring that this function would be aimed at solving a single task. The function should not be designed to solve several problems.
In addition, you should strive to ensure that function calls do not lead to side effects. In most cases, this means that the function should not change something that is declared outside of it. The data in it come through the parameters. She should not work with anything else. Return any of the functions you need using the keyword
return
.
2. Breakdown of the code into modules
Functions that are used in a similar way or perform similar actions can be grouped in a single module (or, if you prefer, in a separate class). Suppose you need to perform various calculations in your project. In this situation, the different stages of such calculations makes sense to express in the form of separate functions (isolated blocks), the calls of which can be combined into chains. However, all these functions can be declared in one file (that is, in a module). Here is an example of the
calculation.js
module, which contains similar functions:
function add(a, b) { return a + b } function subtract(a, b) { return a - b } module.exports = { add, subtract }
But how can you use this module in another file (let's call it
index.js
):
const { add, subtract } = require('./calculations') console.log(subtract(5, add(3, 2))
The following recommendations can be given to developers of front-end applications. To export the most important entities declared in the module, use the default export capabilities. For non-essential entities, named exports can be applied.
3. Use several parameters of functions instead of a single object with parameters.
When declaring a function, you should strive to use several parameters, and not a single object with parameters. Here are a couple of examples:
Having a function of several parameters allows, looking at the first line of its declaration, to immediately find out what it needs to pass. This is the reason why I give this recommendation.
Despite the fact that when developing functions, it is necessary to strive to ensure that each of them solves only one task, the size of the function code can be quite large. If the function accepts a single object with parameters, then in order to find out exactly what it expects, you may need to view all of its code, spending a lot of time on it. Sometimes it may seem that when working with functions it is much easier to use a single object with parameters. But if you write functions, given the possible future scaling of the application, it is better to use several parameters.
It should be noted that there is a certain limit, after which the use of individual parameters loses meaning. In my case, these are four or five parameters. If a function needs so much input, then the programmer should think about using an object with parameters.
The main reason for this recommendation is that the individual parameters expected by the function must be passed to it in a certain order. If some of the parameters are optional, then instead of them it is necessary to pass functions to something like
undefined
or
null
. When using an object with parameters, the order of parameters in the object does not matter. With this approach, you can do without setting the optional parameters to
undefined
.
4. Restructuring
Destructuring is a useful mechanism that appeared in ES6. It allows you to extract the specified fields from objects and immediately write them into variables. It can be used when working with objects and modules:
In particular, when working with modules, it makes sense to import into a certain file not the entire module, but only the necessary functions, giving them friendly names. Otherwise, you will have to access the functions using the variable symbolizing the module.
A similar approach is applicable to the cases when a single object is used as a parameter of a function. This allows, looking at the first line of the function, to immediately find out what exactly it expects to receive as an object with parameters:
function logCountry({name, code, language, currency, population, continent}) { let msg = `The official language of ${name} ` if(code) msg += `(${code}) ` msg += `is ${language}. ${population} inhabitants pay in ${currency}.` if(contintent) msg += ` The country is located in ${continent}` } logCountry({ name: 'Germany', code: 'DE', language 'german', currency: 'Euro', population: '82 Million', }) logCountry({ name: 'China', language 'mandarin', currency: 'Renminbi', population: '1.4 Billion', continent: 'Asia', })
As you can see, despite the fact that the function accepts a single object with parameters, its destructuring allows you to know what needs to be placed in it when the function is called. The following advice will be devoted to how to more precisely inform the user of the function about exactly what it expects.
By the way, destructuring can also be used when working with functional components of React.
5. Set standard values for function parameters.
It makes sense to use the standard values of the parameters of functions, the values of the parameters, when destructuring objects with parameters, and in those cases when the functions accept parameter lists. First, it gives the programmer an example of what functions can be passed. Secondly, it allows you to find out which parameters are required and which are optional. Let's add the declaration of the function from the previous example with the standard values of the parameters:
function logCountry({ name = 'United States', code, language = 'English', currency = 'USD', population = '327 Million', continent, }) { let msg = `The official language of ${name} ` if(code) msg += `(${code}) ` msg += `is ${language}. ${population} inhabitants pay in ${currency}.` if(contintent) msg += ` The country is located in ${continent}` } logCountry({ name: 'Germany', code: 'DE', language 'german', currency: 'Euro', population: '82 Million', }) logCountry({ name: 'China', language 'mandarin', currency: 'Renminbi', population: '1.4 Billion', continent: 'Asia', })
It is obvious that in some cases, if you did not pass an important parameter to the function, you need to give an error, and not use the standard value of this parameter. But often, however, the method described here is very useful.
6. Do not pass unnecessary data to functions.
The previous recommendation leads us to an interesting conclusion. It lies in the fact that functions do not need to transfer the data that they do not need. If you follow this rule, then the development of functions may require additional time. But in the long term, this approach will lead to the formation of a code base that is distinguished by good readability. In addition, it is incredibly useful to know exactly what data is used in each particular place of the program.
7. Limiting the number of lines in files and the maximum level of nesting code
I have seen large files with program code. Very big. Some had over 3,000 lines. In such files it is very difficult to navigate.
As a result, it is recommended to limit the size of files, measured in lines of code. I usually strive to ensure that the size of my files does not exceed 100 lines. Sometimes, when it is difficult to break some kind of logic into small fragments, the size of my files reaches 200-300 lines. And very rarely, their size reaches 400 lines. Files that exceed this limit are hard to read and maintain.
While working on your projects, boldly create new modules and folders. The project structure should resemble a forest consisting of trees (groups of modules and modules files) and branches (sections of modules). Strive to ensure that your projects would not be like mountain ranges.
If we talk about the appearance of the files with the code, then they should be similar to the terrain with low hills. The idea is to avoid large levels of nesting code. It is necessary to strive to ensure that the nesting of the code does not exceed four levels.
It is possible that applying the appropriate ESLint linter rules will help to follow these guidelines.
8. Use tools to automatically format the code.
When teamwork over JavaScript projects, it is necessary to develop a clear guide to the style and formatting of the code. You can automate code formatting with ESLint. This linter offers the developer a huge set of rules that can be customized. There is an
eslint --fix
that can fix some errors.
However, I recommend using Prettier, not ESLint, to automate the formatting of the code. With this approach, the developer may not care about formatting the code. He only needs to write quality programs. All code automatically formatted using a single set of rules will look consistent.
9. Use well-designed variable names.
The variable name, ideally, should reflect its contents. Here are some guidelines for selecting informative variable names.
▍Functions
Usually functions perform some kind of action. People, when they talk about actions, use verbs. For example - convert (convert) or display (show). The names of functions are recommended to form so that they begin with a verb. For example,
convertCurrency
or
displayUser
.
▍Arrays
Arrays usually contain sets of some values. As a result, it makes sense to add the letter
s
to the name of the variable storing the array. For example:
const students = ['Eddie', 'Julia', 'Nathan', 'Theresa']
▍Logic values
Logical variable names make sense to start with
is
or
has
. This brings them closer to constructions that are available in ordinary language. For example, here’s the question: “Is that person a teacher?”. The answer to it can be “Yes” or “No”. Similarly, you can do and picking up names for logical variables:
const isTeacher = true
▍Parameters of functions passed to standard array methods
Here are some standard JavaScript array methods:
forEach
,
map
,
reduce
,
filter
. They allow you to perform certain actions with arrays. They are passed functions that describe array operations. I have seen how many programmers simply pass parameters to such functions with names like
el
or
element
. Although this approach saves the programmer from thinking about the naming of such parameters, it is better to call them taking into account the data that they contain. For example:
const cities = ['Berlin', 'San Francisco', 'Tel Aviv', 'Seoul'] cities.forEach(function(city) { ... })
▍ Identifiers
It often happens that a programmer needs to work with identifiers of certain data sets or objects. If such identifiers are nested, nothing special needs to be done with them. I, for example, when working with MongoDB, usually, before returning an object to a frontend application, convert
_id
to
id
. When extracting identifiers from objects, it is recommended to form their names by placing the object type in front of
id
. For example:
const studentId = student.id
The exception to this rule is to work with MongoDB links in models. In such cases, it is recommended to name the fields in accordance with the models to which they are referenced. This, when filling out documents that are referenced in the fields, will help maintain the purity and consistency of the code:
const StudentSchema = new Schema({ teacher: { type: Schema.Types.ObjectId, ref: 'Teacher', required: true, }, name: String, ... })
10. Use the async / await construct where possible
Using callbacks degrades code readability. This is especially true for nested callbacks. Promises straightened things up a bit, but I believe that the code that uses the async / await construct is best read. Even beginners and developers who have switched to JavaScript from other languages can easily figure this out. The most important thing here is to master the concepts underlying async / await. It is not necessary to use this construction everywhere only because of its novelty.
11. Procedure for importing modules
Recommendations 1 and 2 demonstrated the importance of choosing the right place for storing code in order to maintain it. Similar ideas apply to the import order of modules. Namely, we are talking about the fact that the logical order of importing modules makes the code clearer. I, importing modules, adhere to the following simple scheme:
This example is based on React. The same idea will be easy to transfer to any other development environment.
12. Avoid using console.log
The
console.log
command is a simple, fast and convenient tool for debugging programs. There are, of course, more advanced tools of this kind, but I think that almost all programmers still use
console.log
. If, using
console.log
for debugging, not to remove the calls of this command that have become unnecessary in time, the console will soon come to a complete mess. It should be noted that it makes sense to leave some logging commands even in the code of projects that are completely ready for work. For example - commands that display error messages and warnings.
As a result, we can say that
console.log
can be used for debugging purposes, and in cases where logging commands are planned to be used in working projects, it makes sense to resort to specialized libraries. Among them are
loglevel and
winston . In addition, to combat unnecessary logging commands, you can use ESLint. This allows you to perform a global search and delete similar commands.
Results
The author of this material says that everything he told about here helps him well in maintaining the cleanliness and scalability of the code base of his projects. We hope these tips will be useful to you.
Dear readers! What could you add to the 12 tips here on writing clean and scalable JS code?
