“This is the 9th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

primers

In the # Githook practice using golangci-lint as an example, we mentioned using Githook + Golangci-lint to check the quality of code before committing. There is a code complexity check item, which can be used to check the complexity of the code in the project. If the code is too complex, the submission will be rejected. Use code complexity checks to avoid writing unmaintainable code.

Speaking of complexity, those who brush Leetcode frequently may immediately think of time complexity and space complexity, but in fact, code specification inspection software cannot do such a detailed inspection. As a result, code specification checking software often uses the concept of cyclomatic complexity to determine the complexity of code.

So what is cyclomatic complexity?

Simply put, if a piece of code has many branches of flow control statements, it can double the workload of the testing girl.

For example, there was a lot of discussion about the large number of if statements that were found in the early Taio E Scroll code when it was decompiled, and the most obvious problem with this was that it reduced maintainability. (The author has only studied for one month, so the requirement should not be too high, but as professionals, we should avoid this situation.)

www.zhihu.com/question/29…

The industry practice

cyclop

Golangci-lint is a plugin that iterates through all functions in your code and then iterates through the syntax tree (AST) of the function, increasing the complexity of the code by 1

  • ast.FuncDeclIf a function is defined within a function, it may be that the object code implements a closure or coroutines within the function
  • ast.IfStmtFor every if, cyclomatic complexity increases by +1
  • ast.ForStmt.ast.RangeStmtCyclomatic complexity +1 for each for
  • ast.CaseClauseCyclomatic complexity increases by 1 for each additional case statement in a switch statement
  • ast.CommClauseSwitch statement each additional slavechannelCase of receiving data, cyclomatic complexity +1
  • ast.BinaryExprIn each expression if there isandororLogically, cyclomatic complexity +1

The core logic is shown below

type complexityVisitor struct {
	Complexity int
}

func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
	switch n := n.(type) {
	case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause:
		v.Complexity++
	case *ast.BinaryExpr:
		if n.Op == token.LAND || n.Op == token.LOR {
			v.Complexity++
		}
	}
	return v
}
Copy the code

Here’s the code

Evaluation: Although the cyclomatic complexity statistics above are simple and rough, there may be some cases of killing innocent people by mistake, but according to practical experience, it can still prevent “dirty” code in most cases. The only exception is if you need to use switch-case to implement the state machine, then you need to adjust the cyclomatic complexity upper limit. Or add //nolint to the code.

gocyclo

Gocyclo was also introduced as a plug-in in Golangci-Lint, which is a bit more nuanced about code complexity

  • encounterif.forStatement complexity +1
  • encounterswitch-caseStatement complexity +1, encountereddefaultBranches don’t add +1
  • encounter&&or||Operator, complexity +1
type complexityVisitor struct {
	// complexity is the cyclomatic complexity
	complexity int
}

// Visit implements the ast.Visitor interface.
func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
	switch n := n.(type) {
	case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt:
		v.complexity++
	case *ast.CaseClause:
		ifn.List ! =nil { // ignore default case
			v.complexity++
		}
	case *ast.CommClause:
		ifn.Comm ! =nil { // ignore default case
			v.complexity++
		}
	case *ast.BinaryExpr:
		if n.Op == token.LAND || n.Op == token.LOR {
			v.complexity++
		}
	}
	return v
}
Copy the code

Here’s the code

conclusion

The core idea of the above open-source code cyclomatic complexity detection software is to count cyclomatic complexity by traversing the nodes of the abstract syntax tree (AST) of the function. If a specific node is encountered, the cyclomatic complexity of the code is +1.

How can cyclomatic complexity be reduced

  • Split large functions, which can be split if there are too many if statements /for loops and there is no correlation between the top and the bottom
  • usemapStore mappings from conditions to results, eliminating excessiveifjudge
  • Optimize your algorithm 😀