dotnet / roslyn-analyzers

MIT License
1.59k stars 465 forks source link

Implement Cognitive Complexity metric #3527

Open Evangelink opened 4 years ago

Evangelink commented 4 years ago

Cognitive Complexity offers a new measurement of how hard code is to understand - one that strikes developers as intuitively right.

https://www.sonarsource.com/resources/white-papers/cognitive-complexity.html

This paper describes Cognitive Complexity, a new metric formulated to more accurately measure the relative understandability of methods. In doing so, it addresses the shortcomings of Cyclomatic Complexity in this area.

Cyclomatic Complexity uses a mathematical model to assess methods, producing accurate measurements of the effort required to test them, but inaccurate measurements of the effort required to understand them.

Cognitive Complexity breaks from the practice of using mathematical models to assess software maintainability. It starts from the precedents set by Cyclomatic Complexity, but uses human judgement to assess how structures should be counted, and to decide what should be added to the model as a whole. As a result, it yields method complexity scores which strike programmers as fairer relative assessments of maintainability than have been available with previous models.

Evangelink commented 4 years ago

@mavasani Could you confirm you are fine with this rule?

mavasani commented 4 years ago

@Evangelink Can you please provide the concrete formulae/algorithm you plan to use here? Note that just like other code complexity rules, the rule would be disabled by default and the primary use case is when used from Metrics.exe.

Evangelink commented 4 years ago

I am doing an extract of some parts of the paper to explain the point of the metric and also it's calculation process but I would recommend to read the full paper anyways.

Illustration of the problem

The two following methods have equal Cyclomatic Complexity, but are strikingly different in terms of understandability. The mathematical model underlying Cyclomatic Complexity gives these two methods equal weight, yet it is intuitively obvious that the control flow of SumOfPrimes is more difficult to understand than that of GetWords.

int SumOfPrimes(int max) // +1
{ 
    int total = 0;
    for (int i = 1; i <= max; i++) // +1
    {
        for (int j = 2; j < i; j++) // +1
        {
            if (i % j == 0) // +1
            {
                continue;
            }
        }

        total += i;
    }

    return total;
}  // Cyclomatic Complexity 4
string GetWords(int number)  // +1
{
    switch (number)
    {
        case 1: // +1
            return "one";
        case 2: // +1
            return "a couple";
        case 3: // +1
            return "a few";
        default:
            return "lots";
    }
 } // Cyclomatic Complexity 4

Methodology

A Cognitive Complexity score is assessed according to three basic rules:

  1. Ignore structures that allow multiple statements to be readably shorthanded into one

  2. Increment (add one) for each break in the linear flow of the code

  3. Increment when flow-breaking structures are nested

Additionally, a complexity score is made up of four different types of increments:

  1. Nesting - assessed for nesting control flow structures inside each other

  2. Structural - assessed on control flow structures that are subject to a nesting increment, and that increase the nesting count

  3. Fundamental - assessed on statements not subject to a nesting increment

  4. Hybrid - assessed on control flow structures that are not subject to a nesting increment, but which do increase the nesting count

Increment and Nesting

There is an increment for each of the following:

The following structures increment the nesting level:

Intuitively 'right' complexity scores

int SumOfPrimes(int max)
{ 
    int total = 0;
    for (int i = 1; i <= max; i++) // +1
    {
        for (int j = 2; j < i; j++) // +2 (1 + 1 for nesting)
        {
            if (i % j == 0) // +3 (1 + 3 for nesting)
            {
                continue; // +1
            }
        }

        total += i;
    }

    return total;
}  // Cognitive Complexity 7
string GetWords(int number)
{
    switch (number) // +1
    {
        case 1:
            return "one";
        case 2:
            return "a couple";
        case 3:
            return "a few";
        default:
            return "lots";
    }
 } // Cognitive Complexity 1