📜 ⬆️ ⬇️

How important it is to write code that people can read

Did you have to complete the project in one fell swoop when there was no need to look at the code again? Hardly. While working on old projects, you probably don’t want to spend time figuring out how this code works. If the code is readable, the product is easy to maintain, and you, your colleagues or employees are happy.

Vivid examples of unreadable code are found at JS1k competitions, the purpose of which is to write the best JS applications consisting of 1024 characters or less. The same can be said about JSF * ck , an extremely peculiar programming style that uses only six different characters for writing JS-code. Looking at the code laid out on these sites, you will be puzzled trying to understand what is happening here. And imagine what it is: write a similar code and a month later try to fix the bug.

If you regularly surf the net or create interfaces, you probably know that filling out a large form takes a lot of time and patience, which is not enough for all users. The same can be said about the code. The easier it is to read it, the more pleasant it is to work with it for a long time. Or at least there will be no desire to throw the computer out of the window.
')
This article will discuss various tricks and tricks to avoid pitfalls and make the code more readable.

Original article: https://www.sitepoint.com/importance-of-code-that-humans-can-read/

Code separation


Returning to the analogy with the forms: sometimes they are divided into parts, to make it easier to display on the screen. The same can be done with the code. If you divide it into parts, then it will be easier to find the desired fragment, without being pushed through the jungle.

File optimization


We have been doing web optimization for years, and JS files are no exception. By performing the minification while waiting for HTTP / 2, we saved the requests by combining several scripts into one. Today you can work as you like, putting the task of processing files on tools like Gulp or Grunt . That is, you can program it the way you want, and let specially trained applications deal with optimization (for example, performing concatenation).

//     API var getUsersRequest = new XMLHttpRequest(); getUsersRequest.open('GET', '/api/users', true); getUsersRequest.addEventListener('load', function() { //  -   }); getUsersRequest.send(); //--------------------------------------------------- //    . // ,       . //--------------------------------------------------- //      API var getPostsRequest = new XMLHttpRequest(); getPostsRequest.open('GET', '/api/posts', true); getPostsRequest.addEventListener('load', function() { //  -   }); getPostsRequest.send(); 

Functions


They allow you to create blocks of reusable code. Usually the contents of a function make it easy to understand where it starts and where it ends. It is a good habit to write small functions - no more than 10 lines. If the function has the correct name, it is easy to understand what it does when called. Below we will return to the naming conventions.

 //     API function getUsers(callback) { var getUsersRequest = new XMLHttpRequest(); getUsersRequest.open('GET', '/api/users', true); getUsersRequest.addEventListener('load', function() { callback(JSON.parse(getUsersRequest.responseText)); }); getUsersRequest.send(); } //      API function getPosts(callback) { var getPostsRequest = new XMLHttpRequest(); getPostsRequest.open('GET', '/api/posts', true); getPostsRequest.addEventListener('load', function() { callback(JSON.parse(getPostsRequest.responseText)); }); getPostsRequest.send(); } //        //     getUsers(function(users) { //  -   }); getPosts(function(posts) { //  -   }); 

The code is easy to read. Please note that both functions are almost identical. You can follow the principle of " do not repeat ", it allows you to maintain order in the code.

 function fetchJson(url, callback) { var request = new XMLHttpRequest(); request.open('GET', url, true); request.addEventListener('load', function() { callback(JSON.parse(request.responseText)); }); request.send(); } //       //     fetchJson('/api/users', function(users) { //  -   }); fetchJson('/api/posts', function(posts) { //  -   }); 

But what if we want to create a new user with a POST request? There are two options:


But thanks to object-oriented programming, you can take all the best of both options by creating a configurable one-time object, while the code will remain fairly simple to maintain.
Note : If you need the basics of exactly object-oriented JavaScript, you can watch this video: The complete guide to object-oriented JavaScript .

Object oriented programming


Consider objects, often called classes, that are context-sensitive function clusters. Such an object is perfectly placed in a separate file. In our case, you can create a basic wrapper for the XMLHttpRequest. Note that until 2015, JavaScript was a prototype-oriented language and had no classes. This fact, along with prototype inheritance, dictate certain code formatting rules.

HttpRequest.js

 function HttpRequest(url) { this.request = new XMLHttpRequest(); this.body = undefined; this.method = HttpRequest.METHOD_GET; this.url = url; this.responseParser = undefined; } HttpRequest.METHOD_GET = 'GET'; HttpRequest.METHOD_POST = 'POST'; HttpRequest.prototype.setMethod = function(method) { this.method = method; return this; }; HttpRequest.prototype.setBody = function(body) { if (typeof body === 'object') { body = JSON.stringify(body); } this.body = body; return this; }; HttpRequest.prototype.setResponseParser = function(responseParser) { if (typeof responseParser !== 'function') return; this.responseParser = responseParser; return this; }; HttpRequest.prototype.send = function(callback) { this.request.addEventListener('load', function() { if (this.responseParser) { callback(this.responseParser(this.request.responseText)); } else { callback(this.request.responseText); } }, false); this.request.open(this.method, this.url, true); this.request.send(this.body); return this; }; 

app.js

 new HttpRequest('/users') .setResponseParser(JSON.parse) .send(function(users) { //  -   }); new HttpRequest('/posts') .setResponseParser(JSON.parse) .send(function(posts) { //  -   }); //    new HttpRequest('/user') .setMethod(HttpRequest.METHOD_POST) .setBody({ name: 'Tim', email: 'info@example.com' }) .setResponseParser(JSON.parse) .send(function(user) { //  -    }); 

Note that constants must be declared immediately in the constructor. The HttpRequest class created above can now be configured over a wide range, using API for many calls. Although its implementation — the chain of method calls — was more complicated, the class itself is easier to maintain. The process of finding a balance between the implementation and the ability to reuse the code can be difficult and depends on the project.

An excellent addition to object-oriented programming are design patterns. By themselves, they do not improve readability, but consistency does it for them!

Human syntax


Files, functions, objects - these are only rough landmarks. They make it easier to scan our code. But to make the code easy to read is a much more subtle art. The smallest details can completely change the picture. For example, often in editors using a vertical line, the length of lines is limited to 80 characters. But there are many other nuances!

Naming


Thanks to proper naming, you can instantly recognize certain elements without figuring out what kind of variable it is or what this function does.

Functions are usually referred to in the Camel Style : first comes the verb, and then the subject.

 function getApiUrl() { /* ... */ } function setRequestMethod() { /* ... */ } function findItemsById(n) { /* ... */ } function hideSearchForm() { /* ... */ } 

For variable names, try using the inverted pyramid technique: first the subject goes, and then the properties.

 var element = document.getElementById('body'), elementChildren = element.children, elementChildrenCount = elementChildren.length; //   ,      “color” var colorBackground = 0xFAFAFA, colorPrimary = 0x663399; //   ,     “background” var backgroundColor = 0xFAFAFA, backgroundImages = ['foo.png', 'bar.png']; //     var headerBackgroundColor = 0xFAFAFA, headerTextColor = 0x663399; 

In such cases, it is better to use associative arrays (objects in JS), then you can get rid of the color prefix, and readability improves.

 var colors = { 'backgroundRed' : red, 'primary': greeen }; 

It is also important to emphasize the distinction between ordinary and special variables. For example, constant names are often capitalized with underscores.

 var URI_ROOT = window.location.href;     ,   . function FooObject { // ... } 

With abbreviations also have a nuance. Someone writes them only in capital letters, someone - in the style of the Camel. In the first case, there may be difficulties in recognizing abbreviations.

But in general, it all depends on the style of writing code adopted by the developer or team. You should always stick to one chosen style, make checks for compliance with it, use a linter.

Compactness and optimization


In many code bases, you can find a “special” code designed to reduce the number of characters or improve the performance of algorithms.

An example of compact code are single-line scripts. Unfortunately, they often use hacks or obscure syntax. As a rule, nested ternary operators are used. Despite their compactness, you have to spend a couple of seconds trying to figure out what they do, as opposed to regular if expressions. So be careful with syntactic abbreviations.

A good approach is to minify the code in production. Files weigh less, are loaded faster by the browser, loading time of the whole page decreases. But minified files, convenient on production, are quite inconvenient in development. Therefore, it is possible to separate the JS used on the prode and on the maiden, store two versions of each file (minified and full), and switch their use according to the environment specified in the configuration.

 // , -    ! var state = isHidden ? 'hidden' : isAnimating ? 'animating' : ''; // , -   ! var state = ''; if (isAnimating) state = 'animating'; if (isHidden) state = 'hidden'; 

Micro-optimizations designed to improve performance are often ineffective. And most often they are less readable than not so productive counterparts.

 //     $el[0].checked; //    ,    // : http://jsperf.com/prop-vs-ischecked/5 $el.prop('checked'); $el.is(':checked'); $el.attr('checked'); 

In case of multiple point checks, it is better to use switch, then it is semantically clearer that now there will be a check for point values.

JavaScript compilers perform code optimization for us well, and over the years they make it better and better. If the difference between optimized and non-optimized code is imperceptible , that part is observed when using thousands or millions of operations, it is better to prefer readability.

Non-code


Think of it as irony, but the best way to make the code readable is to add a non-executable syntax. Let's call it non-code. But do not forget that in JS there are generally accepted standards for formatting code that should not be neglected. They will be discussed below.

Indentation


Surely every developer at least once in his life helped to write code to another developer or inspect minified code in which all spaces and indents are removed. For the first time, this was a surprise for you. In various visual fields, such as design and typography, free space is just as important as filling. Most likely you will want to find a delicate balance between them. The range of opinions regarding this balance depends on the company, the team and the specific developer. Fortunately, there are some generally accepted rules:


Any other rules need to be discussed with those with whom you work. But no matter what style of code you take, the key is consistency.

 function sendPostRequest(url, data, cb) { //        var requestMethod = 'POST', requestHeaders = { 'Content-Type': 'text/plain' }; // ,    XMLHttpRequest var request = new XMLHttpRequest(); request.addEventListener('load', cb, false); request.open(requestMethod, url, false); request.send(data); } 

Comments


Like indents, comments can fine cut your code. And at the same time, they contain a description of your code. Be sure to add comments for:


 //     var sum = values.reduce(function(previousValue, currentValue) { return previousValue + currentValue; }); 

Not all fixes are obvious. Additional information clarifies a lot:

 if ('addEventListener' in element) { element.addEventListener('click', myFunc); } // IE8     .addEventListener, //      .attachEvent // http://caniuse.com/#search=addEventListener // https://msdn.microsoft.com/en-us/library/ms536343%28VS.85%29.aspx else { element.attachEvent('click', myFunc); } 

When using documentation for functions, it is better not to use comments in the code and try to write multi-line functions that do not need comments. At work, you can use todo comments, many IDE support searching by them. Todo is used to mark pieces of code that need to be improved or completed. Therefore, it will be easier for other developers to understand your approach, the logic behind planning and designing. Putting the right todo can be very helpful.

Embedded Documentation


When writing object-oriented software, the built-in documentation can make your code more free, like regular comments. She also explains the purpose and details of the properties and methods. Many IDEs use built-in documentation as hints, which even documentation generation tools use! Regardless of the reason, writing docks in code is a great solution.

 /** *  HTTP- * @constructor * @param {string} url */ function HttpRequest(url) { // ... } /** *    * @param {Object} headers * @return {HttpRequest} */ HttpRequest.prototype.setHeaders = function(headers) { for (var header in headers) { this.headers[header] = headers[header]; } //    return this; }; 

Although param {Object} headers is indicated here, it is better to specify a description of what is stored and transmitted in this parameter. It is recommended to specify this information immediately after the parameter itself. Let's say it is Holds success request headers (200). Then the comment will look like this:

@param {Object} headers Holds success request headers( 200 )

There are different formats of documentation for the functions; it is very important that the whole team use the same format. And if the developer is one, then you need to use the same format in different places. In the description of the formal parameters it is necessary to indicate the TI of the formal parameter. Some IDEs on the fly suggest if a variable of the wrong type is passed. JS is a language with dynamic typing, and it’s not a fact that the IDE will generate an error, but when parsing such documentation you can avoid a lot of errors.

Riddles of callbacks


Events and asynchronous calls are excellent JavaScript properties, but they often degrade the readability of the code.
Usually asynchronous calls are accompanied by callbacks. Sometimes they need to be run sequentially, and sometimes it is better to wait first for the readiness of all of them. In the documentation for the function, you must make a reference to the callback function so that you can easily go to it or view the description.

 function doRequest(url, success, error) { /* ... */ } doRequest('https://example.com/api/users', function(users) { doRequest('https://example.com/api/posts', function(posts) { //  -     }, function(error) { //   /api/posts }); }, function(error) { //   /api/users }); 

To solve both problems, a Promise object was implemented in ES2015 (also known as ES6).

 function doRequest(url) { return new Promise(function(resolve, reject) { //   //     resolve(response) //     reject(error) }); } //    doRequest('https://example.com/api/users') //     ,  .then() .then(function(users) { /* ... */ }) //    Promise   reject(),  .catch() .catch(function(error) { /* ... */ }); //    Promise Promise.all([ doRequest('https://example.com/api/users'), doRequest('https://example.com/api/posts') ]) .then(function(responses) { /* ... */ }) .catch(function(error) { /* ... */ }); 

Although I had to add part of the code, in general it became easier to interpret correctly. If you want to know more about Promise, you can read JavaScript Goes Asynchronous (and It's Awesome) .

ES6 / ES2015


If you read the ES2015 specification, you might have noticed that all the code examples in this article are for older versions (with the exception of the Promise object). Despite the fact that ES6 gives us very wide opportunities, there are a number of complaints about its terrible readability.

Fat arrow syntax (fat arrow syntax) defines a function that inherits the value of this from its parent scope. At least for this, it was created. It is also tempting to use it to define normal functions.

 var add = (a, b) => a + b; console.log(add(1, 2)); // 3 

Another example is the rest and spread syntax.

 /** *    * @param {Array} numbers * @return {Number} */ function add(...numbers) { return n.reduce(function(previousValue, currentValue) { return previousValue + currentValue; }, 0); } add(...[1, 2, 3]); /** *  a, b  c * @param {Number} a * @param {Number} b * @param {Number} c * @return {Number} */ function add(a, b, c) { return a + b + c; } add(1, 2, 3); 

The ES2015 specifications describe a very useful, but unintelligible, and sometimes confusing syntax that has become a target for critics. But it’s not about giving up these opportunities, but just using them with caution.

Conclusion


To maintain readability and maintainability of the code, you must remember every aspect of your project. Everything matters - from the file system to small variations of syntax. This is especially noticeable when working in a team, because it is difficult to make each participant constantly follow all the rules. Part of the problem is solved through code revisions, but the probability of error still remains. Fortunately, there are several useful tools:


It is very important to analyze the complexity of the code, and there are different approaches to the analysis. Read more about this here: http://jscomplexity.org/complexity

In addition to tools for maintaining quality and style, there are a number of tools to improve readability. Try different syntax highlighting schemes, or use a minimap for hierarchical evaluation of your script ( Atom , Brackets ). For example, https://github.com/airbnb/javascript is a fairly popular standard for formatting code in JS. It should be noted that code formatting and standard depend on the tools used. For example, in the case of React and JSX there will be other stylistic requirements.

Source: https://habr.com/ru/post/307380/


All Articles