Giter VIP home page Giter VIP logo

gocognit's Introduction

Go Reference go-recipes

Gocognit

Gocognit calculates cognitive complexities of functions (and methods) in Go source code. A measurement of how hard does the code is intuitively to understand.

Understanding the complexity

Given code using if statement,

func GetWords(number int) string {
    if number == 1 {            // +1
        return "one"
    } else if number == 2 {     // +1
        return "a couple"
    } else if number == 3 {     // +1
        return "a few"
    } else {                    // +1
        return "lots"
    }
} // Cognitive complexity = 4

Above code can be refactored using switch statement,

func GetWords(number int) string {
    switch number {             // +1
        case 1:
            return "one"
        case 2:
            return "a couple"
        case 3:
            return "a few"
        default:
            return "lots"
    }
} // Cognitive complexity = 1

As you see above codes are the same, but the second code are easier to understand, that is why the cognitive complexity score are lower compare to the first one.

Comparison with cyclomatic complexity

Example 1

Cyclomatic complexity

func GetWords(number int) string {      // +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

Cognitive complexity

func GetWords(number int) string {
    switch number {                     // +1
        case 1:
            return "one"
        case 2:
            return "a couple"
        case 3:
            return "a few"
        default:
            return "lots"
    }
} // Cognitive complexity = 1

Cognitive complexity give lower score compare to cyclomatic complexity.

Example 2

Cyclomatic complexity

func SumOfPrimes(max int) int {         // +1
    var total int

OUT:
    for i := 1; i < max; i++ {          // +1
        for j := 2; j < i; j++ {        // +1
            if i%j == 0 {               // +1
                continue OUT
            }
        }
        total += i
    }

    return total
} // Cyclomatic complexity = 4

Cognitive complexity

func SumOfPrimes(max int) int {
    var total int

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

    return total
} // Cognitive complexity = 7

Cognitive complexity give higher score compare to cyclomatic complexity.

Rules

The cognitive complexity of a function is calculated according to the following rules:

Note: these rules are specific for Go, please see the original whitepaper for more complete reference.

Increments

There is an increment for each of the following:

  1. if, else if, else
  2. switch, select
  3. for
  4. goto LABEL, break LABEL, continue LABEL
  5. sequence of binary logical operators
  6. each method in a recursion cycle

Nesting level

The following structures increment the nesting level:

  1. if, else if, else
  2. switch, select
  3. for
  4. function literal or lambda

Nesting increments

The following structures receive a nesting increment commensurate with their nested depth inside nesting structures:

  1. if
  2. switch, select
  3. for

Installation

go install github.com/uudashr/gocognit/cmd/gocognit@latest

or

go get github.com/uudashr/gocognit/cmd/gocognit

Usage

$ gocognit
Calculate cognitive complexities of Go functions.

Usage:

  gocognit [<flag> ...] <Go file or directory> ...

Flags:

  -over N    show functions with complexity > N only
             and return exit code 1 if the output is non-empty
  -top N     show the top N most complex functions only
  -avg       show the average complexity over all functions,
             not depending on whether -over or -top are set
  -json      encode the output as JSON
  -f format  string the format to use 
             (default "{{.PkgName}}.{{.FuncName}}:{{.Complexity}}:{{.Pos}}")

The (default) output fields for each line are:

  <complexity> <package> <function> <file:row:column>

The (default) output fields for each line are:

  {{.Complexity}} {{.PkgName}} {{.FuncName}} {{.Pos}}

or equal to <complexity> <package> <function> <file:row:column>

The struct being passed to the template is:

  type Stat struct {
    PkgName    string
    FuncName   string
    Complexity int
    Pos        token.Position
  }

Examples:

$ gocognit .
$ gocognit main.go
$ gocognit -top 10 src/
$ gocognit -over 25 docker
$ gocognit -avg .
$ gocognit -ignore "_test|testdata" .

The output fields for each line are:

<complexity> <package> <function> <file:row:column>

Ignore individual functions

Ignore individual functions by specifying gocognit:ignore directive.

//gocognit:ignore
func IgnoreMe() {
    // ...
}

Related project

gocognit's People

Contributors

alexandear avatar davidkroell avatar ivanruski avatar nikolaydubina avatar rliebz avatar svilgelm avatar uudashr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

gocognit's Issues

Value is not so meaningful for go code

in GoLang, we have a wast amount of if err != nil {}.

let's consider a following example:

if _, err := strconv.ParseFloat(value, 64) ; err != nil {
  return err
}

There could be a dozen of similar statements in a function, each adding two points to a computed complexity value.

These if's add no complexity - modern IDE's even collapse these lines due there is nothing of interest.

It seems the tool should ignore such lines.

Add support for `ignore` like gocyclo

Shall we add support for the ignore in both command flag and code directive?

Example: gocyclo

We expect to do as the following:

  • Command flag

     $ gocognit
     Usage:
     		gocognit [flags] <Go file or directory> ...
     Flags:
     		-ignore REGEX

    Examples

     $ gocognit "_test|vendor/|code_gen|" .
  • Ignoring specific functions

     //gocognit:ignore
     func f1() {
     	// ...
     }
     	
     //gocognit:ignore
     var f2 = func() {
     	// ...
     }

Support ignore directive

Ignore specific function such as

 //gocognit:ignore
 func f1() {
 	// ...
 }
 	
 //gocognit:ignore
 var f2 = func() {
 	// ...
 }

Refer to #18

Tag v1.1 may not be valid according to Go modules

I'm trying to install version v1.1 released a few hours ago, but go keeps telling me:

env GOPROXY='direct' go install github.com/uudashr/gocognit/cmd/[email protected]
go: github.com/uudashr/[email protected]: no matching versions for query "v1.1"

This may be due to the fact that Go requires semantic versions, and v1.1 misses the patch version. A semver tag for the same commit would look like v1.1.0, with the zeroth patch revision.

In the meantime, the workaround is using the revision hash, which works:

env GOPROXY='direct' go install github.com/uudashr/gocognit/cmd/gocognit@bc9ca12659bf

goroutine stack exceeds 1000000000-byte limit

I run gocognit v1.0.2 on gocognit repository branch master and got goroutine stack exceeds 1000000000-byte limit

go run cmd/gocognit/main.go -over 15 .
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0201e0388 stack=[0xc0201e0000, 0xc0401e0000]
fatal error: stack overflow

runtime stack:
runtime.throw(0x113aa8e, 0xe)

But, run it on small codebase like this doesn't get the error

go version go1.16.5 darwin/amd64

Evaulate `goto` end practices

For goto end practices, gocognit is not able to evaluate properly for the following case:

// Generate is to create all the debian / DEBIAN files and directories.
func (me *Data) Generate(workingDir string) (err error) {
	var path string

	err = me.Sanitize()
	if err != nil {
		goto done
	}

	if workingDir == "" {
		err = fmt.Errorf("%s: workingDir = ''", ERROR_DIR_MISSING)
		goto done
	}

	// identify debian or DEBIAN directory by Control/BuildSource.
	if me.Control.BuildSource {
		path = filepath.Join(workingDir, "debian")
	} else {
		path = filepath.Join(workingDir, "DEBIAN")
	}

	err = me.createBaseDir(path)
	if err != nil {
		goto done
	}

	err = me.createControl(path)
	if err != nil {
		goto done
	}

	err = me.createCopyright(path)
	if err != nil {
		goto done
	}

	err = me.createChangelog(path)
	if err != nil {
		goto done
	}

	err = me.createSourceDir(path)
	if err != nil {
		goto done
	}

	err = me.writeSourceFormat(path)
	if err != nil {
		goto done
	}

	err = me.writeSourceLocalOptions(path)
	if err != nil {
		goto done
	}

	err = me.writeSourceOptions(path)
	if err != nil {
		goto done
	}

	err = me.writeManpages(path)
	if err != nil {
		goto done
	}

	err = me.writeScripts(path)
	if err != nil {
		goto done
	}

	err = me.writeRules(path)
	if err != nil {
		goto done
	}

	err = me.writeCompat(path)
	if err != nil {
		goto done
	}

	err = me.writeInstall(path)
	if err != nil {
		goto done
	}

done:
	return err
}

Will dump:

monteur/internal/archive/deb/Data.go:102:1: cognitive complexity 32 of func `(*Data).Generate` is hi
gh (> 20) (gocognit)

If switching the practice back to return everytime, gocognit seems okay.

Note that this "uni-direction" jump is a practice (adopted from https://www.kernel.org/doc/html/v4.17/process/coding-style.html#centralized-exiting-of-functions, specifically designed for clean-up (e.g. programatically closing files when defer is not suitable.


In case any zealot of anti-goto: it's fine to use goto for a single direction flow especially for clean up as long as it is not abused for structural programming replacement.

For most cases: http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html

Marking difference with sonarqube for logical sequence

Example:

func test1() {
        if a == 1 { // +1 if
        }
}
func test2() {
        if a == 1 && b == 1 { // +1 if, +1 &&
        }
}
func test3() {
        if a == 1 && b == 1 && c == 1 { // +1 if, +1 &&
        }
}
func test4() {
        if x == 1{ // +1 if
        if a == 1 && b == 1 && c == 1 { // +2 if, +1 &&
        }
        }
}

gives this:

8 pote test4 /tmp/a.go:13:1
6 pote test3 /tmp/a.go:13:1
4 pote test2 /tmp/a.go:8:1
1 pote test1 /tmp/a.go:3:1

If we read the "Sequences of logical operators" section of the whitepaper, I think this should be 4 2 2 1 instead. It seems like gocognit:

  • adds complexity for every && instead of only once
  • uses the current nesting to compute the && increment instead of a flat +1 (see Appendix C)

When presented with multiple paths, analysis is only reported for the last one

func analyze(paths []string) ([]gocognit.Stat, error) {
var (
stats []gocognit.Stat
err error
)
for _, path := range paths {
stats, err = analyzePath(path)
if err != nil {
return nil, err
}
}
return stats, nil
}

If err is nil, stats is simply overwritten instead of being merged. The for loop implies that gocognit should accept several packages, and in fact it does, but only reports stats for the last package, ignoring the rest.

I think, it should either merge the stats or print an error telling that it only supports one path.

Expressions in sequences of logical operators evaluated differently than variables

Given two equivalent functions:

package example

func ExprFunc(a, b, c any) bool {
	if a != nil || b != nil || c != nil {
		return false
	}

	return true
}

func VarFunc(a, b, c any) bool {
	na := a != nil
	nb := b != nil
	nc := c != nil
	if na || nb || nc {
		return false
	}

	return true
}

Both have the same logical flow. However, gocognit evaluates the complexity of these differently:

6 example ExprFunc example.go:3:1
2 example VarFunc example.go:11:1

Based on my read of the white paper, I would expect that both of these functions evaluate to a complexity of 2: one increment for the if statement, and one increment for the sequence of || logical operators. I'm not sure why a v != nil expression would incur an increment in the complexity score.

Incorrect Cognitive Complexity

Hi,

Could you take a look at my code and tell me why does the Gocognit calculate a wrong Cognitive Complexity for the function getRemote: https://github.com/SVilgelm/bumptag/blob/test-gocognit/bumptag.go#L166
Here is the action with the gocognit results: https://github.com/SVilgelm/bumptag/runs/349960561

And here is a sonarqube result with a cognitive complexity calculated as 16 for same function: https://sonarcloud.io/project/issues?branch=test-gocognit&id=SVilgelm_bumptag&open=AW8M-323UbmGVVKzFfdy&resolved=false&types=CODE_SMELL

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.