JavaScript: style elements
- Transfer
In 1920, the book of William Stranka Jr., "Elements of Style," was released. Recommendations from it regarding the English language are relevant today. The same principles applied to the code can improve the quality of programs.
It should be noted that we are not talking about strict rules. What we are talking about today is just a recommendation. Even if you decide to follow them, there may very well be good reasons to deviate from them, for example, if this helps make the code more understandable. However, in doing so, be vigilant and remember that people are subject to cognitive distortion . For example - choosing between normal and arrow functions

in JavaScript, someone who is not very familiar with the latter will prefer ordinary functions, because of habit, considering them more understandable, simpler, more convenient.
The principles from the "Elements of Style" are not accidentally still alive. The thing is that usually their use makes texts better. Usually the author of the book is right. It is worth deviating from them only in those cases when there is a good reason for that - and not because of a whim or personal preference.
Many of the recommendations from the chapter "Basic principles of composition" apply to the program code:
Almost the same can be said about code style:
A module is a collection of functions or data structures. Data structures are how we represent the state of a program. However, nothing interesting happens until it comes to using functions.
In JavaScript, there are three types of functions.
Functions for implementing I / O operations and certain data processing algorithms are needed almost everywhere, but the vast majority of the functions that you have to use will be engaged in mapping.
If your function is designed to perform I / O operations, do not deal with mapping tasks in it. If the function is intended for mapping, do not perform I / O operations on it.
It must be said that procedural functions violate both the “one function - one task” rule and the rule regarding loosely coupled language constructions. However, you cannot do without such functions.
An ideal function is a simple, deterministic, pure function that has the following basic properties:
Concise code is very important in software development, as the more code, the more places where you can make a mistake. Less code means fewer places where an error can hide, leading to fewer errors.
Laconic code is easier to read, since it has a higher level of the ratio of useful data to informational "interference". The reader needs to weed out less syntactic “noise” in order to understand the meaning of the program. Thus, less code means less syntactic “noise”, and, as a result, a clearer transfer of meaning.
To put it in words from “Style Elements”, a compressed code is an energetic code. For example, such a construction:
It can be reduced to this:
Those who are familiar with the minimal syntax typical of arrow functions (they appeared in ES6 in 2015) are more likely to read this record, rather than the code from the first example. Unnecessary elements, such as brackets, keyword
In the first version, there are many utility syntax constructs. These are brackets, and a keyword
Sometimes we give a name to something, for which it is not really needed. Say, some kind of intermediate variable, without which you can do. Why is this harmful? The problem here is that the human brain has limited short-term memory resources . Having met a variable in the text of the program, we are forced to remember it. If there are a lot of names, our memory is full, when reading periodically we have to go back ...
That is why experienced developers accustom themselves to eliminating unnecessary variables.
For example, in most situations, variables created just to store the return value of a function should be avoided. The name of the function should give adequate information about what exactly the function will return. Consider an example:
Having got rid of unnecessary, the code can be rewritten like this:
Another common approach to reducing the number of variables is to use a composition of functions and the so-called “pointless notation”.
Patchless notation is a way of declaring functions without mentioning the arguments that these functions operate on. Common uses for this approach are currying and composition of functions.
Here is an example of currying:
Take a look at the feature announcement
Take a look at an example that uses function composition. Function composition is the application of a function to the results returned by another function. Whether you are aware of this or not, you apply the composition of functions constantly.
For example, when you use chains of method calls like
By involving the composition of two functions, you get rid of the need to create a variable to store an intermediate value between function calls.
Let's see how this technique allows you to write cleaner code:
The same thing can be done with any function.
A functor is an object that implements a mapping function. For example, in JS, these are arrays (
You practically do the same thing every time you use call chains in promises.
In fact, each functional programming library implements at least two ways of composition of functions. This is a function
For example, in Lodash, such functions are called, respectively,
However, such functionality can be implemented independently, without libraries:
If the above seems to you something very abstruse, and you don’t know how you would take advantage of all this, consider this:
The essence of software development is composition. We create programs by composing small modules, functions and data structures.
Understanding the tools for composing functions and objects is just as important for a programmer as it is for a builder — the ability to handle a drill and an assembly gun. And the use of an imperative code to combine functions and the unjustified use of variables to store intermediate results resembles the assembly of furniture with adhesive tape.
As a result, we suggest you remember the following:
Give software constructs as clear and precise names as possible:
Call predicate functions and booleans as if they were questions that answered yes or no:
Use verb forms in function names:
The naming of event handlers and life cycle methods is an exception to the rule of using verbs in function names, since they are used as qualifiers. They show, not "what" to do, but "when." They should be named following the following pattern: “<when to perform an action>, <verb>”.
The names of event handlers from the list that are considered unsuccessful look as if we want to trigger an event, rather than respond to it.
Take a look at the following options for the hypothetical component's life cycle methods that are created to invoke the handler function before updating this component:
In the first example, we use a passive voice (“will be updated”, not “update”). This name is redundant, it is not clearer than other options.
The second example looks better, but the point of this lifecycle method is to call a handler. The name
We can go further along the path of simplification. Since we are talking about the methods of the object, when called, the object itself will be mentioned. This means that adding an object name to a method name is redundant. Think about how the design will look like the following, if you call a method, referring to the component:
Functional impurities are functions that add properties and methods to objects. Such functions are called up one after another in a conveyor belt resembling an assembly line in a factory. Each function receives
I prefer to name such functions using adjectives. In order to find the right word, you can use the suffixes “ing” and “able”. Here are some examples:
... a series of statements soon becomes monotonous and boring.
William Strank Jr., Elements of Style.
Developers fill functions with sequences of language constructs. These constructions are designed so that they are carried out one after another, in fact, being an example of a series of loosely coupled statements. A similar approach, when too many such calls are collected in a certain block of the program, leads to the appearance of the so-called “spaghetti code”.
In addition, call sets are often repeated in many similar forms. Moreover, each of the repeating blocks may very slightly differ from the others, and often such differences arise completely unexpectedly. For example, the basic needs of a user interface component correspond to the needs of almost all such components. You can implement what all these components need, based on the various stages of their life cycle, breaking the implementation into several functions.
Consider this sequence of calls:
This function performs three different things: loading data, building, based on what was loaded, the data model of the interface element, and displaying the element on the page.
In most modern libraries for developing interfaces, each of the above tasks is solved separately from the others, say, using a dedicated function. Separating these tasks, we can combine functions without any special problems, achieving the desired result in various situations.
With this approach, we could completely replace, say, the function of outputting the component, and this would not affect other parts of the program. In React, for example, there are many rendering subsystems designed for different platforms and different library usage scenarios. Here is a far from complete list: ReactNative for native iOS and Android applications, AFrame for WebVR, ReactDOM / Server for rendering components on the server side.
Another problem with the above function is that it does not allow you to prepare a model of the interface element and display it on the page without first loading the source data. What if this data is already loaded? Ultimately, if a similar function that combines several operations is called several times, this leads to unnecessary actions.
Separation of operations, in addition, opens the way to independent testing. In the process of writing code, I constantly run unit tests in order to immediately assess the impact on the application of changes made to it. However, if, as in our example, we combine the rendering code of the control with the code for loading the source data, we cannot just pass any conditional data to the element output function for test purposes. Here you have to test everything - and loading, and preparation, and data output. This, if you need to check only one thing, will lead to an unjustified waste of time: data, for example, must be downloaded over the network, processed, displayed in the browser ... To get the test results, you will have to wait longer than when checking an individual component.
In our example, there are already three separate functions whose calls can be placed in different methods of the component life cycle. For example, you can load the source data when the component is connected, processing this data and displaying the component on the screen can be performed in response to an event related to updating the state of the interface element.
Application of the above principles leads to the emergence of software with more clearly defined areas of responsibility of its individual components. Each of the components can reuse the same data structures and lifecycle event handlers; as a result, we do not perform actions many times that are enough to be done only once.
Many frameworks and templates provide for organizing program files by their type. If we are talking about a simple project, such as a small calculator, or a ToDo application, this approach will not cause problems, but in larger developments it is better to group the files in accordance with the functionality of the application that they implement.
For example, here are two options for the file hierarchy for a ToDo application. The first option is to group files by type:
The second is a logical grouping:
Grouping files by the principle of the functionality they implement allows, if necessary, to make changes to some part of the application, not to constantly go from folder to folder in search of the necessary files.
As a result, we recommend grouping the files based on what kind of application functionality they implement.
Let's move on to examples of variable names:
This design:
... better than this:
So:
... better than that:
Sometimes a logical variable interests us only in situations where its value is false. The use of such a variable name in the affirmative would lead to the fact that when it is necessary to check the operator to apply the logical negation
Do not create functions that you must pass when calling
…better than:
When creating applications, the programmer often needs to solve very similar tasks. The code that can be repeated is usually much more than completely unique. As a result, in the course of work you have to constantly do the same thing. The good thing here is that it makes it possible to generalize similar code and create abstractions. To do this, it is enough to identify the same parts of the code, select them and use them wherever they are needed. And in the course of development, pay attention only to designs that are unique to a given fragment of the application. In fact, it is precisely this purpose that various libraries and frameworks serve.
Here, for example, are the components of the user interface. And ten years have not passed since then when it was commonplace to pile up interface updates using jQuery, the application logic and the organization of its interaction with the outside world. Later, programmers began to realize that MVC could be used in client web applications, and they began to separate models from the interface update logic.
As a result, they started building web applications using a component approach, which allowed declaratively modeling components using something like templates created using HTML or JSX.
All this led to the use of the same UI update logic for all components, which is much better than the unique imperative code.
Those who are familiar with components can easily understand how they work, even if they are talking about an unfamiliar application. Namely, it is immediately clear to a knowledgeable person that there is a kind of declarative markup that describes user interface elements, event handlers to control the behavior of the component, and life cycle events to which callback functions are bound, called when it is needed.
When we use the same templates to solve similar problems, anyone familiar with the templates will be able to quickly understand what exactly the code does.
The ES6 standard was adopted in 2015, but today, after two years, many developers are avoiding new features. They strive to write code that, in their opinion, easier to read, only because they are so accustomed to . Among these new features are the operator
So, the code should be simple, but not simplified. Taking this into account, compact code has the following advantages:
If, with this approach, you think about errors, then the following is obtained:
Given the above, the compact code also has the following useful properties:
New syntactic constructions, advanced techniques, such as currying and composition of functions, give the programmer advantages, allowing you to write better code. Investing in all of this is worth the time and effort. The rejection of the new, possibly covered by concern for those who will read the code and may not understand anything, leads to the writing of programs that are similar to babbling an adult with a baby who has barely learned to walk.
It is completely natural to assume that the code reader does not know anything about the implementation of various mechanisms, but do not consider it dull or not knowing the language.
The expression of thoughts in the code should be clear, but not primitively simplified. This is both harmful and a waste of time. We advise everyone who is still not writing on ES6 to think about how, through practice, to understand the new features of the language, enrich their own “programmer's dictionary” and make their program texts more concise and understandable. And, of course, we hope that the ideas from William Strand Jr.'s book applied to JS will help you improve your code.
Dear readers! What ES6 features do you use?

in JavaScript, someone who is not very familiar with the latter will prefer ordinary functions, because of habit, considering them more understandable, simpler, more convenient.
The principles from the "Elements of Style" are not accidentally still alive. The thing is that usually their use makes texts better. Usually the author of the book is right. It is worth deviating from them only in those cases when there is a good reason for that - and not because of a whim or personal preference.
Many of the recommendations from the chapter "Basic principles of composition" apply to the program code:
- Make the paragraph the smallest part of the composition. One paragraph - one topic.
- Avoid unnecessary words.
- Use a valid deposit.
- Avoid sequences of loosely coupled sentences.
- Words in sentences related in meaning to each other should not be shared by other language constructs.
- Use affirmative statements.
- Express thoughts that are close in meaning and purpose in a similar form using parallel constructions.
Almost the same can be said about code style:
- Make the feature a minimal part of the composition. One function - one task.
- Avoid unnecessary code.
- Use a valid deposit.
- Avoid sequences of loosely coupled language constructs.
- Keep in one place the code and other program elements aimed at solving one problem.
- Use the affirmative form for variable names and when building expressions.
- Use the same templates to solve similar problems.
Function as a unit of composition
The essence of software development is composition. We create programs by composing modules, functions and data structures.
Understanding the process of creating functions and how to use them together with other functions is one of the fundamental skills of a programmer.
A module is a collection of functions or data structures. Data structures are how we represent the state of a program. However, nothing interesting happens until it comes to using functions.
In JavaScript, there are three types of functions.
- Communication functions, that is, those that perform I / O operations.
- Procedural functions, which are a set of grouped instructions for solving a certain algorithmic problem.
- Mapping functions that take data, transform it, and return what happened.
Functions for implementing I / O operations and certain data processing algorithms are needed almost everywhere, but the vast majority of the functions that you have to use will be engaged in mapping.
▍One task - one function
If your function is designed to perform I / O operations, do not deal with mapping tasks in it. If the function is intended for mapping, do not perform I / O operations on it.
It must be said that procedural functions violate both the “one function - one task” rule and the rule regarding loosely coupled language constructions. However, you cannot do without such functions.
An ideal function is a simple, deterministic, pure function that has the following basic properties:
- The same input always gives the same output.
- When it is called, there are no side effects.
Redundant code
The vibrant text is concise. There should be no unnecessary words in the sentence, no unnecessary sentences in the paragraph, for the same reason that there should not be unnecessary lines in the drawing, and no extra details in the mechanism. This does not mean that the writer should use only short sentences, or, avoiding the details, to do with general descriptions. This means that every word should have a meaning. [Extra words omitted].
William Strank Jr., "Elements of Style"
Concise code is very important in software development, as the more code, the more places where you can make a mistake. Less code means fewer places where an error can hide, leading to fewer errors.
Laconic code is easier to read, since it has a higher level of the ratio of useful data to informational "interference". The reader needs to weed out less syntactic “noise” in order to understand the meaning of the program. Thus, less code means less syntactic “noise”, and, as a result, a clearer transfer of meaning.
To put it in words from “Style Elements”, a compressed code is an energetic code. For example, such a construction:
function secret (message) {
return function () {
return message;
}
};It can be reduced to this:
const secret = msg => () => msg;Those who are familiar with the minimal syntax typical of arrow functions (they appeared in ES6 in 2015) are more likely to read this record, rather than the code from the first example. Unnecessary elements, such as brackets, keyword
function, instruction return, are omitted here. In the first version, there are many utility syntax constructs. These are brackets, and a keyword
function, and return. They, for those who are familiar with arrow functions, are nothing more than syntactic “noise”. And, in modern JavaScript, such constructions exist only so that the code can be read by those who are not yet confident enough in ES6. Although, ES6 became the language standard back in 2015, so the time has come to learn better.Unnecessary Variables
Sometimes we give a name to something, for which it is not really needed. Say, some kind of intermediate variable, without which you can do. Why is this harmful? The problem here is that the human brain has limited short-term memory resources . Having met a variable in the text of the program, we are forced to remember it. If there are a lot of names, our memory is full, when reading periodically we have to go back ...
That is why experienced developers accustom themselves to eliminating unnecessary variables.
For example, in most situations, variables created just to store the return value of a function should be avoided. The name of the function should give adequate information about what exactly the function will return. Consider an example:
const getFullName = ({firstName, lastName}) => {
const fullName = firstName + ' ' + lastName;
return fullName;
};Having got rid of unnecessary, the code can be rewritten like this:
const getFullName = ({firstName, lastName}) => (
firstName + ' ' + lastName
);Another common approach to reducing the number of variables is to use a composition of functions and the so-called “pointless notation”.
Patchless notation is a way of declaring functions without mentioning the arguments that these functions operate on. Common uses for this approach are currying and composition of functions.
Here is an example of currying:
const add2 = a => b => a + b;
// теперь мы можем объявить бесточечную версию inc(),
// которая позволяет добавить 1 к любому числу.
const inc = add2(1);
inc(3); // 4Take a look at the feature announcement
inc(). Please note that there is no keyword functionor syntax elements specific to declaring arrow functions. There is no description of the parameters of the function here, since the function does not use them. Instead, it returns another function that knows what to do with the arguments passed to it. Take a look at an example that uses function composition. Function composition is the application of a function to the results returned by another function. Whether you are aware of this or not, you apply the composition of functions constantly.
For example, when you use chains of method calls like
.map()and promise.then(). If we turn to the most general form of recording composition of functions, we get the following construction:f(g(x)). In mathematics, this is usually written as f ∘ gwhat reads as “applying a function fto the result of a function g”. By involving the composition of two functions, you get rid of the need to create a variable to store an intermediate value between function calls.
Let's see how this technique allows you to write cleaner code:
const g = n => n + 1;
const f = n => n * 2;
// С использованием точечной нотации:
const incThenDoublePoints = n => {
const incremented = g(n);
return f(incremented);
};
incThenDoublePoints(20); // 42
// compose2 — Принимает две функции и возвращает их композицию
const compose2 = (f, g) => x => f(g(x));
// В бесточечной нотации:
const incThenDoublePointFree = compose2(f, g);
incThenDoublePointFree(20); // 42The same thing can be done with any function.
A functor is an object that implements a mapping function. For example, in JS, these are arrays (
Array.map()) or promises ( promise.then()). We will write another version of the function compose2using the chain of calls of mapping functions for the composition of functions:const compose2 = (f, g) => x => [x].map(g).map(f).pop();
const incThenDoublePointFree = compose2(f, g);
incThenDoublePointFree(20); // 42You practically do the same thing every time you use call chains in promises.
In fact, each functional programming library implements at least two ways of composition of functions. This is a function
compose()that applies functions from right to left, and pipe()that applies functions from left to right. For example, in Lodash, such functions are called, respectively,
compose()and flow(). When I use this library, I use the function flow()like this:import pipe from 'lodash/fp/flow';
pipe(g, f)(20); // 42However, such functionality can be implemented independently, without libraries:
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
pipe(g, f)(20); // 42If the above seems to you something very abstruse, and you don’t know how you would take advantage of all this, consider this:
The essence of software development is composition. We create programs by composing small modules, functions and data structures.
Understanding the tools for composing functions and objects is just as important for a programmer as it is for a builder — the ability to handle a drill and an assembly gun. And the use of an imperative code to combine functions and the unjustified use of variables to store intermediate results resembles the assembly of furniture with adhesive tape.
As a result, we suggest you remember the following:
- If it is possible to express an idea in a smaller amount of code without changing or confusing its meaning, then do so.
- The same goes for variables. If there is such an opportunity, and this will not damage the logic and comprehensibility of the program - the less variables there are - the better.
Active voice
A valid voice usually means a clearer and more lively expression of thought than a passive one.
William Strank Jr., "Elements of Style"
Give software constructs as clear and precise names as possible:
myFunction.wasCalled()better thanmyFunction.hasBeenCalled()createUser()better thanUser.create()notify()better thanNotifier.doNotification()
Call predicate functions and booleans as if they were questions that answered yes or no:
isActive(user)better thangetActiveStatus(user)isFirstRun = false;better thanfirstRun = false;
Use verb forms in function names:
increment()better thanplusOne()unzip()better thanfilesFromZip()filter(fn, array)better thanmatchingItemsFromArray(fn, array)
▍Event handlers
The naming of event handlers and life cycle methods is an exception to the rule of using verbs in function names, since they are used as qualifiers. They show, not "what" to do, but "when." They should be named following the following pattern: “<when to perform an action>, <verb>”.
element.onClick(handleClick)better thanelement.click(handleClick)component.onDragStart(handleDragStart)better thancomponent.startDrag(handleDragStart)
The names of event handlers from the list that are considered unsuccessful look as if we want to trigger an event, rather than respond to it.
▍ Life Cycle Methods
Take a look at the following options for the hypothetical component's life cycle methods that are created to invoke the handler function before updating this component:
componentWillBeUpdated(doSomething)componentWillUpdate(doSomething)beforeUpdate(doSomething)
In the first example, we use a passive voice (“will be updated”, not “update”). This name is redundant, it is not clearer than other options.
The second example looks better, but the point of this lifecycle method is to call a handler. The name
componentWillUpdate(handler)is read as if the component is going to act on the handler, update it, which does not express the true value of this software construct. We mean the following: “Before the component is updated, call the handler.” The name beforeComponentUpdate()expresses our intention most clearly.We can go further along the path of simplification. Since we are talking about the methods of the object, when called, the object itself will be mentioned. This means that adding an object name to a method name is redundant. Think about how the design will look like the following, if you call a method, referring to the component:
component.componentWillUpdate(). It will read the same way: "Vasya Vasya will have cutlets for lunch." Double reference to the name of the object is redundant. As a result, the following is obtained: component.beforeUpdate(doSomething)better than c omponent.beforeComponentUpdate(doSomething). Functional impurities are functions that add properties and methods to objects. Such functions are called up one after another in a conveyor belt resembling an assembly line in a factory. Each function receives
instancean object at the input , and adds something to it before passing it to the next function in the pipeline.I prefer to name such functions using adjectives. In order to find the right word, you can use the suffixes “ing” and “able”. Here are some examples:
- const duck = composeMixins (flying, quacking);
- const box = composeMixins (iterable, mappable);
Sequences of loosely coupled language constructs
... a series of statements soon becomes monotonous and boring.
William Strank Jr., Elements of Style.
Developers fill functions with sequences of language constructs. These constructions are designed so that they are carried out one after another, in fact, being an example of a series of loosely coupled statements. A similar approach, when too many such calls are collected in a certain block of the program, leads to the appearance of the so-called “spaghetti code”.
In addition, call sets are often repeated in many similar forms. Moreover, each of the repeating blocks may very slightly differ from the others, and often such differences arise completely unexpectedly. For example, the basic needs of a user interface component correspond to the needs of almost all such components. You can implement what all these components need, based on the various stages of their life cycle, breaking the implementation into several functions.
Consider this sequence of calls:
const drawUserProfile = ({ userId }) => {
const userData = loadUserData(userId);
const dataToDisplay = calculateDisplayData(userData);
renderProfileData(dataToDisplay);
};This function performs three different things: loading data, building, based on what was loaded, the data model of the interface element, and displaying the element on the page.
In most modern libraries for developing interfaces, each of the above tasks is solved separately from the others, say, using a dedicated function. Separating these tasks, we can combine functions without any special problems, achieving the desired result in various situations.
With this approach, we could completely replace, say, the function of outputting the component, and this would not affect other parts of the program. In React, for example, there are many rendering subsystems designed for different platforms and different library usage scenarios. Here is a far from complete list: ReactNative for native iOS and Android applications, AFrame for WebVR, ReactDOM / Server for rendering components on the server side.
Another problem with the above function is that it does not allow you to prepare a model of the interface element and display it on the page without first loading the source data. What if this data is already loaded? Ultimately, if a similar function that combines several operations is called several times, this leads to unnecessary actions.
Separation of operations, in addition, opens the way to independent testing. In the process of writing code, I constantly run unit tests in order to immediately assess the impact on the application of changes made to it. However, if, as in our example, we combine the rendering code of the control with the code for loading the source data, we cannot just pass any conditional data to the element output function for test purposes. Here you have to test everything - and loading, and preparation, and data output. This, if you need to check only one thing, will lead to an unjustified waste of time: data, for example, must be downloaded over the network, processed, displayed in the browser ... To get the test results, you will have to wait longer than when checking an individual component.
In our example, there are already three separate functions whose calls can be placed in different methods of the component life cycle. For example, you can load the source data when the component is connected, processing this data and displaying the component on the screen can be performed in response to an event related to updating the state of the interface element.
Application of the above principles leads to the emergence of software with more clearly defined areas of responsibility of its individual components. Each of the components can reuse the same data structures and lifecycle event handlers; as a result, we do not perform actions many times that are enough to be done only once.
Storage of code and other program elements aimed at solving one problem
Many frameworks and templates provide for organizing program files by their type. If we are talking about a simple project, such as a small calculator, or a ToDo application, this approach will not cause problems, but in larger developments it is better to group the files in accordance with the functionality of the application that they implement.
For example, here are two options for the file hierarchy for a ToDo application. The first option is to group files by type:
.
├── components
│ ├── todos
│ └── user
├── reducers
│ ├── todos
│ └── user
└── tests
├── todos
└── userThe second is a logical grouping:
.
├── todos
│ ├── component
│ ├── reducer
│ └── test
└── user
├── component
├── reducer
└── testGrouping files by the principle of the functionality they implement allows, if necessary, to make changes to some part of the application, not to constantly go from folder to folder in search of the necessary files.
As a result, we recommend grouping the files based on what kind of application functionality they implement.
Using the affirmative form for variable names and constructing expressions
Make clear statements. Avoid a languid, colorless, indecisive, evasive tongue. Do not use the word as a means of denial, in antithesis, or as a way to evade the topic.
William Strank Jr., "Elements of Style"
Let's move on to examples of variable names:
isFlyingbetter thanisNotFlyinglatebetter thannotOnTime
▍ Conditional operator
This design:
if (err) return reject(err);
// делаем что-нибудь...... better than this:
if (!err) {
// ... делаем что-нибудь
} else {
return reject(err);
}▍Ternary operator
So:
{
[Symbol.iterator]: iterator ? iterator : defaultIterator
}... better than that:
{
[Symbol.iterator]: (!iterator) ? defaultIterator : iterator
}▍About negative statements
Sometimes a logical variable interests us only in situations where its value is false. The use of such a variable name in the affirmative would lead to the fact that when it is necessary to check the operator to apply the logical negation
!. In such cases, it is better to give the variables clear negative names. The word “not” in the variable name and the operator !in the comparison operations lead to the appearance of vague formulations. Let's look at a few examples. if (missingValue)better than better than better than .if (!hasValue)
if (anonymous)if (!user)
if (isEmpty(thing))if (notDefined(thing))▍ Arguments of functions that are null and undefined
Do not create functions that you must pass when calling
undefinedor nullinstead of optional parameters. In such situations, it is best to use an object with named parameters:const createEvent = ({
title = 'Untitled',
timeStamp = Date.now(),
description = ''
}) => ({ title, description, timeStamp });
// позже...
const birthdayParty = createEvent({
title: 'Birthday Party',
description: 'Best party ever!'
});…better than:
const createEvent = (
title = 'Untitled',
timeStamp = Date.now(),
description = ''
) => ({ title, description, timeStamp });
// позже...
const birthdayParty = createEvent(
'Birthday Party',
undefined, // Этого можно было избежать
'Best party ever!'
);Шаблоны и решение схожих задач
… параллельное построение требует внешней схожести фрагментов текста, имеющих сходное содержание и назначение. Подобие формы позволяет читателю легче распознавать сходство содержимого.
Уильям Странк-младший, «Элементы стиля»
When creating applications, the programmer often needs to solve very similar tasks. The code that can be repeated is usually much more than completely unique. As a result, in the course of work you have to constantly do the same thing. The good thing here is that it makes it possible to generalize similar code and create abstractions. To do this, it is enough to identify the same parts of the code, select them and use them wherever they are needed. And in the course of development, pay attention only to designs that are unique to a given fragment of the application. In fact, it is precisely this purpose that various libraries and frameworks serve.
Here, for example, are the components of the user interface. And ten years have not passed since then when it was commonplace to pile up interface updates using jQuery, the application logic and the organization of its interaction with the outside world. Later, programmers began to realize that MVC could be used in client web applications, and they began to separate models from the interface update logic.
As a result, they started building web applications using a component approach, which allowed declaratively modeling components using something like templates created using HTML or JSX.
All this led to the use of the same UI update logic for all components, which is much better than the unique imperative code.
Those who are familiar with components can easily understand how they work, even if they are talking about an unfamiliar application. Namely, it is immediately clear to a knowledgeable person that there is a kind of declarative markup that describes user interface elements, event handlers to control the behavior of the component, and life cycle events to which callback functions are bound, called when it is needed.
When we use the same templates to solve similar problems, anyone familiar with the templates will be able to quickly understand what exactly the code does.
Conclusions: the code should be simple but not simplified
The ES6 standard was adopted in 2015, but today, after two years, many developers are avoiding new features. They strive to write code that, in their opinion, easier to read, only because they are so accustomed to . Among these new features are the operator
…, arrow functions, implicit return. Avoiding new technologies for the sake of familiar but outdated ones is a big mistake. Acquaintance with the new takes place through practice, and after the capabilities of ES6 become familiar, their advantage over alternatives from ES5 becomes quite obvious. Compressed code is simpler than the alternative overloaded syntax. So, the code should be simple, but not simplified. Taking this into account, compact code has the following advantages:
- It is less error prone.
- It is easier to debug.
If, with this approach, you think about errors, then the following is obtained:
- They are long and expensive to fix.
- Some errors lead to the appearance of others.
- Errors hinder the process of working on the main functionality of software projects.
Given the above, the compact code also has the following useful properties:
- Its easier to write.
- Its easier to read.
- It is easier to maintain.
New syntactic constructions, advanced techniques, such as currying and composition of functions, give the programmer advantages, allowing you to write better code. Investing in all of this is worth the time and effort. The rejection of the new, possibly covered by concern for those who will read the code and may not understand anything, leads to the writing of programs that are similar to babbling an adult with a baby who has barely learned to walk.
It is completely natural to assume that the code reader does not know anything about the implementation of various mechanisms, but do not consider it dull or not knowing the language.
The expression of thoughts in the code should be clear, but not primitively simplified. This is both harmful and a waste of time. We advise everyone who is still not writing on ES6 to think about how, through practice, to understand the new features of the language, enrich their own “programmer's dictionary” and make their program texts more concise and understandable. And, of course, we hope that the ideas from William Strand Jr.'s book applied to JS will help you improve your code.
Dear readers! What ES6 features do you use?