laitimes

Common routines for JS programming

author:Flash Gene
Common routines for JS programming

Guide

Taste the herbs to know the sweetness and bitterness. Routines often help improve the readability, extensibility, and efficiency of your code. The following are some of the code routines summarized in the author's work to share with you.

In martial arts novels, there are often such scenes, a teenager was chased by the enemy to a desperate situation and strayed into a cave, and accidentally found that the stone wall was engraved with the long-lost peerless martial arts of the rivers and lakes. Before he could comprehend the mystery, suddenly the cave began to shake and collapse, the mountain rock cracked, and the young man escaped. Later, he went incognito, studied hard, and finally realized this peerless skill. The full moon is in the shadow of the bow, and the stars are in the end of the sword. A sword in ten years, breaking through the sky, the sword intent is thousands of miles, straight to the sky. 

In the world of programming, this may be the case. Before code practice, you may not understand the design principles, patterns, and specifications in textbooks. The thing to do is to keep it in mind. In the years that come to pass, everyone may develop their own routine, which is extremely effective in solving practical problems. And these routines are actually integrated with those theories.

Taste the herbs to know the sweetness and bitterness. Routines often help improve the readability, extensibility, and efficiency of your code. Here are some of my favorites.

Type-defined routines

A data type is a general term for a set of values and a set of operations defined on that set of values. Generally speaking, when the type of data is determined, the logical structure of the code is also determined, and the two complement each other. Past coding experience has shown that there is a strong correlation between the difficulty of logic implementation and the quality of design and the type of data. Choosing the right data type simplifies the logic and makes your code more extensible and readable. This feels very much like the relationship between data structures and algorithms, although the latter tends to affect the utilization and operational efficiency of computer hardware.

example

For example, the following code snippet has multiple magic strings in it, which at first glance can be overwhelming. We know that an enum is a collection of constants. When you need to use some fixed constants in your code but are obscure, consider using enumeration definitions to improve the readability of your code.

...
  if (group === 'customer_free_group') {
    list.push({
      id: 'customer_free_cid',
      Type: 'EQ',
      bizType: 'customer_free',
    });
  } else if (group === 'customer_biz_group') {
    list.push({
      id: 'customer_biz_cid',
      Type: 'AND',
      bizType: 'customer_biz'
    });
  } else if (group === 'contact_group') {
    list.push({
      id: 'contact_cid',
      Type: 'IN',
      bizType: 'contact',
    });
  }
...           

Use enumerated types to define the constants here, and as soon as the type definition comes out, it seems that the code logic is easier to understand:

enum GroupTypes {
  customerFreeGroup = 'customer_free_group', // 免费客户组
  customerBizGroup = 'customer_biz_group', // 收费客户组
  contactGroup = 'contact_group', // 联系人组
}


enum LogicalOperationTypes {
  EQ = 'EQ',
  IN = 'IN',
  AND = 'AND',
  GT = 'GT',
  LT = 'LT',
}


enum BizTypes {
  customer_free = 'customer_free', // 免费客户
  customer_biz = 'customer_biz', // 收费客户
  contact = 'contact', // 联系人
}           

Then the magic string of the original code was changed, and the changed code seemed to be able to reveal some information that would help the reader understand, and there was some progress in readability. And because there is a clear naming and centralized management place, it is also convenient for subsequent expansion:

...
  if (group === GroupTypes.customer_free_group) {
    list.push({
      id: IdTypes.customer_free_cid,
      type: LogicalOperationTypes.EQ,
      bizType: BizTypes.customer_free,
    });
  } else if (group === GroupTypes.customer_biz_group) {
    list.push({
      id: IdTypes.customer_biz_cid,
      type: LogicalOperationTypes.AND,
      bizType: BizTypes.customer_biz,
    });
  } else if (group === GroupTypes.contact_group) {
    list.push({
      id: IdTypes.contact_cid,
      type: LogicalOperationTypes.IN,
      bizType: BizTypes.contact,
    });
  }
...           

Then, we found that there were multiple similar code structures in the code. There are many ways to eliminate similar code, and using appropriate type definitions is one of them. When we perceive a similar structure in our code, we may be able to move our minds to the map. First, try to define a similar code structure into a mapping table:

const groupConfigMap = {
  customer_free_group: {
    id: IdTypes.customer_free_cid,
    type: LogicalOperationTypes.EQ,
    bizType: BizTypes.customer_free,
  },
  customer_biz_group:{
    id: IdTypes.customer_biz_cid,
    type: LogicalOperationTypes.AND,
    bizType: BizTypes.customer_biz,
  },
  contact_group: {
    id: IdTypes.contact_cid,
    type: LogicalOperationTypes.IN,
    bizType: BizTypes.contact,
  },
};           

Once defined, the logic in the main code will look like this:

...
  if (group in groupConfigMap) {
    list.push(groupConfigMap[group]);
  }
...           

When the group is in the mapping table, the corresponding object is directly pushed into the list array. This makes the code more concise and easy to extend. If there is a new group type in the future, simply add it to the mapping table. The main code logic was reduced from 20 lines to 3 lines. Although the requirements for ten lines at a glance are a bit high, it should be no problem to look at the past three lines at a glance. The code is clear at a glance, simple and clear, and it feels really good to read.

brief summary

If the conditional branch is primarily based on the value of a key, consider using a mapping table to store the corresponding behavior, which can convert the conditional judgment into a simple lookup operation. By redefining data by using data types such as enumerations and mapping tables, complex logic can be simplified, redundant code can be eliminated, and code can be made more concise and easy to maintain. 

Routines for function extraction

There may be a logical structure of multiple branches and loops in a function, which can cause a function to be too long, which can affect the reading experience. If left unchecked, it can become difficult to understand and maintain. At this point, we can consider encapsulating a piece of code that is repeated in multiple places or is responsible for a specific task into a new function. Functional programming emphasizes the use of pure functions and immutable data, most commonly by separating business logic and data processing, and the data processing part is easy to use functional programming. Of course, the new functions should conform to the principle of single responsibility, and each function should do only one thing. 

example

Here's a partial snippet of code from a function:

...
  if (bizStatus === 'RUNNING') {
    bizName = 'flow_in_approval';
  } else if (bizStatus === 'COMPLETED') {
    if (bizAction === 'modify') {
      bizName = 'flow_modify';
    } else if (bizAction === 'revoke') {
      bizName = 'flow_revoke';
    } else {
      bizName = 'flow_completed';
    }
  } else {
    bizName = 'sw_flow_forward';
  }
...           

This code is based on the values of BizStatus and BizAction to determine and set the corresponding bizName, the logic is relatively independent, and it can be extracted from the original function of hundreds of lines and encapsulated into a new function getBizName. Let's use the previous data type routine to tidy up, use enums to store constants, and apply them to your code:

const BizStatus = {
  RUNNING: 'RUNNING',
  COMPLETED: 'COMPLETED',
};


const BizAction = {
  MODIFY: 'modify',
  REVOKE: 'revoke',
  REFUSE: 'refuse'
};


const getBizName = (bizStatus:BizStatus, bizAction:BizAction)=>{
  let bizName = '';
  if (bizStatus === BizStatus.RUNNING) {
    bizName = 'flow_in_approval';
  } else if (bizStatus === BizStatus.COMPLETED) {
    if (bizAction === BizAction.MODIFY) {
      bizName = 'flow_modify';
    } else if (bizAction === BizAction.REVOKE) {
      bizName = 'flow_revoke';
    } else {
      bizName = 'flow_completed';
    }
  } else {
    bizName = 'flow_forward';
  }
  return bizName;
}           

After that, the position of the original function becomes sequential. The sequential structure is the easiest to understand:

...
  bizName = getBizName(bizStatus, bizAction);
...           

brief summary

If a function itself is too large and there are multiple branching and looping structures in it, consider encapsulating each branching or looping structure into a new function. Through function extraction, you can gradually decompose large and complex functions, and keep the main process in the original function as simple and clear as possible. By properly naming the new function, the readability and maintainability of the original function will be greatly enhanced.

However, when you see the getBizName function, you will feel that something is strange. Then you need to use the following branch logic processing routine, and continue to read on.

Branching routines

A branching structure is a common logical structure in program design, which allows a program to execute different codes according to different conditions. However, excessive branching can lead to code that is difficult to understand and maintain. Optimized branching logic to improve code readability, maintainability, and performance.

Guard Clause is a programming style that uses conditional statements to exit function execution early, avoiding deep nesting, making code flatter and returning early. It is commonly used in two scenarios, one is used at the beginning of the function to verify parameters or condition checks, and if it does not meet the conditions, it will exit immediately to avoid continuing to execute the remaining code. The second is to return the corresponding value in multiple conditional branches of the function. 

example

Continuing with the unfinished code above, we will combine the deeply nested conditions into a single condition through logical operations to avoid multiple layers of nesting of conditions. Then use the guard statement to make the function return as soon as possible, so that you don't need to continue to execute the subsequent code. This makes the code more concise and easier to understand. When extending functionality, there is no need to go deep into nesting to understand the logic, and it is easier to modify.

const getBizName = (bizStatus, bizAction) => {
  if (bizStatus === BizStatus.RUNNING) {
    return 'flow_in_approval';
  }
  if (bizStatus === BizStatus.COMPLETED && bizAction === BizAction.MODIFY) {
    return 'flow_modify';
  }
  if (bizStatus === BizStatus.COMPLETED && bizAction === BizAction.REVOKE) {
    return 'flow_revoke';
  }
  if (bizStatus === BizStatus.COMPLETED) {
    return 'flow_completed';
  }
  return 'flow_forward';
};           

Of course, this example is a little bit special. You must be thinking that it can also be handled using type-defined routines. Here, when bizStatus is COMPLETED, you can also consider the function extraction again. It depends on the complexity of the code being processed. 

brief summary

Simplifying the way to merge conditional expressions, guard statements, policy patterns, and the previously mentioned mapping tables is a tool that can often be incorporated into branching logic to improve the readability and maintainability of your code. Regular activity keeps things in good shape. The code is constantly updated, and the tools are often used. 

Change the encapsulation routine

In business requirements, it is common to encounter scenarios where requirements are frequently adjusted. For example, the introduction steps for newcomers, the red dot position of the menu, and the content of functional pop-up windows need to be modified many times after they are launched. So how do you deal with similar changes in requirements? Configurability is a great tool. Configurability is a way to store a program's configuration information in an external file. By design, the configuration and logic are separated, and the configuration drive is implemented in the logic. In this way, when the requirements change, it is easy to modify the configuration to achieve the goal, avoiding cumbersome code modifications and online changes.

example

For example, if you want to display the task list to a qualified novice, the page will have multiple behaviors after the task is clicked:

Common routines for JS programming

Considering that subsequent task items may change, configuration is used. First, through generalized abstraction, the capability requirements are clarified, for example, this requirement can be described as:

1. The number and content of tasks in the task list will change from time to time.

2. The content of the pop-up window and the behavior of the button after clicking will change depending on the task.

3. There are currently three types of button clicking behaviors on the task list page: opening a pop-up window, calling an interface or JSAPI, and opening a new page. 

Once the requirements are clearly described, use the appropriate data type definition to describe the view and configure the data, for example:

interface ITaskModel {
  key: string;
  iconUrl: string;
  title: string;
  subTitle: IDescriptionModel[];
  desc: IDescriptionModel[];
  status: ITaskStatusEnum;
  action: ITaskActionModel;
  utParams: IUTParamsModel;
}


interface ITaskActionModel {
  type: ITaskActionComponentType;
  name: string;
  targetConfig: IActionTargetModel;
}


interface IActionTargetModel {
  type: ITaskActionType;
  value: string; 
  targetConfig: IActionTargetModel;
}           

Finally, there is the implementation of data-driven logic.

brief summary

Configurability can be seen as the application of a larger type definition routine. It usually needs to first transform business requirements into generalized requirements through abstraction, and then use appropriate data types to define data, separating business logic and data, so as to achieve the purpose of rapid response by modifying configuration when requirements change.

To be continued

The length of the article is more to be left for the next update, so I will end with a sentence: don't be sad when you lose, and don't be humble when you win. With a sword in his hand, he has righteousness in his heart. When I saw the sea far away, I was heroic. When you see the flowers in full bloom, you don't hide the joy in your heart. The road ahead is dangerous, but I don't know what to fear. With friends by his side, he wants to get drunk and sing. There are thousands of books of truth in the world, but you want to do it as you like. It's a good season when the flowers are getting more and more charming, so it's better to make an appointment with three or five friends to travel, and don't miss this good time.

Author: Shan Dan

Source: WeChat public account: Alibaba Cloud developer

Source: https://mp.weixin.qq.com/s/sl8CcJgJCY_xksmBVE4NhA

Read on