How to Write small and clear JavaScript Functions

Source: Internet
Author: User
Taking JavaScript as an example, this article describes how to optimize functions to make the functions clear and easy to read and more efficient and stable. The complexity of software continues to grow. Code quality is very important to ensure the reliability and scalability of applications. However...

Taking JavaScript as an example, this article describes how to optimize functions to make the functions clear and easy to read and more efficient and stable.

The complexity of software continues to grow. Code quality is very important to ensure the reliability and scalability of applications.

However, almost every developer, including myself, has seen low-quality code in his career. This is a pitfall. Low-quality code has the following lethal features:

  • Functions are super long and full of messy functions.

  • Functions usually have some side effects, which are difficult to understand or even cannot be debugged at all.

  • Names of vague functions and variables.

  • Fragile code: a small change may unexpectedly destroy other application components.

  • The code coverage is missing.

They all sound like: "I can't understand how this code works." "This code is a bunch of troubles." It's too difficult to modify this code.

I have encountered such a situation that one of my colleagues cannot continue to work on a Ruby-based REST API and then leave the company. He took over the project from the previous development team.

Fix existing bugs, introduce new bugs, and add new features to add a series of new bugs, such loops (so-called fragile code ). The customer does not want to better design and reconstruct the entire application, and developers also make a wise choice-to maintain the status quo.

Well, this kind of thing often happens, and it's pretty bad. So what can we do?

First of all, you should keep in mind that it is totally different to make the application run, and to ensure the quality of the Code. On the one hand, you need to meet product requirements. On the other hand, you should take some time to make sure that the function functions are simple, use readable variables and function names, and avoid function side effects.

Functions (including object methods) are the gear to run applications. First, you should focus on their structure and overall layout. This article includes some examples to show how to write clear, easy-to-understand, and tested functions.

1. The function should be small, very small

To avoid using a large function that contains a large number of functions, you should divide the function into several smaller functions. Large Black Box functions are difficult to understand and modify, especially for testing.

Assume that a function is required to calculate the weights of an array, map, or common JavaScript Object. The total weight can be obtained by calculating the weight of each member:

  • Null or undefined variables are counted as 1 point.

  • The basic type is counted as 2 points.

  • An object or function is counted as 4 points.

For example, the weight of the array [null, 'Hello world', {}] is calculated as follows: 1 (null) + 2 (string is the basic type) + 4 (object) = 7.

Step 0: the initial big Function

We start with the worst instance. All logics are encoded in the getCollectionWeight () function:

function getCollectionWeight(collection) {    letcollectionValues;  if (collectioninstanceof Array) {    collectionValues = collection;  } else if (collectioninstanceof Map) {    collectionValues = [...collection.values()];  } else {    collectionValues = Object.keys(collection).map(function (key) {      return collection[key];    });  }  return collectionValues.reduce(function(sum, item) {    if (item == null) {      return sum + 1;    }     if (typeof item === 'object' || typeof item === 'function') {      return sum + 4;    }    return sum + 2;  }, 0);}letmyArray = [null, { }, 15];  letmyMap = new Map([ ['functionKey', function() {}] ]);  letmyObject = { 'stringKey': 'Hello world' };  getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)   getCollectionWeight(myMap);    // => 4   getCollectionWeight(myObject); // => 2

The problem is obvious: the getCollectionWeight () function is super long and looks like a black box filled with "unexpected. You may also find that you cannot understand what it is to do at first glance. Try again. There are many such functions in the application.

If you encounter such code at work, you are wasting your time and energy. On the contrary, high-quality code will not be uncomfortable. In high-quality code, functions that are exquisite and self-document excellent are very easy to read and understand.

Step 1: Calculate the weight based on the type and discard those "fascinating numbers ".

Now, our goal is to split this giant function into smaller, independent, and reusable functions. Step 1: extract the code that calculates the Weight Based on the type. This new function is named getWeight ().

Let's take a look at these "fascinating numbers": 1, 2, and 4. Without knowing the background of the story, relying on these numbers alone cannot provide any useful information. Fortunately, ES2015 allows the definition of static read-only references, so you can simply create several constants and replace those "fascinating numbers" with meaningful names ". (I especially like the saying "Numbers": D)

Create a small function getWeightByType () and use it to improve getCollectionWeight ():

// Code extracted into getWeightByType() function getWeightByType(value) {    const WEIGHT_NULL_UNDEFINED  = 1;  const WEIGHT_PRIMITIVE      = 2;  const WEIGHT_OBJECT_FUNCTION = 4;  if (value == null) {    return WEIGHT_NULL_UNDEFINED;  }   if (typeof value === 'object' || typeof value === 'function') {    return WEIGHT_OBJECT_FUNCTION;  }  return WEIGHT_PRIMITIVE;} function getCollectionWeight(collection) {    letcollectionValues;  if (collectioninstanceof Array) {    collectionValues = collection;  } else if (collectioninstanceof Map) {    collectionValues = [...collection.values()];  } else {    collectionValues = Object.keys(collection).map(function (key) {      return collection[key];    });  }  return collectionValues.reduce(function(sum, item) {    return sum + getWeightByType(item);  }, 0);}letmyArray = [null, { }, 15];  letmyMap = new Map([ ['functionKey', function() {}] ]);  letmyObject = { 'stringKey': 'Hello world' };  getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)   getCollectionWeight(myMap);    // => 4   getCollectionWeight(myObject); // => 2

It looks much better, right? The getWeightByType () function is an independent component used to determine the weight values of each type. And it is reusable. You can use it in any other function.

GetCollectionWeight () is slightly thinner.

WEIGHT_NULL_UNDEFINED, WEIGHT_PRIMITIVE, and WEIGHT_OBJECT_FUNCTION are constants with self-document capabilities. Their names show the weights of various types. You don't need to guess the meaning of numbers 1, 2, and 4.

Step 2: Continue splitting to make it scalable

However, this upgrade is still insufficient. Assume that you want to calculate the weight of a Set or even other user-defined sets. GetCollectionWeight () Quickly expands because it contains a specific set of logic for obtaining weights.

Let's extract the code that obtains the map weight to getMapValues (), and put the code that obtains the basic JavaScript Object Weight to getPlainObjectValues. Let's take a look at the improved version.

function getWeightByType(value) {    const WEIGHT_NULL_UNDEFINED = 1;  const WEIGHT_PRIMITIVE = 2;  const WEIGHT_OBJECT_FUNCTION = 4;  if (value == null) {    return WEIGHT_NULL_UNDEFINED;  }   if (typeof value === 'object' || typeof value === 'function') {    return WEIGHT_OBJECT_FUNCTION;  }  return WEIGHT_PRIMITIVE;} // Code extracted into getMapValues() function getMapValues(map) {    return [...map.values()];} // Code extracted into getPlainObjectValues() function getPlainObjectValues(object) {    return Object.keys(object).map(function (key) {    return object[key];  });} function getCollectionWeight(collection) {    letcollectionValues;  if (collectioninstanceof Array) {    collectionValues = collection;  } else if (collectioninstanceof Map) {    collectionValues = getMapValues(collection);  } else {    collectionValues = getPlainObjectValues(collection);  }  return collectionValues.reduce(function(sum, item) {    return sum + getWeightByType(item);  }, 0);}letmyArray = [null, { }, 15];  letmyMap = new Map([ ['functionKey', function() {}] ]);  letmyObject = { 'stringKey': 'Hello world' };  getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)   getCollectionWeight(myMap);    // => 4   getCollectionWeight(myObject); // => 2

Now let's look at the getCollectionWeight () function, and you will find it easy to understand its mechanism. It looks like an interesting story.

Every function is simple and clear. You don't need to spend time exploring code and Understanding code. This is what the fresh version of the Code should look like.

Step 3: endless Optimization

Even now, there is still much room for optimization!

You can create an independent function getCollectionValues () and use the if/else statement to differentiate the types in the Set:

function getCollectionValues(collection) {    if (collectioninstanceof Array) {    return collection;  }  if (collectioninstanceof Map) {    return getMapValues(collection);  }  return getPlainObjectValues(collection);}

Then, getCollectionWeight () should become purely abnormal, because it only works: Use getCollectionValues () to obtain the values in the set, and then call the sum accumulators in turn.

You can also create an independent accumulators function:

function reduceWeightSum(sum, item) {    return sum + getWeightByType(item);}

Ideally, the getCollectionWeight () function should not be defined.

Finally, the original giant function has been converted into the following small functions:

function getWeightByType(value) {    const WEIGHT_NULL_UNDEFINED = 1;  const WEIGHT_PRIMITIVE = 2;  const WEIGHT_OBJECT_FUNCTION = 4;  if (value == null) {    return WEIGHT_NULL_UNDEFINED;  }   if (typeof value === 'object' || typeof value === 'function') {    return WEIGHT_OBJECT_FUNCTION;  }  return WEIGHT_PRIMITIVE;} function getMapValues(map) {    return [...map.values()];} function getPlainObjectValues(object) {    return Object.keys(object).map(function (key) {    return object[key];  });} function getCollectionValues(collection) {    if (collectioninstanceof Array) {    return collection;  }  if (collectioninstanceof Map) {    return getMapValues(collection);  }  return getPlainObjectValues(collection);} function reduceWeightSum(sum, item) {    return sum + getWeightByType(item);} function getCollectionWeight(collection) {    return getCollectionValues(collection).reduce(reduceWeightSum, 0);}letmyArray = [null, { }, 15];  letmyMap = new Map([ ['functionKey', function() {}] ]);  letmyObject = { 'stringKey': 'Hello world' };  getCollectionWeight(myArray);  // => 7 (1 + 4 + 2)   getCollectionWeight(myMap);    // => 4   getCollectionWeight(myObject); // => 2

This is the art of writing simple and exquisite functions!

In addition to the optimization of the code quality, you also get many other benefits:

  • The getCollectionWeight () function's readability is greatly improved through self-document code.

  • The length of the getCollectionWeight () function is greatly reduced.

  • If you want to calculate weights of other types, the code for getCollectionWeight () will not expand dramatically.

  • These split functions are low-coupling and highly reusable components. Your colleagues may want to import them into other projects, and you can easily implement this requirement.

  • When a function encounters an accidental error, the call stack will be more detailed, because the stack contains the function name, and you can even immediately find the function with an error.

  • These small functions are simpler and easier to test and can achieve high code coverage. Instead of exhausting various scenarios to test a large function, you can perform a structured test to test each small function separately.

  • You can refer to the CommonJS or ES2015 module format to create the split function as an independent module. This will make your project files lighter and more structured.

These suggestions can help you overcome application complexity.

In principle, your function should not exceed 20 rows-the smaller the better.

Now, I think you may ask me this question: "I don't want to write every line of code as a function. Is there any rule that tells me when to stop splitting ?". This is the next topic.

2. The function should be simple.

Let's relax and think about the definition of the application?

Each application must meet a series of requirements. The developer's principle is to split these requirements into executable components (namespaces, classes, functions, code blocks, etc.) with smaller columns to complete the specified tasks respectively.

A component is composed of other smaller components. If you want to write a component, you can only select the required component from the lower-level components in the abstraction layer to create your own component.

In other words, you need to break down a function into several small steps, and ensure that these steps are all abstract, at the same level, and only at the lower level of abstraction. This is very important because it will make the function simple and "do only one thing ".

Why is this necessary? Because simple functions are very clear. Clarity means easy to understand and modify.

Let's give an example. Suppose you need to implement a function so that the array only retains the prime numbers (2, 3, 5, 7, 11, etc.) and removes non-prime numbers (1, 4, 6, 8, etc ). The function is called as follows:

getOnlyPrime([2, 3, 4, 5, 6, 8, 11]); // => [2, 3, 5, 11]

How to Implement the getOnlyPrime () function using several lower-level abstract steps? We do this:

To implement the getOnlyPrime () function, we use the isPrime () function to filter the numbers in the array.

It is very simple. You only need to execute a filter function isPrime () on the number array.

Do you need to implement isPrime () details at the current abstraction layer? No, because the getOnlyPrime () function implements some column steps at different abstraction layers. Otherwise, getOnlyPrime () contains too many functions.

Keep in mind the concept of simple functions. Let's implement the function body of the getOnlyPrime () function:

function getOnlyPrime(numbers) {    return numbers.filter(isPrime);}getOnlyPrime([2, 3, 4, 5, 6, 8, 11]); // => [2, 3, 5, 11]

As you can see, getOnlyPrime () is very simple. It only contains the steps of the lower-level abstraction layer: The. filter () method of the array and the isPrime () function.

Now, enter the next level of abstraction.

The. filter () method of the array is provided by the JavaScript engine and can be directly used. Of course, the standard has accurately described its behavior.

Now you can go into the details of how to implement isPrime:

To implement the isPrime () function to check whether a number n is a prime number, you only need to check whether all integers between 2 and Math. sqrt (n) cannot be divisible by n.

With this algorithm (not efficient, but for the sake of simplicity, use it), we will encode the isPrime () function:

function isPrime(number) {    if (number === 3 || number === 2) {    return true;  }  if (number === 1) {    return false;  }  for (letpisor = 2; pisor <= Math.sqrt(number); pisor++) {    if (number % pisor === 0) {      return false;    }  }  return true;} function getOnlyPrime(numbers) {    return numbers.filter(isPrime);}getOnlyPrime([2, 3, 4, 5, 6, 8, 11]); // => [2, 3, 5, 11]

GetOnlyPrime () is very small and clear. It obtains only the necessary steps from the lower-level abstraction.

As long as you follow these rules, the function becomes concise and clear, and the readability of complex functions will be greatly improved. Code can be precisely abstracted and graded to avoid the occurrence of large sections of code that are difficult to maintain.

3. Use concise function names

The function name should be very concise: moderate length. Ideally, the name should give a clear summary of the functions of the function, without the need for readers to gain a deeper understanding of the Implementation Details of the function.

For functions that use the camel style, start with a lowercase letter: addItem (), saveToStore (), or getFirstName.

Because a function is an operation, the name should contain at least one verb. For example, deletePage () and verifyCredentials (). When you need get or set attributes, use the standard set and get Prefix: getLastName () or setLastName ().

Avoid misleading names in production code, such as foo (), bar (), a (), fun (), and so on. Such a name is meaningless.

If the functions are short and clear, the name should be concise: the code will be as charming as a poem.

4. Summary

Of course, the examples here are all very simple. In reality, the code is more complex. You may complain that writing clear functions only drops down at the abstraction level, which is too boring. However, if you start your practice from the very beginning of the project, it is far from complicated as you think.

If there are already some complicated functions in the application and you want to refactor them, you may find it difficult. In addition, in many cases, it is impossible to do so within a reasonable period of time. However, a journey of a thousand miles begins with a single step: to split a part of the journey as far as possible.

Of course, the most correct solution should be to implement the application in the right way from the very beginning of the project. In addition to taking some time for implementation, you should also spend some effort to build a reasonable function structure, as we suggest-to keep them short and clear.

ES2015 has implemented a very good module system. It clearly suggests that small functions are excellent engineering practices.

Remember, clean and well-organized Code usually takes a lot of time. You will find it difficult to do this. It may take many attempts and may iterate and modify a function many times.

However, nothing is even more painful than the messy code, so it is worth it!

The above shows you how to write a small and clear JavaScript function details. For more information, see other related articles in the first PHP community!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.