项目作者: uudashr

项目描述 :
Calculates cognitive complexities of functions in Go source code. (Golang cognitive complexity)
高级语言: Go
项目地址: git://github.com/uudashr/gocognit.git
创建时间: 2019-09-22T16:21:25Z
项目社区:https://github.com/uudashr/gocognit

开源协议:MIT License

下载


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,

  1. func GetWords(number int) string {
  2. if number == 1 { // +1
  3. return "one"
  4. } else if number == 2 { // +1
  5. return "a couple"
  6. } else if number == 3 { // +1
  7. return "a few"
  8. } else { // +1
  9. return "lots"
  10. }
  11. } // Cognitive complexity = 4

Above code can be refactored using switch statement,

  1. func GetWords(number int) string {
  2. switch number { // +1
  3. case 1:
  4. return "one"
  5. case 2:
  6. return "a couple"
  7. case 3:
  8. return "a few"
  9. default:
  10. return "lots"
  11. }
  12. } // 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

  1. func GetWords(number int) string { // +1
  2. switch number {
  3. case 1: // +1
  4. return "one"
  5. case 2: // +1
  6. return "a couple"
  7. case 3: // +1
  8. return "a few"
  9. default:
  10. return "lots"
  11. }
  12. } // Cyclomatic complexity = 4

Cognitive complexity

  1. func GetWords(number int) string {
  2. switch number { // +1
  3. case 1:
  4. return "one"
  5. case 2:
  6. return "a couple"
  7. case 3:
  8. return "a few"
  9. default:
  10. return "lots"
  11. }
  12. } // Cognitive complexity = 1

Cognitive complexity give lower score compare to cyclomatic complexity.

Example 2

Cyclomatic complexity

  1. func SumOfPrimes(max int) int { // +1
  2. var total int
  3. OUT:
  4. for i := 1; i < max; i++ { // +1
  5. for j := 2; j < i; j++ { // +1
  6. if i%j == 0 { // +1
  7. continue OUT
  8. }
  9. }
  10. total += i
  11. }
  12. return total
  13. } // Cyclomatic complexity = 4

Cognitive complexity

  1. func SumOfPrimes(max int) int {
  2. var total int
  3. OUT:
  4. for i := 1; i < max; i++ { // +1
  5. for j := 2; j < i; j++ { // +2 (nesting = 1)
  6. if i%j == 0 { // +3 (nesting = 2)
  7. continue OUT // +1
  8. }
  9. }
  10. total += i
  11. }
  12. return total
  13. } // 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

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

or

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

Usage

  1. $ gocognit
  2. Calculate cognitive complexities of Go functions.
  3. Usage:
  4. gocognit [<flag> ...] <Go file or directory> ...
  5. Flags:
  6. -over N show functions with complexity > N only
  7. and return exit code 1 if the output is non-empty
  8. -top N show the top N most complex functions only
  9. -avg show the average complexity over all functions,
  10. not depending on whether -over or -top are set
  11. -test indicates whether test files should be included
  12. -json encode the output as JSON
  13. -d enable diagnostic output
  14. -f format string the format to use
  15. (default "{{.Complexity}} {{.PkgName}} {{.FuncName}} {{.Pos}}")
  16. -ignore expr ignore files matching the given regexp
  17. The (default) output fields for each line are:
  18. <complexity> <package> <function> <file:row:column>
  19. The (default) output fields for each line are:
  20. {{.Complexity}} {{.PkgName}} {{.FuncName}} {{.Pos}}
  21. or equal to <complexity> <package> <function> <file:row:column>
  22. The struct being passed to the template is:
  23. type Stat struct {
  24. PkgName string
  25. FuncName string
  26. Complexity int
  27. Pos token.Position
  28. Diagnostics []Diagnostics
  29. }
  30. type Diagnostic struct {
  31. Inc string
  32. Nesting int
  33. Text string
  34. Pos DiagnosticPosition
  35. }
  36. type DiagnosticPosition struct {
  37. Offset int
  38. Line int
  39. Column int
  40. }

Examples:

  1. $ gocognit .
  2. $ gocognit main.go
  3. $ gocognit -top 10 src/
  4. $ gocognit -over 25 docker
  5. $ gocognit -avg .
  6. $ gocognit -ignore "_test|testdata" .

The output fields for each line are:

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

Ignore individual functions

Ignore individual functions by specifying gocognit:ignore directive.

  1. //gocognit:ignore
  2. func IgnoreMe() {
  3. // ...
  4. }

Diagnostic

To understand how the complexity are calculated, we can enable the diagnostic by using -d flag.

Example:

  1. $ gocognit -json -d .

It will show the diagnostic output in JSON format



JSON Output

json [ { "PkgName": "prime", "FuncName": "SumOfPrimes", "Complexity": 7, "Pos": { "Filename": "prime.go", "Offset": 15, "Line": 3, "Column": 1 }, "Diagnostics": [ { "Inc": 1, "Text": "for", "Pos": { "Offset": 69, "Line": 7, "Column": 2 } }, { "Inc": 2, "Nesting": 1, "Text": "for", "Pos": { "Offset": 104, "Line": 8, "Column": 3 } }, { "Inc": 3, "Nesting": 2, "Text": "if", "Pos": { "Offset": 152, "Line": 9, "Column": 4 } }, { "Inc": 1, "Text": "continue", "Pos": { "Offset": 190, "Line": 10, "Column": 5 } } ] } ]