Understanding Cyclomatic Complexity and Code Quality Metrics
This article explains cyclomatic complexity, its calculation formulas, practical examples, and how to measure and improve code quality using tools like SonarQube, ESLint, and codeMetrics, while also presenting refactoring techniques such as single‑responsibility, open‑closed, polymorphism, early returns, and functional programming to reduce complexity.
Cyclomatic complexity, introduced by Thomas J. McCabe in 1976, quantifies the number of linearly independent paths through a program's control‑flow graph, indicating the minimum number of test cases needed to achieve thorough coverage; higher values suggest lower code quality and harder maintenance.
Measurement Formulas
Three common formulas are used:
V(G) = e - n + 2p (edges, nodes, connected components; p is usually 1).
V(G) = number of decision nodes + 1 (region count).
V(G) = R (number of regions in the control‑flow graph).
For a sample function, the calculation yields V(G) = 9 - 7 + 2 = 4.
Example Calculation
function sort(A: number[]): void {
let i = 0;
const n = 4;
let j = 0;
while (i < n - 1) {
j = i + 1;
while (j < n) {
if (A[i] < A[j]) {
const temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
i = i + 1;
}
}The above function has a cyclomatic complexity of 4.
Detection Tools
Popular static‑analysis tools that report cyclomatic complexity include:
Project
SonarQube
ESLint
codeMetrics
Complexity Metric
Supported
Supported
Supported
Detection Efficiency
High
Medium
Low
Accuracy
High
Medium
Low
Supported Languages
Many
General
General
Guidance for High Complexity
High
Medium
Low
Installation steps for SonarQube involve downloading the server, configuring the database, installing the scanner, and linking projects. For ESLint, install the core package and the eslint-plugin-complexity plugin:
npm install eslint --save-dev npm install eslint-plugin-complexity --save-devConfigure ESLint with a .eslintrc.js file:
module.exports = {
env: {
browser: true,
es6: true,
},
extends: ['eslint:recommended'],
plugins: ['complexity'],
rules: {
'complexity': ['error', { max: 10 }],
},
};Refactoring Practices to Reduce Complexity
1. Single‑Responsibility Principle
class User {
login(username: string, password: string): boolean { /* ... */ }
getProfile(userId: number): object { /* ... */ }
}Refactored into separate classes:
class Authenticator {
login(username: string, password: string): boolean { /* ... */ }
}
class UserProfile {
getProfile(userId: number): object { /* ... */ }
}2. Open‑Closed Principle
function quickSort(data: number[]) { /* implementation */ }
function mergeSort(data: number[]) { /* implementation */ }Refactored using strategy pattern:
interface SortStrategy {
sort(data: number[]): number[];
}
class QuickSortStrategy implements SortStrategy { /* ... */ }
class MergeSortStrategy implements SortStrategy { /* ... */ }
class Sorter {
private strategy: SortStrategy;
constructor(strategy: SortStrategy) { this.strategy = strategy; }
sort(data: number[]) { return this.strategy.sort(data); }
}3. Remove Duplicate Code
function calculateTotalPrice(products) {
let totalPrice = 0;
for (let i = 0; i < products.length; i++) {
const product = products[i];
totalPrice += product.price * product.quantity;
if (product.isOnSale) { totalPrice -= product.discount; }
}
return totalPrice;
}After extracting price calculation:
function calculateTotalPrice(products) {
let totalPrice = 0;
for (let i = 0; i < products.length; i++) {
const product = products[i];
totalPrice += calculateProductPrice(product);
}
return totalPrice;
}
function calculateProductPrice(product) {
let productPrice = product.price * product.quantity;
if (product.isOnSale) { productPrice -= product.discount; }
return productPrice;
}4. Introduce Polymorphism
abstract class Animal {
abstract makeSound(): void;
}
class Dog extends Animal { makeSound() { console.log('汪汪汪!'); } }
class Cat extends Animal { makeSound() { console.log('喵喵喵!'); } }
const animals: Animal[] = [new Dog(), new Cat()];
animals.forEach(animal => animal.makeSound());5. Early Return
function calculateBonus(salary: number, level: string) {
if (salary <= 0) { return 0; }
let bonus = 0;
switch (level) {
case 'A': bonus = salary * 0.2; break;
case 'B': bonus = salary * 0.1; break;
case 'C': bonus = salary * 0.05; break;
default: break;
}
return bonus;
}6. Use Small Functions
function processItems(items: any[]) {
const results = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const result = processItem(item);
if (result !== null) { results.push(result); }
}
return results;
}
function processItem(item: any) {
const result1 = processItemPart1(item);
const result2 = processItemPart2(item);
if (result1 !== null && result2 !== null) {
return result1 * 2 + result2 * 3;
}
return null;
}
function processItemPart1(item: any) { /* ... */ }
function processItemPart2(item: any) { /* ... */ }7. Functional Programming
function sum(array) {
return array.reduce((acc, val) => acc + val, 0);
}Assistance from Copilot Chat
When cyclomatic complexity becomes too high, developers can use AI assistants like Copilot Chat to suggest refactorings, extract functions, and apply the above best practices automatically.
Overall, understanding the metric, using appropriate static‑analysis tools, and applying systematic refactoring techniques help keep code maintainable and testable.
TAL Education Technology
TAL Education is a technology-driven education company committed to the mission of 'making education better through love and technology'. The TAL technology team has always been dedicated to educational technology research and innovation. This is the external platform of the TAL technology team, sharing weekly curated technical articles and recruitment information.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.