What is cyclomatic complexity
Cyclomatic complexity, look at the Explanation in The Encyclopedia
Cyclomatic complexity is a measure of code complexity created by Thomas J. McCabe, Sr., in 1976. Is put forward. In the concept of software testing, cyclomatic complexity is used to measure the complexity of a module’s decision structure, which is represented by the number of linearly independent paths, that is, the minimum number of paths needed to be tested reasonably to prevent errors. High cyclomatic complexity indicates that program code may be of low quality and difficult to test and maintain. For example, if a piece of source code contains no control-flow statements (conditions or decision points), the cyclomatic complexity of the code is 1 because there is only one path in the code. If a piece of code contains only one if statement, and the if statement has only one condition, the cyclomatic complexity of the code is 2. A code block containing two nested if statements, or an if statement with two conditions, has a cyclomatic complexity of 3.
To put it simply, cyclomatic complexity represents the number of decision nodes. The more decision statements, the higher cyclomatic complexity
So how do we calculate the cyclomatic complexity of code, and here we’re starting from the definition, and we can see that cyclomatic complexity is the number of nodes, so cyclomatic complexity is, ok
Cyclomatic complexity = Decision node +1
In Java, common decision nodes are:
- If statement
- While statement
- For statement
- A case statement
- Catch statement
- And and or Boolean operations
- ? : ternary operator
Plug-in design
example
The cyclomatic complexity detection is theoretically the earlier the better, so it is necessary to give the corresponding detection prompt in the coding period, so an Android Studio plug-in is written to detect the prompt.
As shown in the figure, when the cyclomatic complexity of a method exceeds the threshold, there is a red line under the method name and an error message. The plug-in we wrote is such a Code Inspection plug-in. Currently supports both Kotlin and Java. Threshold Settings in the Preferences | Editor | Inspections, as shown
The default is 10
design
The principle of the whole detection is to use the above mentioned formula cyclomatic complexity = to determine the node +1.
So the key is to find the number of nodes in the method. The overall plug-in design is shown below
Here, we use the plug-in SDK provided by Intellij IDEA to find the decision node by traversal the current file through PSI. NodeChecker is an abstract class, where nodeSet represents the PsiElement set corresponding to the decision node. Fun check(element: T): Int Returns the number of judge nodes. Fun isNode(element: PsiElement): Boolean checks whether the current PSI node is a judge node.
Abstract class NodeChecker {protected open val nodeSet:MutableSet< class <out PsiElement>> = mutableSetOf() Abstract Fun check(element: PsiElement): Int Open Fun isNode(element: PsiElement): Boolean { nodeSet.forEach { if (it.isInstance(element)) { return true } } return false } }Copy the code
JavaNodeChecker inherits NodeChecker and is used to determine cyclomatic complexity of Java code
class JavaNodeChecker : NodeChecker() {
override val nodeSet: MutableSet<Class<out PsiElement>>
get() = mutableSetOf(
PsiIfStatement::class.java,
PsiWhileStatement::class.java,
PsiDoWhileStatement::class.java,
PsiForStatement::class.java,
PsiForeachStatement::class.java,
PsiSwitchLabelStatement::class.java,
PsiCatchSection::class.java,
PsiConditionalExpression::class.java,
)
override fun check(statement: PsiElement): Int {
var nodeNum = 0
if (isNode(statement)) {
nodeNum++
}
statement.children.forEach {
if (it is PsiJavaToken && (JavaTokenType.ANDAND == it.tokenType || JavaTokenType.OROR == it.tokenType)) {
nodeNum++
}
nodeNum += check(it)
}
return nodeNum
}
}
Copy the code
Principle is very simple, copying the nodeSet, which contains the Java determine the Psi of a node type, each psiElement recursive judgment at the same time, for && and | | symbols do extra sense.
KotlinNodeChecker and JavaNodeChecker are similar, only because the Psi type is different and the nodeSet is different
class KTNodeChecker : NodeChecker() {
override val nodeSet: MutableSet<Class<out PsiElement>>
get() = mutableSetOf(
KtIfExpression::class.java,
KtWhileExpression::class.java,
KtDoWhileExpression::class.java,
KtForExpression::class.java,
KtSafeQualifiedExpression::class.java,
KtWhenConditionWithExpression::class.java,
KtCatchClause::class.java
)
override fun check(element: PsiElement): Int {
var nodeNum = 0
if (isNode(element)) {
nodeNum++
}
element.children.forEach {
if (it is KtBinaryExpression && (KtTokens.ANDAND == it.operationToken || KtTokens.OROR == it.operationToken)) {
nodeNum++
}
nodeNum+=check(it)
}
return nodeNum
}
}
Copy the code
JavaCodeMetricsInspection and KTCodeMetricsInspection respectively corresponding to the plugin. The XML localInspection implementation class, And use JavaNodeChecker and KTNodeChecker respectively to detect PsiMethod
< localInspection language = "JAVA" displayName = "JAVA code cyclomatic complexity" groupPath = "JAVA" groupBundle = "messages. InspectionsBundle" groupKey="group.names.probable.bugs" enabledByDefault="true" level="ERROR" implementationClass="com.skateboard.codemetrics.JavaCodeMetricsInspection" /> <localInspection language="kotlin" DisplayName = "Kotlin code cyclomatic complexity" groupPath = "Kotlin groupBundle" = "messages. InspectionsBundle" groupKey="group.names.probable.bugs" enabledByDefault="true" level="ERROR" implementationClass="com.skateboard.codemetrics.KTCodeMetricsInspection"/>Copy the code
About how to write Code Inspection plug-in, related documents can reference plugins.jetbrains.com/docs/intell…
The project code has been uploaded to github github.com/skateboard1…
The last
In the future, we will consider adding incremental loop complexity detection in code submission or Git Pipleline to achieve a closed loop of cyclomatic complexity code quality
Follow my official account: “Old Arsenic on skateboards”