DevUI is a team with both design and engineering perspectives, serving huawei DevCloud platform and huawei internal background systems, as well as designers and front-end engineers. Ng Component Library: Ng-Devui (Welcome Star)

The introduction

Refactoring is an unavoidable part of our development process. Refactor the code to fit diverse scenarios that were not considered at the beginning of the current module design, and increase the maintainability, robustness, and testability of the module. So, how do you define the direction of refactoring and quantify the results of refactoring?

Code Cyclomatic complexity (CC) can be an alternative metric.

What is cyclomatic complexity

Cyclomatic complexity (CC), also known as conditional or cyclic complexity, is a software metric that was developed in 1976 by Thomas J. McCabe, Sr., to indicate the complexity of a program with the symbol VG or M. Cyclomatic complexity is the number of linearly independent paths in a program’s source code.

Why reduce cyclomatic complexity of modules (functions)

The following table is a basic comparison of the cyclomatic complexity of a module (function) with the state of the code. In addition to the code condition, testability, maintenance cost and other indicators given in the table, modules (functions) with high cyclomatic complexity are also associated with high software complexity, low cohesion, high risk, and low readability. We want to reduce the cyclomatic complexity of modules (functions), which means to reduce their software complexity, increase cohesion, reduce the number of possible software defects, and enhance testability and readability.

Cyclomatic complexity

The code in

measurability

Maintenance costs

1-10

Clear and structured

high

low

10-20

complex

In the

In the

20-30

Very complicated

low

high

> 30

Do not read

unpredictable

Very high

Reduce software complexity

When McCabe proposed cyclomatic complexity, one of his original aims was to limit cyclomatic complexity during software development. He advises programmers to calculate the complexity of their development modules, and if a module’s cyclomatic complexity exceeds 10, break it up into smaller modules. The NIST (National Institute of Standards and Technology) structured testing methodology has adjusted this approach slightly, and in some specific cases, the cyclomatic complexity upper limit of modules may be relaxed to 15. The methodology also acknowledges that there may be special cases where a module’s complexity may need to exceed the upper limit. The recommendation is that “a module’s cyclic complexity should be within the upper limit, or provide written data explaining why it is necessary for the module to exceed the upper limit.”

Increase module cohesion

Good code modules are low coupling and high cohesion. You would expect a module of high complexity to have low cohesion, at least not to the extent of functional cohesion. A module with high complexity and low cohesion has many decision points, and such modules tend to run more than one well-defined task and thus have low cohesion.

Reduce the number of possible software defects

Many studies have pointed out that the cyclomatic complexity of modules (functions) is correlated with the number of defects in them, and many such studies have found a highly positive correlation between the cyclomatic complexity and the number of defects: modules and methods with the highest cyclomatic complexity also have the highest number of defects.

Enhance module testability

A module (function) with high cyclomatic complexity will inevitably have more running branches from the calculation methods described below. Writing such modules, such as unit test cases, will be very complicated, and later use case maintenance will also be a problem.

Improve code readability

Code readability is a factor that must be considered between large projects and team collaboration. For modules (functions) with high cyclomatic complexity, the code readability will decrease with the increase of logical complexity, which is not conducive to the cooperation between members and later maintenance.

How do you calculate cyclomatic complexity

Calculation method

The cyclomatic complexity of a program is the number of linearly independent paths. IF there is no control flow like IF instruction or FOR loop in the program, because there is only one path in the program, the cyclomatic complexity is 1; IF there is an IF instruction in the program, there will be two different paths, corresponding to the IF condition is true and not true, so the cyclomatic complexity is 2.

Mathematically, the cyclomatic complexity of a structured program is defined by the control flow diagram of the program. The control flow diagram is a directed graph in which the nodes are the basic modules of the program. If a module may run another module after its completion, the arrows are used to link the two modules and indicate the possible running order. Cyclomatic complexity M can be defined as follows:

M = E − N + 2P

Among them

E is the number of edges in the graph

N is the number of nodes in the figure

P is the number of connected components

Measurement tools

CodeMetrics

A VSCode plug-in for TS, JS code cyclomatic complexity measurement.

ESLint

Eslint can also configure rules for cyclomatic complexity, such as:

rules: { 
  complexity: [ 
    'error', 
    { 
      max: 15 
    } 
  ] 
}
Copy the code

Represents the current maximum cyclomatic complexity per function of 15, otherwise ESLint will give an error.

conard cc

An open source code cyclomatic complexity detection tool (github: github.com/ConardLi/aw…) Can generate a cyclomatic complexity report for code under the current project.

How to reduce module (function) cyclomatic complexity

Cyclomatic complexity of commonly used structures

To reduce cyclomatic complexity, we need to understand which statements and which structures contribute to our increased complexity. The following is a description of the cyclomatic complexity of common structures.

Sequential structure

The sequential structure complexity is 1.

Ex. :

function func() {
  let a = 1, b = 1, c;
  c = a * b;
}
Copy the code

As shown in the code above, the internal structure of func function is sequential, and its control flow diagram is as follows:

Edge: 1, point: 2, connected branch: 1,

Cyclomatic complexity:

M = 1 - 2 + 2 * 1 = 1
Copy the code

If – else – else, switch – case

Each additional branches, complexity increases 1, &&, | | operation for a branch.

Ex. :

function func() { let a = 1, b = 1, c; if (a = 1) { c = a + b; } else { c = a - b; }}Copy the code

Side: 4, point: 4, connected branch: 1,

Cyclomatic complexity:

M = 4 - 4 + 2 * 1 = 2
Copy the code

Loop structure

Adding a loop increases the complexity by 1. ,

Ex. :

function func() {
  let a = 1, b = 1, c = 2;
  for (let i = 1; i < 10; i++) {
    b += a;
  }
  c = a + b;
}
Copy the code

Side: 4, point: 4, connected branch: 1,

Cyclomatic complexity:

M = 4 - 4 + 2 * 1 = 2
Copy the code

return

Theoretically, a return does not increase the cyclomatic complexity of the current module, but in the view of some measurement tools, a return statement adds a path to the overall program and, if returned earlier, increases the program’s uncertainty, so in most calculation tools, each additional return statement increases the complexity by 1.

Methods to reduce the cyclomatic complexity of modules (functions) are commonly used

1. Function extraction and separation, single responsibility (recommended)

Since it is to reduce the cyclomatic complexity of a module (function), for functions with high complexity, the first step is to refine functions and split functions, and each function should have a single responsibility.

Ex. :

function add(a, b) {
  let tempA;
  if (a === 10) {
    tempA = 11;
  } else if (a === 12) {
    tempA = 12;
  }
  let tempB;
  if (b === 10) {
    tempB = 13;
  } else if (b === 12) {
    tempB = 12;
  }
  return tempB + tempA;
}
Copy the code

Refactoring is:

function add(a, b) { return calcA(a) + calcB(b); } function calcA(a) { if (a === 10) { return 11; } else if (a === 12) { return 12; } } function calcB(b) { if (b === 10) { return 13; } else if (b === 12) { return 12; }}Copy the code

It not only reduces the cyclomatic complexity of add function, but also makes the code structure clearer and more readable, and also increases the maintainability and testability of the current code.

Of course, too much of a good thing is a bad thing. Our goal is to refine functions, keep them single, and not violently split them in order to reduce cyclomatic complexity.

2. Optimization algorithm (reduce unnecessary conditions, loop branches)

From the cyclomatic complexity calculation, both condition and cycle branch can increase module cyclomatic complexity. To some extent, complex conditions and cyclic structures are optimizable, reducing unnecessary structures and thus cyclomatic complexity.

Ex. :

let a = 'a', c;
if (a === 'a') {
  c = a + 1;
} else if (a === 'b') {
  c = a + 2;
} else if (a === 'c') {
  c = a + 3;
} else if (a === 'd') {
  c = a + 4;
}
return c;
Copy the code

Refactoring is:

let a = 'a', c;
let conditionMap = {
  a: 1,
  b: 2,
  c: 3,
  d: 4
}
c = a + conditionMap[a];
return c;
Copy the code

All conditional branches are eliminated, greatly reducing the cyclomatic complexity of the current function.

3. Expression logic optimization

Logical computation will also increase cyclomatic complexity, optimize some complex logical expressions, reduce unnecessary logical judgment, and reduce cyclomatic complexity to a certain extent.

Ex. :

a && b || a && c
Copy the code

It can be simply optimized as:

a && (b || c)
Copy the code

Thus the cyclomatic complexity of the expression is reduced by 1.

5. Reduce early return

From the perspective of reducing cyclomatic complexity, as most of the current cyclomatic complexity calculation tools calculate the number of return, reducing the number of return statements is also a way to optimize the measurement rules for these tools.

Ex. :

let a = 1, b = 1;
if (a = 1) {
  return a + b;
} else {
  return a - b;
}
Copy the code

Refactoring is:

let a = 1, b = 1, c;
if (a = 1) {
  c = a + b;
} else {
  c = a - b;
}
return c;
Copy the code

Cyclomatic complexity will decrease by 1.

conclusion

Code with high Cyclomatic complexity (CC) is never good code, and Cyclomatic complexity can be used as a reference to measure the quality of our code. The cyclomatic complexity can be calculated by control flow diagram. In order to reduce the cyclomatic complexity of module (function), extracting the resolution function, optimizing algorithm and optimizing logical expression are all possible methods.

reference

Cyclic complexity: zh.wikipedia.org/wiki/%E5%BE…

Rounding cyclomatic complexity: kaelzhang81. Making. IO / 2017/06/18 /…

Front-end code Quality – Cyclomatic complexity Principles and Practices: juejin.cn/post/684490…

Join us

We are DevUI team, welcome to come here and build elegant and efficient human-computer design/research and development system with us. Email: [email protected].

DevUI Bang bang bang bang

Previous articles are recommended

Building your Own Angular Component Library

Pagination with Vue/React/Angular Frameworks

“How to build a grayscale Publishing environment”