Key Points
This article was reviewed by Matt Burnett, Simon Codrington and Nilson Jacques. Thanks to all SitePoint peer reviewers for getting SitePoint content to its best!
Have you ever completed a project in one run at a time without having to look at the code again? Neither I. When working on old projects, you may want to spend very little or no time figuring out how the code works. Readable code is essential to keep the product maintained and satisfy you and your colleagues or collaborators.
Exaggerated examples of hard-to-read code can be found in the JS1k competition, with the goal of writing the best JavaScript application in 1024 characters or less, as well as JSF*ck (NSFW by the way), which is a A profound programming style, using only six different characters to write JavaScript code. Checking the code on these sites will make you wonder what's going on. Imagine writing code like this and trying to fix the error after a few months.
If you browse the internet regularly or build interfaces, you may know that it is easier to exit a large, bulky form than to exit a simple and small form. The same goes for the code. People may prefer using it when considered easier to read and use. At least it will prevent you from throwing away your computer from being frustrated.
In this article, I will explore tips and tricks for making the code easier to read, as well as pitfalls to avoid.
Persist in form analogies, the form is sometimes divided into parts, making it less difficult. The code can do the same. By dividing it into sections, readers can jump to the sections related to them instead of struggling to browse the jungle.
For many years, we have been optimizing all kinds of things for the network. JavaScript files are no exception. Thinking about minifying and pre-HTTP/2, we save on HTTP requests by combining scripts into one. Today, we can work as we wish and use task runners like Gulp or Grunt to process our files. It is safe to say that we can program the way we like and leave optimizations (such as connections) to the tool.
// 从 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();
function allows us to create reusable code blocks. Generally, the contents of a function are indented, so it is easy to see the start and end positions of the function. A good habit is to keep the function small - 10 rows or less. It is also easy to understand what happens when the function is called when the function is named correctly. We will introduce the naming convention later.
// 从 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) { // // 对帖子执行某些操作 // });
We can simplify the above code. Note that these two functions are almost exactly the same? We can apply the "Don't Repeat Yourself" (DRY) principle. This prevents confusion.
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) { // 对帖子执行某些操作 });
What if we want to create a new user through a POST request? At this point, one option is to add optional parameters to the function, thereby introducing new logic to the function, making it too complex to become a function. Another option is to create a new function specifically for POST requests, which will cause code duplication.
We can get the advantages of both through object-oriented programming, allowing us to create a configurable single-use object while maintaining its maintainability.
Note: If you need a beginner in object-oriented JavaScript, I recommend this video: The authoritative guide to object-oriented JavaScript
Consider objects, commonly called classes, which are a set of context-aware functions. An object is ideal for putting in a dedicated file. In our example, we can build a basic wrapper for XMLHttpRequest.
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) { // 对新用户执行某些操作 });
The HttpRequest class created above is now very configurable and can be applied to many of our API calls. Although the implementation (a series of chain method calls) is more complex, the functionality of the class is easy to maintain. Balance between implementation and reusability can be difficult and project-specific.
Design pattern is a great addition when using OOP. While they won't improve readability by themselves, consistency will!
Files, functions, objects, these are just rough lines. They make your code easy to scan . Making the code easier to read is a more meticulous art. The slightest details can have a significant impact. For example, limiting your line length to 80 characters is an easy solution, usually enforced by the editor via vertical lines. But there are more! Name
Proper naming can lead to instant recognition without finding what the value is or the function's role.// 从 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();
For variable names, try to apply the inverted pyramid method. The topic is placed in front and the attributes are placed in back.
// 从 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) { // // 对帖子执行某些操作 // });
It is also important to be able to distinguish between ordinary and special variables. For example, the names of constants are usually written in capital letters and underlined.
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) { // 对帖子执行某些操作 });
Classes usually use camel nomenclature, starting with capital letters.
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; };
A small detail is the abbreviation. Some choose to capitalize the abbreviation, while others choose to stick to the camel nomenclature. Using the former may make it more difficult to identify subsequent abbreviations.
In many code bases, you may encounter some "special" code to reduce the number of characters or improve the performance of your algorithm.
Single-line code is an example of concise code. Unfortunately, they usually rely on tricks or obscure syntax. The nested ternary operators seen below are a common example. Although it is concise, it may also take one or two seconds to understand its effect compared to a normal if statement. Be careful with syntax shortcuts.
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) { // 对新用户执行某些操作 });
Micro-optimization is performance optimization and usually has little impact. In most cases, they are not as easy to read as lower-performance equivalents.
function getApiUrl() { /* ... */ } function setRequestMethod() { /* ... */ } function findItemsById(n) { /* ... */ } function hideSearchForm() { /* ... */ }
JavaScript compilers are very good at optimizing code for us, and they are constantly improving. Unless the difference between unoptimized code and optimized code is obvious (usually after thousands or millions of operations), it is recommended to choose a code that is easier to read.
This is ironic, but a better way to keep the code readable is to add syntax that won't be executed. Let's call it non-code.
I'm pretty sure every developer has had other developers providing it, or checking the compressed code for a certain website - code where most of the spaces are removed. It may be quite surprising to encounter this for the first time. In different fields of visual arts, such as design and typography, blank spaces are as important as fill. You need to find a delicate balance between the two. Perceptions of this balance vary by company, team, and developers. Fortunately, there are some rules that are generally recognized by :
One expression per line,Comments
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;
Interpretation and argumentation of non-obvious codes,
var URI_ROOT = window.location.href;
// 从 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();
When writing object-oriented software, inline documents, like ordinary comments, can provide some breathing space for the code. They also help clarify the purpose and details of an attribute or method. Many IDEs use them for prompts, and the generated document tools use them too! Whatever the reason, writing a document is an excellent practice.
// 从 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) { // // 对帖子执行某些操作 // });
Events and asynchronous calls are powerful features of JavaScript, but it usually makes the code harder to read.
Asynchronous calls are usually provided using callbacks. Sometimes you want to run them in order, or wait for all the asynchronous calls to be ready.
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) { // 对帖子执行某些操作 });
Promise object was introduced in ES2015 (also known as ES6) to solve both problems. It allows you to flatten nested asynchronous requests.
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; };
Although we introduced other code, this is easier to explain correctly. You can read more about Promise here: JavaScript becomes asynchronous (and great)
If you understand the ES2015 specification, you may have noticed that all the code examples in this article are older versions (except Promise objects). Although ES6 provides us with powerful features, there are still some issues with readability.
Fat arrow syntax defines a function that inherits the value of this from its parent scope. At least, that's why it was designed. It is also tempting to use it to define regular functions.
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) { // 对新用户执行某些操作 });
Another example is the rest and spread syntax.
function getApiUrl() { /* ... */ } function setRequestMethod() { /* ... */ } function findItemsById(n) { /* ... */ } function hideSearchForm() { /* ... */ }
I mean, the ES2015 specification introduces many useful but obscure, sometimes confusing syntaxes, which make it easy to be misused in single-line code. I don't want to prevent these features from being used. I hope to encourage caution to use them.
At every stage of the project, remember to keep the code readable and maintainable. Everything from file systems to tiny syntax choices is important. Especially in a team, it is difficult to always enforce all rules. Code review can help, but there is still room for human error. Fortunately, there are some tools that can help you do this!
In addition to code quality and style tools, there are tools that can make any code easier to read. Try different syntax highlighting themes, or try using a minimap to see a top-down overview of the script (Atom, Brackets).
What do you think about writing readable and maintainable code? I would love to hear your thoughts in the comments below.
The readability of the code is crucial for the following reasons. First, it makes the code easier to understand, debug and maintain. When the code is readable, it is easier for other developers to understand the role of the code, which is especially important in collaborative environments. Secondly, highly readable code is more likely to be correct. If developers can easily understand the code, it is unlikely that they will introduce errors when modifying the code. Finally, highly readable code is easier to test. If the code is clear and concise, it is easier to determine what needs to be tested and how to test it.
The language is considered easy to read if it has a clear and concise syntax, uses meaningful identifiers, and contains comments that explain the effect of the code. High-level languages like Python and Ruby are often considered easy to read because they use English-like syntax and allow for clear, descriptive variable names. However, it is also possible to improve the readability of low-level languages like C or Java through good coding practices such as consistent indentation, use of spaces, and comprehensive annotations.
function can significantly reduce the amount of code by allowing developers to reuse it. Instead of writing the same code multiple times, write a function once and then call it when you need to perform a specific task. This not only makes the code shorter and easier to read, but also makes the code easier to maintain and debug, as any changes only need to be made in one place.
Machine code is the lowest-level programming language consisting of binary code that can be executed directly by a computer's central processor (CPU). High-level languages, on the other hand, are closer to human languages and require that they are converted into machine code by a compiler or interpreter before execution. High-level languages are often easier to read and write, and they provide more abstraction with hardware, making them easier to port between different types of machines.
Interpreters and compilers are tools for converting high-level languages into machine code. The interpreter translates and executes code line by line, which allows interactive encoding and debugging. However, this may be slower than compiling the code. On the other hand, the compiler converts the entire program into machine code before execution, which can increase execution speed. However, any code errors can only be discovered after the entire program is compiled.
Assembly language is a low-level programming language that uses mnemonic code to represent machine code instructions. Each assembly language is specific to a specific computer architecture. While it's easier to read than machine code, it's still harder to read and write than high-level languages. However, it allows direct control of the hardware, which is very useful in some cases.
There are several ways to improve the readability of the code. These methods include using meaningful variables and function names, indenting the code consistently, separating different parts of the code with spaces, and containing comments that explain the code's role. It is also important to follow the conventions and best practices of the programming language you are using.
Comments play a crucial role in making the code readable. They provide an explanation of the function of the code, the reasons why certain decisions are made, and how complex code parts work. This can be very helpful for other developers who need to understand and use your code. However, it is important to make comments concise and relevant and update them when the code changes.
Highly readable code greatly facilitates collaboration. When the code is easy to read, it is easier for other developers to understand and engage in contributions. This is especially important in large projects where multiple developers are working on different parts of the code base. Readable code also makes it easier to get new team members into the group because they can quickly understand what the code does and how it works.
Highly readable code can significantly improve the quality of the software. When the code is easy to read, it is easier to spot and fix errors and make sure that the code is doing what it should do. It also makes it easier to maintain and enhance the software over time, as it clearly illustrates the role of each part of the code. This can lead to more reliable, efficient and more powerful software.
The above is the detailed content of The Importance of Writing Code That Humans Can Read. For more information, please follow other related articles on the PHP Chinese website!