本文目录导读:

- 使用 ESLint 的复杂度规则
- 使用 Plato 工具
- 使用 Python 脚本来分析
- 使用 JavaScript 自定义检查脚本
- 使用 Husky + lint-staged(推荐)
- 高级检查 - 多种复杂度指标
- 最佳实践
Git钩子脚本可以通过多种方式检查函数复杂度,以下是几种常见的方法:
使用 ESLint 的复杂度规则
安装配置
npm install --save-dev eslint eslint-plugin-complexity
创建 .eslintrc.js
module.exports = {
rules: {
'complexity': ['error', { max: 10 }], // 最大圈复杂度
}
};
pre-commit 钩子脚本
#!/bin/bash # .git/hooks/pre-commit echo "检查函数复杂度..." # 获取暂存区的 .js 文件 STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".js$") if [ -z "$STAGED_FILES" ]; then echo "没有需要检查的 JS 文件" exit 0 fi # 运行 ESLint 检查复杂度 npx eslint --rule 'complexity: ["error", 10]' $STAGED_FILES if [ $? -ne 0 ]; then echo "复杂度检查失败!请简化函数复杂度" exit 1 fi echo "复杂度检查通过"
使用 Plato 工具
安装 Plato
npm install -g plato # 或 yarn global add plato
pre-commit 脚本
#!/bin/bash
# .git/hooks/pre-commit
THRESHOLD=15 # 复杂度阈值
# 检查每个暂存的文件
for file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|jsx|tsx)$'); do
if [ -f "$file" ]; then
# 使用 plato 检查复杂度
plato -r -d report_temp -t "Complexity Check" "$file" > /dev/null 2>&1
# 解析报告中的复杂性信息
COMPLEXITY=$(grep -oP '(?<=cyclomatic complexity )[0-9]+' report_temp/report.json 2>/dev/null)
if [ ! -z "$COMPLEXITY" ] && [ "$COMPLEXITY" -gt "$THRESHOLD" ]; then
echo "⚠️ 文件 $file 中存在函数复杂度超标 ($COMPLEXITY > $THRESHOLD)"
rm -rf report_temp
exit 1
fi
rm -rf report_temp
fi
done
echo "复杂度检查通过"
使用 Python 脚本来分析
安装 radon
pip install radon
pre-commit 脚本
#!/usr/bin/env python3
# .git/hooks/pre-commit-check.py
import subprocess
import json
from radon.complexity import cc_visit
from radon.visitors import Function
def check_complexity():
# 获取暂存的文件
result = subprocess.run(
['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
capture_output=True, text=True
)
files = [f for f in result.stdout.split('\n') if f.endswith('.py')]
threshold = 10 # 圈复杂度阈值
failed = False
for file_path in files:
try:
with open(file_path, 'r', encoding='utf-8') as f:
code = f.read()
# 分析复杂度
functions = cc_visit(code)
for func in functions:
if func.complexity > threshold:
print(f"⚠️ 文件 {file_path}")
print(f" 函数 {func.name} 复杂度: {func.complexity} (阈值: {threshold})")
failed = True
except Exception as e:
print(f"❌ 分析 {file_path} 时出错: {e}")
return failed
if __name__ == "__main__":
if check_complexity():
print("\n❌ 复杂度检查失败!请简化高复杂度函数")
exit(1)
else:
print("✅ 复杂度检查通过")
exit(0)
pre-commit 脚本调用
#!/bin/bash # .git/hooks/pre-commit python3 .git/hooks/pre-commit-check.py
使用 JavaScript 自定义检查脚本
创建复杂度检查工具
// complexity-check.js
const fs = require('fs');
const path = require('path');
function calculateCyclomaticComplexity(code) {
// 匹配会增加复杂度的关键字
const patterns = [
/if/g, /else if/g, /for/g, /while/g, /case/g,
/catch/g, /\&\&/g, /\|\|/g, /\?/g
];
let complexity = 1; // 基础复杂度
patterns.forEach(pattern => {
const matches = code.match(pattern);
if (matches) {
complexity += matches.length;
}
});
return complexity;
}
// 解析函数
function extractFunctions(code) {
const functionRegex = /(?:function\s+(\w+)|(\w+)\s*=\s*function|(\w+)\s*=>\s*{)/g;
const functions = [];
let match;
while ((match = functionRegex.exec(code)) !== null) {
const name = match[1] || match[2] || match[3];
const startIndex = match.index;
// 简单找到函数体结束
let endIndex = code.indexOf('}', startIndex);
if (endIndex > -1) {
const functionCode = code.substring(startIndex, endIndex + 1);
functions.push({
name,
code: functionCode,
start: startIndex,
end: endIndex
});
}
}
return functions;
}
function processFile(filePath, threshold = 10) {
const code = fs.readFileSync(filePath, 'utf8');
const functions = extractFunctions(code);
const issues = [];
functions.forEach(func => {
const complexity = calculateCyclomaticComplexity(func.code);
if (complexity > threshold) {
issues.push({
file: filePath,
function: func.name || 'anonymous',
complexity,
threshold
});
}
});
return issues;
}
// 主函数
const threshold = 10;
const stagedFiles = process.argv[2].split('\n');
let hasErrors = false;
stagedFiles.forEach(file => {
if (file.endsWith('.js') || file.endsWith('.ts')) {
const issues = processFile(file, threshold);
issues.forEach(issue => {
console.log(`⚠️ 函数复杂度超标: ${issue.file} - ${issue.function}`);
console.log(` 复杂度: ${issue.complexity} (阈值: ${issue.threshold})`);
hasErrors = true;
});
}
});
if (hasErrors) {
process.exit(1);
}
pre-commit 脚本
#!/bin/bash # .git/hooks/pre-commit echo "检查函数复杂度..." STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts)$') if [ -z "$STAGED_FILES" ]; then exit 0 fi node complexity-check.js "$STAGED_FILES" if [ $? -ne 0 ]; then echo "❌ 复杂度检查失败" exit 1 fi echo "✅ 复杂度检查通过"
使用 Husky + lint-staged(推荐)
安装和配置
npm install --save-dev husky lint-staged eslint
package.json 配置
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": [
"eslint --rule 'complexity: [\"error\", 10]'",
"git add"
]
}
}
高级检查 - 多种复杂度指标
综合检查脚本
#!/usr/bin/env python3
"""综合复杂度检查"""
import ast
import subprocess
from collections import defaultdict
class ComplexityChecker(ast.NodeVisitor):
def __init__(self):
self.complexities = defaultdict(int)
self.current_function = None
def visit_FunctionDef(self, node):
self.current_function = node.name
self.complexities[node.name] = 1 # 基础复杂度
self.generic_visit(node)
def visit_If(self, node):
if self.current_function:
self.complexities[self.current_function] += 1
self.generic_visit(node)
def visit_While(self, node):
if self.current_function:
self.complexities[self.current_function] += 1
self.generic_visit(node)
def visit_For(self, node):
if self.current_function:
self.complexities[self.current_function] += 1
self.generic_visit(node)
def visit_BoolOp(self, node):
if self.current_function:
# 每个布尔运算符增加复杂度
self.complexities[self.current_function] += len(node.values) - 1
self.generic_visit(node)
def check_files():
result = subprocess.run(
['git', 'diff', '--cached', '--name-only', '--diff-filter=ACM'],
capture_output=True, text=True
)
files = [f for f in result.stdout.split('\n') if f.endswith('.py')]
threshold = 10
failed = False
for file_path in files:
try:
with open(file_path, 'r') as f:
tree = ast.parse(f.read())
checker = ComplexityChecker()
checker.visit(tree)
for func_name, complexity in checker.complexities.items():
if complexity > threshold:
print(f"⚠️ {file_path}:{func_name} 复杂度 {complexity} (阈值 {threshold})")
failed = True
except SyntaxError:
print(f"❌ {file_path}: 语法错误")
failed = True
return failed
if __name__ == "__main__":
if check_files():
print("\n❌ 复杂度检查失败")
exit(1)
else:
print("✅ 复杂度检查通过")
exit(0)
最佳实践
-
阈值设置:
- 圈复杂度:建议 10-15
- 认知复杂度:建议 15-20
-
性能优化:
- 只检查暂存的文件
- 缓存已检查的结果
-
错误处理:
- 提供清晰的错误信息
- 标记具体的问题函数
-
集成到 CI/CD:
# .github/workflows/complexity.yml name: Check Complexity on: [push, pull_request] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Check complexity run: | pip install radon radon cc . -s -n 10 -e "tests/*"
这些方法可以根据项目的技术栈和需求选择和调整,建议从简单的 ESLint 规则开始,逐步增加更复杂的检查。