Keep in mind that the person who will maintain your program is a severely violent psychopath who knows where you live.

1. Introduction

Have you ever had the following thoughts?

  • Refactoring a project is better than developing a new one…
  • Who wrote this code? I wish…

Do you have any of the following problems in your projects?

  • Individual projects are getting bigger and bigger, and team members’ code styles are not consistent, which makes it impossible to fully control the overall code quality
  • There is no accurate standard to measure the complexity of the code structure, and it is impossible to quantify the code quality of a project
  • It is not immediately possible to quantify the improvement in code quality after refactoring

In view of the above problems, cyclomatic complexity is the main character of this paper. Starting from the principle of cyclomatic complexity, this paper will introduce the calculation method of cyclomatic complexity, how to reduce the cyclomatic complexity of code, how to obtain cyclomatic complexity, and the practical application of cyclomatic complexity in company projects.

2. Cyclomatic complexity

2.1 define

Cyclomatic complexity (Cyclomatic complexity) is a measure of code complexity, also known as conditional or cyclic complexity. It is used to measure the complexity of a module’s decision structure, in terms of the number of independent existing paths, or in terms of the number of least used test cases covering all possible scenarios. Hereinafter referred to as CC. The symbol is VG or M.

Cyclomatic complexity was developed in 1976 by Thomas J. McCabe, Sr. Is put forward.

High cyclomatic complexity indicates that the program code has complex judgment logic and may be of low quality and difficult to test and maintain. High cyclomatic complexity has a lot to do with the possibility of program errors.

2.2 Measurement Standard

Low code complexity means bad code, but high code complexity means bad code.

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

3. Calculation method

3.1 Control Flow Chart

A control flow chart is an abstract representation of a process or program. It is an abstract data structure used in the compiler, maintained internally by the compiler, and represents all the paths that a program traverses during execution. It shows the possible flow of all basic block execution in a process in the form of graph, and can also reflect the real-time execution process of a process.

Here are some common control flows:

3.2 Node determination method

There’s a simple way to do it, and cyclomatic complexity is essentially equal to the number of decision nodes plus one. As mentioned above: if else, switch case, for loop, ternary operators, etc., all belong to a decision node, such as the following code:

function testComplexity(*param*) {
    let result = 1;
    if (param > 0) {
        result--;
    }
    for (let i = 0; i < 10; i++) {
        result += Math.random();
    }
    switch (parseInt(result)) {
        case 1:
            result += 20;
            break;
        case 2:
            result += 30;
            break;
        default:
            result += 10;
            break;
    }
    return result > 20 ? result : result;
}
Copy the code

The above code has one if statement, one for loop, two case statements, and one ternary operator, so the complexity of the code is 1+2+1+1+1=6. In addition, it is important to note | | and && statements will be counted as a decision node, such as the following code of complex to 3:

function testComplexity(*param*) {
    let result = 1;
    if (param > 0 && param < 10) {
        result--;
    }
    return result;
}
Copy the code

3.3 Point edge calculation method

M = E − N + 2P
Copy the code
  • E: Controls the number of edges in the flow diagram
  • N: controls the number of nodes in the flow diagram
  • P: number of independent components

The first two, edges and nodes, are the most basic concepts in a data structure diagram:

P is the number of independent components in the graph. What does independent components mean? Take a look at the following two graphs, connected on the left and disconnected on the right:

  • Connected graph: Any two vertices in a graph are connected

A connected graph is an independent component in the graph, so the number of independent components in the left diagram is 1, and there are two independent components on the right.

For the control flow diagram that our code translates into, normally all nodes should be connected, unless you return before some nodes, which is obviously wrong. So the number of individual components of each program flowchart is 1, so the above formula can be simplified as M = E − N + 2.

4. Reduce cyclomatic complexity of code

We can reduce cyclomatic complexity through code refactoring.

Be careful with refactoring; the sample code is just an idea, and the actual code is far more complex than the sample code.

4.1 Abstract Configuration

Complex logical decisions are simplified through abstract configuration. For example, the following code performs operations based on the user’s choices, reduces the complexity of the code after refactoring, and if there is a new option, it can be directly added to the configuration, without having to go into the code logic to make changes:

4.2 Single responsibility – Refining function

Single responsibility principle (SRP) : Every class should have a single function, and a class should have only one reason for change.

In JavaScript, there are not so many classes that need to be used, and the single responsibility principle applies more at the object or method level.

The function should do one thing, do one thing well, do one thing only. Clean code

The key is how to define this “one thing”, how to abstract the logic in the code, and effectively refine the function to reduce the code complexity and maintenance costs.

4.3 Replace control flags with break and return

We often use a control flag to indicate that the program is running in a certain state. In many cases, using breaks and returns can replace these flags and reduce code complexity.

4.4 Replace parameters with functions

The setField and getField functions are typical functions that replace parameters. If there are no setField and getField functions, we may need a very complicated setValue and getValue to complete the assignment operation:

4.5 Simplified condition judgment – Reverse condition

Some complex conditional judgments may be made easier by reverse thinking.

4.6 Simplified judgment of conditions – merge conditions

Combine complex redundant conditional judgments.

4.7 Simplified condition judgment – extraction conditions

Semantically extract complex and difficult conditions.

5. Cyclomatic complexity detection method

5.1 eslint rules

Eslint provides rules for checking code cyclomatic complexity:

We will turn on the complexity rule in Rules and set the rule severity to WARN or error for code with cyclomatic complexity greater than 0.

    rules: {
        complexity: [
            'warn',
            { max: 0}}]Copy the code

Eslint will then automatically detect the code complexity of all functions and output a message similar to the following.

Method 'testFunc' has a complexity of 12. Maximum allowed is 0
Async function has a complexity of 6. Maximum allowed is0....Copy the code

5.2 CLIEngine

You can use esLint’s CLIEngine to scan code locally using custom ESLint rules and get the output of the scan.

Initialize CLIEngine:

const eslint = require('eslint');

const { CLIEngine } = eslint;

const cli = new CLIEngine({
    parserOptions: {
        ecmaVersion: 2018,},rules: {
        complexity: [
            'error',
            { max: 0}}}]);Copy the code

Use executeOnFiles to scan for the specified files and retrieve the results, filtering out all complexity messages.

const reports = cli.executeOnFiles(['. ']).results;

for (let i = 0; i < reports.length; i++) {
    const { messages } = reports[i];
    for (let j = 0; j < messages.length; j++) {
        const { message, ruleId } = messages[j];
        if (ruleId === 'complexity') {
             console.log(message); }}}Copy the code

5.3 to extract the message

To extract useful information from esLint’s test results, test a few different types of functions first and see what esLint’s test results look like:

function func1() {
    console.log(1);
}

const func2 = (a)= > {
    console.log(2);
};

class TestClass {
    func3() {
        console.log(3); }}async function func4() {
    console.log(1);
}
Copy the code

Execution Result:

Function 'func1' has a complexity of 1. Maximum allowed is 0.
Arrow function has a complexity of 1. Maximum allowed is 0.
Method 'func3' has a complexity of 1. Maximum allowed is 0.
Async function 'func4' has a complexity of 1. Maximum allowed is 0.
Copy the code

You can see that everything is the same except for the function type and the complexity.

Function type:

  • Function: ordinary function
  • Arrow function: arrow function
  • Method: class method
  • Async function: asynchronous function

Interception method type:

const REG_FUNC_TYPE = /^(Method |Async function |Arrow function |Function )/g;

function getFunctionType(message) {
    let hasFuncType = REG_FUNC_TYPE.test(message);
    return hasFuncType && RegExp. $1;
}
Copy the code

To extract the useful parts:

const MESSAGE_PREFIX = 'Maximum allowed is 1.';
const MESSAGE_SUFFIX = 'has a complexity of ';

function getMain(message) {
    return message.replace(MESSAGE_PREFIX, ' ').replace(MESSAGE_SUFFIX, ' ');
}

Copy the code

Extraction method Name:

function getFunctionName(message) {
    const main = getMain(message);
    let test = /'([a-zA-Z0-9_$]+)'/g.test(main);
    return test ? RegExp. $1 : The '*';
}
Copy the code

Intercepting code complexity:

function getComplexity(message) {
    const main = getMain(message);
    (/(\d+)\./g).test(main);
    return +RegExp. $1;
}
Copy the code

In addition to message, there are other useful messages:

  • Function position: getmessagesIn thelinecolumnThe row and column positions of the function
  • Current file name:reportsYou can obtain the absolute path of the currently scanned filefilePathTo obtain the real file name, do the following:
filePath.replace(process.cwd(), ' ').trim()
Copy the code
  • According to the complexity level of the function, reconstruction suggestions are given:
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
Cyclomatic complexity The code in
1-10 Do not need to reconstruct
11-15 Suggest refactoring
> 15 Refactoring is highly recommended

6. Architecture design

The code complexity detection is encapsulated into a basic package, and the detection data is output according to the customized configuration for other applications to call.

Above shows the use eslint code complexity thinking, here we will encapsulate it into a general tools, considering the tool may be used under different scenarios, such as: web version of the analysis report, cli version of the command line tools, we put the general ability of abstracting in the form of NPM package for other applications.

Before we can calculate the complexity of the project code, we need to have a basic capability, code scanning, that is, we need to know which files in the project we are going to analyze. Eslint has this capability first, we can also use glob to traverse files directly. But they both have the disadvantage that the ignore rule is different, which is a bit of a learning cost for the user, so I’ll wrap the code scan manually here, using the generic NPM Ignore rule, so that the code scan can use configuration files like.gitignore directly. In addition, code scanning is a fundamental capability of code analysis, and other code analysis can also be common.

  • Basic ability
    • Code scanning capability
    • Complexity detection capability
    • .
  • application
    • Command line tool
    • Code analysis report
    • .

7. Basic Ability – Code scanning

Both the NPM package and the CLI command source code covered in this article are available in my open source project awesome- CLI.

Awesome-cli is my new open source project: a fun and useful command line tool that will continue to be maintained, stay tuned, welcome star.

Code scan (C-scan) source: github.com/ConardLi/aw…

Code scanning is the underlying capability of code analysis. It mainly helps us get the file path we want. It should satisfy the following two requirements:

  • What type of file do I want to get
  • I don’t want any files

7.1 the use of

npm i c-scan --save

const scan = require('c-scan');
scan({
    extensions:'**/*.js'.rootPath:'src'.defalutIgnore:'true'.ignoreRules: [].ignoreFileName:'.gitignore'
});
Copy the code

7.2 the return value

Array of compliant file paths:

7.3 parameter

  • extensions

    • Scan file extension
    • Default value:**/*.js
  • rootPath

    • Scanning file Path
    • Default value:.
  • defalutIgnore

    • Whether to enable default ignore (globRule)
    • glob ignoreRules are for internal use, for uniformityignoreRules: Custom rules are usedgitignoreThe rules
    • Default value:true
    • On by defaultglob ignoreRules:
const DEFAULT_IGNORE_PATTERNS = [
    'node_modules/**'.'build/**'.'dist/**'.'output/**'.'common_build/**'
];
Copy the code
  • ignoreRules

    • Custom ignore rules (gitignoreRule)
    • Default value:[]
  • ignoreFileName

    • Custom ignore rule configuration file path (gitignoreRule)
    • Default value:.gitignore
    • Specified asnullIs not enabledignoreThe configuration file

7.4 Core Implementation

Based on glob, customize ignore rule for secondary encapsulation.

@param {*} rootPath and path * @param {*} Extensions * @param {*} defalutIgnore Whether to enable default ignore */
function getGlobScan(rootPath, extensions, defalutIgnore) {
    return new Promise(resolve= > {
        glob(`${rootPath}${extensions}`,
            { dot: true.ignore: defalutIgnore ? DEFAULT_IGNORE_PATTERNS : [] },
            (err, files) => {
                if (err) {
                    console.log(err);
                    process.exit(1);
                }
                resolve(files);
            });
    });
}

/** * Load ignore configuration file and process it into an array * @param {*} ignoreFileName */
async function loadIgnorePatterns(ignoreFileName) {
    const ignorePath = path.resolve(process.cwd(), ignoreFileName);
    try {
        const ignores = fs.readFileSync(ignorePath, 'utf8');
        return ignores.split(/[\n\r]|\n\r/).filter(pattern= > Boolean(pattern));
    } catch (e) {
        return[]; }}/** * Configure filter file list based on ignore * @param {*} files * @param {*} ignorePatterns * @param {*} CWD */
function filterFilesByIgnore(files, ignorePatterns, ignoreRules, cwd = process.cwd()) {
    const ig = ignore().add([...ignorePatterns, ...ignoreRules]);
    const filtered = files
        .map(raw= > (path.isAbsolute(raw) ? raw : path.resolve(cwd, raw)))
        .map(raw= > path.relative(cwd, raw))
        .filter(filePath= >! ig.ignores(filePath)) .map(raw= > path.resolve(cwd, raw));
    return filtered;
}
Copy the code

8. Basic ability – Code complexity detection

Code complexity check (C-Complexity) source: github.com/ConardLi/aw…

The code inspection base package should have the following capabilities:

  • Customize scan folders and types
  • Ignore files
  • Define minimum reminder code complexity

8.1 the use of

npm i c-complexity --save

const cc = require('c-complexity');
cc({},10);
Copy the code

8.2 the return value

  • FileCount: indicates the number of files
  • FuncCount: The number of functions
  • Result: detailed result
    • FuncType: Function type
    • FuncName; The name of the function
    • Position: detailed position (row and column number)
    • FileName: indicates the relative file path
    • Complexity: code complexity
    • Advice: Refactor the advice

8.3 parameter

  • scanParam
    • Inherited from the parameters scanned by the code above
  • min
    • Minimum reminder code complexity, default is 1

9. Application – Code complexity detection tool

Code complexity detection (Conard CC) source: github.com/ConardLi/aw…

9.1 Specifying the minimum reminder complexity

Minimum complexity that can trigger alerts.

  • The default is10
  • Through the commandconard cc --min=5The custom

9.2 Specifying Scan Parameters

User-defined scan rules

  • The scan parameters are inherited from abovescan param
  • Such as:conard cc --defalutIgnore=false

10. Application – Code complexity report

Part of the screenshots come from our internal project quality monitoring platform. As an important indicator, cyclomatic complexity plays a crucial role in measuring project code quality.

Trends in code complexity

A scheduled task obtains the daily code complexity, code lines, and number of functions of the code, and draws a line graph of the change trend of code complexity and code lines based on the daily data.


Determine the health of the project by the trend of [complexity/lines of code] or [complexity/functions].

  • If the ratio keeps going up, your code is getting harder and harder to understand. This not only exposes us to the risk of unexpected functional interactions and defects, but also makes it difficult to reuse code and modify and test it due to the excessive cognitive burden we face in modules with more or less related functionality. (Figure 1)

  • If the ratio changes at a certain stage, it indicates that the iteration quality is poor during this period. (Figure 2 below)

  • Complexity graphs can quickly help you spot both of these problems earlier, and you may need to refactor your code if you find them. Complexity trends are also useful for tracking your code refactoring. The downward trend in complexity bodes well. This either means that your code becomes simpler (for example, refactoring if-else into a polymorphic solution) or less code (extracting irrelevant parts into other modules). (Figure 3)

  • After the code is refactored, you need to continue to explore complexity trends. What often happens is that we spend a lot of time and effort refactoring, fail to address the root cause, and soon the complexity slips back to where it was. (Figure 4) You might think this is an exception, but studies have shown that this happens very often after analyzing hundreds of code bases. Therefore, it is important to keep an eye on trends in code complexity.

Code complexity file distribution

Count the number of functions for each complexity distribution.

Code complexity file details

Calculate the code complexity of each function, list the high-complexity file distribution from highest to lowest, and give suggestions for refactoring.

In actual development, not all code needs to be analyzed, such as packaging products, static resource files, etc. These files can often mislead our analysis results. Analysis tools now ignore some rules by default, such as: Gitignore files, static directories and so on. In fact, these rules also need to be improved according to the actual situation of the project, so that the analysis results become more accurate.

reference

  • Plus push r & D quality and standard combat
  • codescene
  • Cyclomatic complexity stuff – Front-end Code Quality series (Part 2)
  • Code quality control – complexity detection
  • Let’s go into cyclomatic complexity

The clown pictures at the beginning of the article come from the Internet, if there is any infringement, please contact me to delete, the rest of the pictures are my original pictures.

summary

I hope after reading this article you can have the following help:

  • Understand the meaning and calculation of cyclomatic complexity
  • Cyclomatic complexity can be used to improve project quality

If there are any mistakes in this article, please correct them in the comments section. If this article has helped you, please like it and follow it.

Both the NPM package and the CLI command source code covered in this article are available in my open source project awesome- CLI.

Want to read more quality articles, can follow my Github blog, your star✨, like and follow is my continuous creation power!

I recommend you to follow my wechat public account [Code Secret Garden] and push high-quality articles every day. We can communicate and grow together.