nkds

导航

 

MonkeyCode 代码质量管理:从静态分析到 AI 驱动的质量保障体系

引言

"质量不是测试出来的,是构建出来的——而 AI 可以帮你更好地构建。"

在软件工程中,代码质量管理是一个永恒的话题。传统的静态分析工具(如 ESLint、SonarQube)擅长发现模式化的问题,但面对业务逻辑错误、架构设计缺陷、安全漏洞等更深层次的质量问题往往力不从心。MonkeyCode 作为开源 AI 编程助手,正在重新定义"代码质量"的边界——将人工智能的能力融入软件开发生命周期的每一个环节。

本文将全面介绍如何利用 MonkeyCode 构建一套完整的、AI 驱动的代码质量保障体系。

🎯 核心信息


一、代码质量的维度演进

1.1 质量定义的四代演变

┌─────────────────────────────────────────────────────────────┐
│           代码质量管理的四代演进                               │
├──────────────┬──────────────────┬───────────────────────────┤
│    代际       │   核心理念         │   代表工具/方法            │
├──────────────┼──────────────────┼───────────────────────────┤
│              │                  │                           │
│  第一代       │ "能跑就行"        │ 手工 Code Review          │
│ (手工时代)    │                  │ 打印代码 + 红笔批注         │
│              │                  │                           │
│  第二代       │ "符合规范"        │ Lint 工具                 │
│ (规则时代)    │                  │ ESLint, Checkstyle, PMD   │
│              │                  │ SonarQube, Coverity        │
│              │                  │                           │
│  第三代       │ "可度量"          │ 度量驱动                   │
│ (度量时代)    │                  │ 圈复杂度 + 覆盖率 + 技术债务 │
│              │                  │ DORA 指标 + DevEx          │
│              │                  │                           │
│  第四代 ⭐     │ "智能理解"        │ AI 驱动                   │
│ (AI 时代)     │                  │ MonkeyCode                │
│              │                  │ 语义理解 + 上下文感知       │
│              │                  │ 预测性质量保障              │
│              │                  │                           │
└──────────────┴──────────────────┴───────────────────────────┘

1.2 传统工具 vs MonkeyCode 的能力对比

质量维度 Lint/静态分析 SonarQube MonkeyCode AI
语法/格式错误 ✅ 强项 ✅ 支持 ✅ 同样支持
编码规范违规 ✅ 强项 ✅ 强项 ✅ 支持并可自动修复
潜在 Bug(空指针等) ⚠️ 基于规则 ✅ 数据流分析 ✅ 语义理解
性能反模式 ❌ 不支持 ⚠️ 有限 ✅ 上下文感知
安全漏洞 ❌ 不支持 ✅ 规则引擎 ✅ 语义+上下文
架构设计问题 ❌ 不支持 ⚠️ 有限 ✅ 全局理解
业务逻辑错误 ❌ 不可能 ❌ 不可能 ✅ 核心能力
API 设计质量 ❌ 不支持 ⚠️ 有限 ✅ 最佳实践匹配
可维护性评估 ⚠️ 指标化 ✅ 技术债务 ✅ 多维评估
修复建议生成 ❌ 仅报错 ⚠️ 通用建议 ✅ 上下文精准建议

二、MonkeyCode 质量检查核心能力

2.1 八大质量检查引擎

# ===== monkeycode/quality/checker.py =====
"""
MonkeyCode 代码质量检查器
八大引擎协同工作,全方位保障代码质量
"""

import ast
import re
from dataclasses import dataclass, field
from typing import Optional
from enum import Enum
from pathlib import Path


class Severity(Enum):
    CRITICAL = "critical"    # 必须立即修复
    HIGH = "high"           # 应尽快修复
    MEDIUM = "medium"       # 建议修复
    LOW = "low"             # 可选修复
    INFO = "info"           # 信息提示


class QualityCategory(Enum):
    CORRECTNESS = "correctness"       # 正确性
    SECURITY = "security"             # 安全性
    PERFORMANCE = "performance"       # 性能
    MAINTAINABILITY = "maintainability"  # 可维护性
    RELIABILITY = "reliability"       # 可靠性
    TESTABILITY = "testability"       # 可测试性
    DOCUMENTATION = "documentation"   # 文档完整性
    ARCHITECTURE = "architecture"     # 架构合规


@dataclass
class QualityIssue:
    """质量问题记录"""
    file_path: str
    line_number: int
    column_number: int
    
    category: QualityCategory
    severity: Severity
    
    title: str                    # 问题标题
    description: str              # 详细描述
    code_snippet: str             # 问题代码片段
    
    # AI 增强信息
    suggested_fix: str            # 建议的修复代码
    explanation: str              # 为什么这是个问题
    confidence: float             # AI 置信度 0-1
    
    # 元数据
    rule_id: str                  # 规则 ID
    effort_to_fix: str            # 修复工作量估计
    references: list[str] = field(default_factory=list)


@dataclass
class QualityReport:
    """质量报告"""
    file_path: str
    total_issues: int
    issues_by_severity: dict[str, int]
    issues_by_category: dict[str, int]
    
    # 分数
    quality_score: float          # 总体质量分 0-100
    scores_by_category: dict[str, float]
    
    # 详情
    issues: list[QualityIssue]
    
    # 趋势(如果有历史数据)
    trend: str = "stable"        # improving / stable / degrading


class MonkeyCodeQualityChecker:
    """MonkeyCode 代码质量检查器"""
    
    def __init__(self):
        self.engines = {
            'correctness': CorrectnessEngine(),
            'security': SecurityEngine(),
            'performance': PerformanceEngine(),
            'maintainability': MaintainabilityEngine(),
            'reliability': ReliabilityEngine(),
            'testability': TestabilityEngine(),
            'documentation': DocumentationEngine(),
            'architecture': ArchitectureEngine(),
        }
    
    def check_file(self, file_path: str) -> QualityReport:
        """检查单个文件"""
        content = Path(file_path).read_text(encoding='utf-8', errors='ignore')
        
        all_issues = []
        
        for engine_name, engine in self.engines.items():
            try:
                issues = engine.check(content, file_path)
                all_issues.extend(issues)
            except Exception as e:
                print(f"[warn] 引擎 {engine_name} 执行失败: {e}")
        
        return self._generate_report(file_path, all_issues)
    
    def check_project(self, project_path: str) -> list[QualityReport]:
        """检查整个项目"""
        reports = []
        
        source_files = self._scan_source_files(project_path)
        
        for file_path in source_files:
            try:
                report = self.check_file(str(file_path))
                reports.append(report)
                
                # 实时输出进度
                if report.total_issues > 0:
                    print(f"[check] {file_path}: "
                          f"{report.total_issues} issues "
                          f"(score: {report.quality_score:.0f})")
            except Exception as e:
                print(f"[error] 检查失败 {file_path}: {e")
        
        # 输出项目级摘要
        self._print_project_summary(reports)
        
        return reports
    
    def _generate_report(
        self, 
        file_path: str, 
        issues: list[QualityIssue]
    ) -> QualityReport:
        """生成单文件质量报告"""
        # 按严重程度统计
        by_severity = {}
        for s in Severity:
            by_severity[s.value] = sum(1 for i in issues if i.severity == s)
        
        # 按类别统计
        by_category = {}
        for c in QualityCategory:
            by_category[c.value] = sum(1 for i in issues if i.category == c)
        
        # 计算总体质量分数
        score = self._calculate_quality_score(issues)
        
        # 计算各类别分数
        category_scores = {}
        for cat in QualityCategory:
            cat_issues = [i for i in issues if i.category == cat]
            cat_scores[cat.value] = self._calculate_category_score(cat_issues)
        
        return QualityReport(
            file_path=file_path,
            total_issues=len(issues),
            issues_by_severity=by_severity,
            issues_by_category=by_category,
            quality_score=score,
            scores_by_category=category_scores,
            issues=sorted(issues, key=lambda x: (
                ['critical', 'high', 'medium', 'low', 'info'].index(x.severity.value),
                x.line_number
            )),
        )
    
    def _calculate_quality_score(self, issues: list[QualityIssue]) -> float:
        """
        计算质量分数 (0-100)
        
        加权扣分模型:
        - critical: -15 分
        - high: -8 分
        - medium: -3 分
        - low: -1 分
        - info: 不扣分
        """
        score = 100.0
        
        penalties = {
            Severity.CRITICAL: 15,
            Severity.HIGH: 8,
            Severity.MEDIUM: 3,
            Severity.LOW: 1,
            Severity.INFO: 0,
        }
        
        for issue in issues:
            penalty = penalties.get(issue.severity, 0)
            # 根据 AI 置信度调整扣分
            adjusted_penalty = penalty * issue.confidence
            score -= adjusted_penalty
        
        return max(0.0, min(100.0, round(score, 1)))
    
    def _calculate_category_score(self, issues: list[QualityIssue]) -> float:
        """计算单个类别的分数"""
        if not issues:
            return 100.0
        
        base_score = 100.0
        for issue in issues:
            if issue.severity == Severity.CRITICAL:
                base_score -= 20 * issue.confidence
            elif issue.severity == Severity.HIGH:
                base_score -= 10 * issue.confidence
            elif issue.severity == Severity.MEDIUM:
                base_score -= 4 * issue.confidence
        
        return max(0.0, round(base_score, 1))
    
    def _print_project_summary(self, reports: list[QualityReport]):
        """打印项目级摘要"""
        print("\n" + "=" * 70)
        print("  MonkeyCode 代码质量检查报告 — 项目总览")
        print("=" * 70)
        
        total_files = len(reports)
        files_with_issues = sum(1 for r in reports if r.total_issues > 0)
        total_issues = sum(r.total_issues for r in reports)
        avg_score = sum(r.quality_score for r in reports) / max(total_files, 1)
        
        # 严重程度汇总
        severity_totals = {}
        for r in reports:
            for sev, count in r.issues_by_severity.items():
                severity_totals[sev] = severity_totals.get(sev, 0) + count
        
        print(f"\n📊 项目概览:")
        print(f"   扫描文件数: {total_files}")
        print(f"   有问题文件: {files_with_issues} ({files_with_issues*100//max(total_files,1)}%)")
        print(f"   问题总数: {total_issues}")
        print(f"   平均质量分: {avg_score:.1f}/100")
        
        print(f"\n🚨 严重程度分布:")
        for sev in ['critical', 'high', 'medium', 'low', 'info']:
            count = severity_totals.get(sev, 0)
            emoji = {'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🔵', 'info': '⚪'}[sev]
            print(f"   {emoji} {sev}: {count}")
        
        # Top 10 最严重问题
        all_issues = []
        for r in reports:
            all_issues.extend(r.issues)
        all_issues.sort(key=lambda x: (
            ['critical', 'high', 'medium', 'low', 'info'].index(x.severity.value),
            -x.confidence
        ))
        
        if all_issues:
            print(f"\n🔥 Top 10 关键问题:")
            for i, issue in enumerate(all_issues[:10], 1):
                print(f"   {i}. [{issue.severity.value.upper()}] "
                      f"{issue.title}"
                      f"\n      📄 {issue.file_path}:{issue.line_number}")
        
        print("\n" + "=" * 70)
    
    def _scan_source_files(self, project_path: str) -> list[Path]:
        """扫描项目源文件"""
        extensions = {'.py', '.ts', '.js', '.tsx', '.jsx', '.java', '.go', '.rs'}
        project = Path(project_path)
        files = []
        for ext in extensions:
            files.extend(project.rglob(f'*{ext}'))
        
        exclude = {'node_modules', '__pycache__', '.git', 'dist', 'build',
                  'vendor', '.venv', 'bin', 'obj', '.next'}
        return sorted([
            f for f in files 
            if not any(part in exclude for part in f.relative_to(project).parts)
        ])


# ===== 各引擎实现 =====

class CorrectnessEngine:
    """正确性检查引擎:检测逻辑错误和潜在 Bug"""
    
    def check(self, content: str, file_path: str) -> list[QualityIssue]:
        issues = []
        
        # 检测1: 空指针/None 检查缺失
        issues.extend(self._check_null_dereference(content, file_path))
        
        # 检测2: 资源泄漏(未关闭的文件/连接)
        issues.extend(self._check_resource_leak(content, file_path))
        
        # 检测3: 异常处理不当
        issues.extend(self._check_exception_handling(content, file_path))
        
        # 检测4: 并发安全问题
        issues.extend(self._check_concurrency(content, file_path))
        
        # 检测5: 边界条件处理
        issues.extend(self._check_boundary_conditions(content, file_path))
        
        return issues
    
    def _check_null_dereference(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测可能的空指针解引用"""
        issues = []
        lines = content.split('\n')
        
        # Python: 使用变量前未检查 None
        none_access_pattern = re.compile(
            r'^(\s*)(\w+)\.(?:\w+)'  # obj.attr 访问
            r'.*?(?!\s*(if|or|and)\s+\2\s+(?:is\s+)?(?:not\s+)?None)',
            re.MULTILINE
        )
        
        for match in none_access_pattern.finditer(content):
            line_num = content[:match.start()].count('\n') + 1
            var_name = match.group(2)
            
            # 简化的启发式:如果变量来自可能为 None 的来源
            suspicious_sources = ['get(', 'find(', 'fetch(', 'query(',
                                'request.', 'session.', 'config.']
            if any(src in content[max(0, match.start()-200):match.start()] 
                   for src in suspicious_sources):
                issues.append(QualityIssue(
                    file_path=file_path,
                    line_number=line_num,
                    column_number=match.start(2) - content.rfind('\n', 0, match.start()),
                    category=QualityCategory.CORRECTNESS,
                    severity=Severity.HIGH,
                    title=f"可能的空指针解引用: '{var_name}'",
                    description=(
                        f"变量 '{var_name}' 在使用前可能未被 None 检查。"
                        f"如果该变量来自可能返回 None 的函数调用或外部输入,"
                        f"直接访问其属性可能导致 AttributeError/NullPointerException。"
                    ),
                    code_snippet=lines[line_num - 1].strip() if line_num <= len(lines) else '',
                    suggested_fix=(
                        f"# 建议在使用前添加 None 检查:\n"
                        f"if {var_name} is not None:\n"
                        f"    {lines[line_num - 1].strip()}\n"
                        f"else:\n"
                        f"    # 处理 {var_name} 为 None 的情况\n"
                        f"    ..."
                    ),
                    explanation="空指针解引用是最常见的运行时错误之一。在 Python 中会导致 AttributeError,在 Java 中会导致 NullPointerException。",
                    confidence=0.75,
                    rule_id="CORRECTNESS-001",
                    effort_to_fix="< 5 min",
                    references=["PEP 8: E721", "Google Python Style Guide"],
                ))
        
        return issues
    
    def _check_resource_leak(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测资源泄漏"""
        issues = []
        lines = content.split('\n')
        
        # 检测: open() 但没有使用 with 语句
        open_without_with = re.compile(
            r'(?<!with\s)(?<!\()\s*\w+\s*=\s*open\s*\(',
            re.MULTILINE
        )
        
        for match in open_without_with.finditer(content):
            line_num = content[:match.start()].count('\n') + 1
            
            # 检查附近是否有 close() 调用
            nearby_code = content[match.start():match.start()+500]
            has_close = bool(re.search(r'\.close\(\)', nearby_code))
            has_with = 'with' in content[max(0, match.start()-100):match.start()]
            
            if not has_close and not has_with:
                issues.append(QualityIssue(
                    file_path=file_path,
                    line_number=line_num,
                    column_number=0,
                    category=QualityCategory.RELIABILITY,
                    severity=Severity.MEDIUM,
                    title="资源泄漏风险: 文件未使用 with 语句管理",
                    description="使用 open() 打开文件但未使用 with 语句,可能导致文件句柄泄漏。",
                    code_snippet=lines[line_num - 1].strip() if line_num <= len(lines) else '',
                    suggested_fix=(
                        "# 推荐使用 with 语句自动管理资源:\n"
                        "with open('filename.txt', 'r') as f:\n"
                        "    content = f.read()\n"
                        "# 文件会在 with 块结束后自动关闭"
                    ),
                    explanation="Python 的 with 语句会确保文件在操作完成后被正确关闭,即使在发生异常的情况下也是如此。",
                    confidence=0.85,
                    rule_id="RELIABILITY-001",
                    effort_to_fix="< 2 min",
                ))
        
        return issues
    
    def _check_exception_handling(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测异常处理问题"""
        issues = []
        
        # 裸 except 子句
        bare_except = re.compile(r'except\s*:', re.MULTILINE)
        for match in bare_except.finditer(content):
            line_num = content[:match.start()].count('\n') + 1
            issues.append(QualityIssue(
                file_path=file_path,
                line_number=line_num,
                column_number=0,
                category=QualityCategory.CORRECTNESS,
                severity=Severity.HIGH,
                title="裸 except 子句:捕获所有异常包括 SystemExit/KeyboardInterrupt",
                description="裸 except 会捕获所有异常,包括应该传播的系统退出信号和键盘中断。",
                suggested_fix="except Exception as e:  # 只捕获常规异常",
                confidence=0.95,
                rule_id="CORRECTNESS-002",
                effort_to_fix="< 1 min",
            ))
        
        return issues
    
    def _check_concurrency(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测并发安全问题"""
        issues = []
        
        # 共享可变状态无锁保护
        global_mutation = re.compile(
            r'global\s+(\w+)\s*\n.*\1\s*=',
            re.MULTILINE | re.DOTALL
        )
        
        for match in global_mutation.finditer(content):
            line_num = content[:match.start()].count('\n') + 1
            var_name = match.group(1)
            issues.append(QualityIssue(
                file_path=file_path,
                line_number=line_num,
                column_number=0,
                category=QualityCategory.RELIABILITY,
                severity=Severity.HIGH,
                title=f"并发安全隐患: 全局变量 '{var_name}' 可能存在竞态条件",
                description="全局可变状态在多线程环境下不安全,可能导致数据竞争。",
                suggested_fix=(
                    f"# 使用线程安全的数据结构或加锁:\n"
                    f"import threading\n"
                    f"_lock = threading.Lock()\n\n"
                    f"def update_{var_name}(new_value):\n"
                    f"    global {var_name}\n"
                    f"    with _lock:\n"
                    f"        {var_name} = new_value"
                ),
                confidence=0.72,
                rule_id="RELIABILITY-002",
                effort_to_fix="10-30 min",
            ))
        
        return issues
    
    def _check_boundary_conditions(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测边界条件处理缺失"""
        issues = []
        
        # 数组/列表访问缺少边界检查
        unchecked_index = re.compile(
            r'(\w+)\[(?!-?\d+\])(\w+)\]',
            re.MULTILINE
        )
        
        for match in unchecked_index.finditer(content):
            var_name = match.group(1)
            index_var = match.group(2)
            
            # 如果索引是用户输入或外部数据
            if index_var in ('user_input', 'request', 'index', 'i'):
                line_num = content[:match.start()].count('\n') + 1
                issues.append(QualityIssue(
                    file_path=file_path,
                    line_number=line_num,
                    column_number=0,
                    category=QualityCategory.CORRECTNESS,
                    severity=Severity.MEDIUM,
                    title=f"数组访问越界风险: '{var_name}[{index_var}]' 缺少边界检查",
                    description="使用动态索引访问数组/列表时,应验证索引在有效范围内。",
                    suggested_fix=(
                        f"if 0 <= {index_var} < len({var_name}):\n"
                        f"    value = {var_name}[{index_var}]\n"
                        f"else:\n"
                        f"    raise IndexError('Index out of range')"
                    ),
                    confidence=0.68,
                    rule_id="CORRECTNESS-003",
                    effort_to_fix="< 5 min",
                ))
        
        return issues


class SecurityEngine:
    """安全检查引擎"""
    
    def check(self, content: str, file_path: str) -> list[QualityIssue]:
        issues = []
        
        # SQL 注入检测
        issues.extend(self._detect_sql_injection(content, file_path))
        
        # XSS 检测
        issues.extend(self._detect_xss(content, file_path))
        
        # 硬编码密钥检测
        issues.extend(self._detect_hardcoded_secrets(content, file_path))
        
        # 不安全的随机数
        issues.extend(self._detect_insecure_random(content, file_path))
        
        # 路径遍历
        issues.extend(self._detect_path_traversal(content, file_path))
        
        return issues
    
    def _detect_sql_injection(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测 SQL 注入漏洞"""
        issues = []
        
        # 危险模式:字符串拼接 SQL
        sql_injection_patterns = [
            (r'(execute|query|raw)\s*\(\s*f["\'].*%s', 'f-string SQL 拼接'),
            (r'(execute|query|raw)\s*\(\s*["\'].*\+.*SELECT', '字符串拼接 SQL'),
            (r'(execute|query|raw)\s*\(\s*["\'].*\.format\s*\(', 'format SQL 拼接'),
            (r'"SELECT.*FROM.*WHERE.*"\s*\+\s*', 'SQL 字符串拼接'),
        ]
        
        for pattern, desc in sql_injection_patterns:
            for match in re.finditer(pattern, content, re.IGNORECASE | re.MULTILINE):
                line_num = content[:match.start()].count('\n') + 1
                issues.append(QualityIssue(
                    file_path=file_path,
                    line_number=line_num,
                    column_number=0,
                    category=QualityCategory.SECURITY,
                    severity=Severity.CRITICAL,
                    title=f"SQL 注入漏洞: {desc}",
                    description="直接将用户输入拼接到 SQL 语句中,攻击者可以通过构造恶意输入执行任意 SQL 命令。",
                    suggested_fix=(
                        "# 使用参数化查询:\n"
                        "cursor.execute(\n"
                        "    'SELECT * FROM users WHERE id = %s',\n"
                        "    (user_id,)\n"
                        ")"
                    ),
                    explanation="SQL 注入是 OWASP Top 10 之一,位列第三。它允许攻击者读取、修改或删除数据库中的数据。",
                    confidence=0.92,
                    rule_id="SECURITY-001",
                    effort_to_fix="< 10 min",
                    references=["OWASP Top 10 - A03:2021 Injection", "CWE-89"],
                ))
        
        return issues
    
    def _detect_hardcoded_secrets(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测硬编码的密钥和凭据"""
        issues = []
        
        secret_patterns = [
            (r'api_key\s*=\s*["\'][A-Za-z0-9]{20,}["\']', 'API Key'),
            (r'secret_key\s*=\s*["\'][A-Za-z0-9]{20,}["\']', 'Secret Key'),
            (r'password\s*=\s*["\'][^"\']{4,}["\']', 'Password'),
            (r'AKIA[A-Z0-9]{16}', 'AWS Access Key'),
            (r'sk-[a-fA-F0-9]{32}', 'OpenAI API Key'),
            (r'ghp_[a-zA-Z0-9]{36}', 'GitHub Personal Token'),
            (r'-----BEGIN (RSA |EC )?PRIVATE KEY-----', 'Private Key'),
        ]
        
        for pattern, secret_type in secret_patterns:
            for match in re.finditer(pattern, content):
                line_num = content[:match.start()].count('\n') + 1
                masked = match.group()[:8] + '*' * 12 + match.group()[-4:]
                issues.append(QualityIssue(
                    file_path=file_path,
                    line_number=line_num,
                    column_number=0,
                    category=QualityCategory.SECURITY,
                    severity=Severity.CRITICAL,
                    title=f"硬编码敏感信息: {secret_type}",
                    description=f"发现硬编码的 {secret_type},这会导致严重的安全风险。代码提交到版本控制后,凭据将永久泄露。",
                    suggested_fix=(
                        f"# 使用环境变量存储敏感信息:\n"
                        f"import os\n"
                        f"{secret_type.lower().replace(' ', '_')} = os.getenv('{secret_type.upper()}')\n\n"
                        f"# 或使用密钥管理系统 (KMS/Vault)"
                    ),
                    explanation="硬编码凭据是导致数据泄露的最常见原因之一。一旦代码被推送到公开仓库,凭据必须被视为已泄露并立即轮换。",
                    confidence=0.98,
                    rule_id="SECURITY-002",
                    effort_to_fix="< 5 min",
                    references=["OWASP Top 10 - A07:2021 Identification and Authentication Failures", "CWE-798"],
                ))
        
        return issues


class PerformanceEngine:
    """性能检查引擎"""
    
    def check(self, content: str, file_path: str) -> list[QualityIssue]:
        issues = []
        
        # N+1 查询模式
        issues.extend(self._detect_n_plus_one(content, file_path))
        
        # 循环内重复计算
        issues.extend(self._detect_repeated_computation(content, file_path))
        
        # 低效数据结构选择
        issues.extend(self._detect_inefficient_data_structures(content, file_path))
        
        # 内存泄漏风险
        issues.extend(self._detect_memory_leak_risk(content, file_path))
        
        return issues
    
    def _detect_n_plus_one(self, content: str, file_path: str) -> list[QualityIssue]:
        """检测 N+1 查询模式"""
        issues = []
        
        # 循环内的数据库查询
        loop_query_pattern = re.compile(
            r'(for|while|foreach)\s*\(.*?\)\s*\{[^}]*'
            r'(db\.|session\.|connection\.|cursor\.|execute|query|select)',
            re.MULTILINE | re.DOTALL
        )
        
        for match in loop_query_pattern.finditer(content):
            line_num = content[:match.start()].count('\n') + 1
            issues.append(QualityIssue(
                file_path=file_path,
                line_number=line_num,
                column_number=0,
                category=QualityCategory.PERFORMANCE,
                severity=Severity.HIGH,
                title="N+1 查询性能问题: 循环内执行数据库查询",
                description="在循环中执行数据库查询会导致 N+1 查询问题,随着数据量增长性能急剧下降。",
                suggested_fix=(
                    "# 使用批量查询替代循环查询:\n"
                    "# Bad (N+1):\n"
                    "for item in items:\n"
                    "    detail = db.query(item.id)  # N queries!\n"
                    "\n"
                    "# Good (batch query):\n"
                    "item_ids = [item.id for item in items]\n"
                    "details = db.batch_query(item_ids)  # 1 query!"
                ),
                explanation="N+1 查询是 Web 应用中最常见的性能问题之一。一个有 100 条数据的页面可能触发 101 次数据库查询。",
                confidence=0.80,
                rule_id="PERFORMANCE-001",
                effort_to_fix="15-30 min",
            ))
        
        return issues


# ... 其他引擎类似实现(省略)...
class MaintainabilityEngine: pass
class ReliabilityEngine: pass
class TestabilityEngine: pass
class DocumentationEngine: pass
class ArchitectureEngine: pass

2.2 质量门禁(Quality Gate)

# ===== .monkeycode/quality-gate.yaml =====
# MonkeyCode 质量门禁配置

version: "1.0"

gates:
  # 门禁1: 提交前检查(本地)
  pre_commit:
    enabled: true
    fail_on: ["critical", "high"]
    warn_on: ["medium"]
    
    rules:
      # 硬性规则
      - id: "no-hardcoded-secrets"
        severity: "critical"
        description: "禁止硬编码密钥和凭据"
        
      - id: "no-sql-injection"
        severity: "critical"
        description: "禁止 SQL 注入风险代码"
        
      - id: "no-bare-except"
        severity: "high"
        description: "禁止裸 except 子句"
        
      - id: "max-complexity"
        severity: "high"
        params:
          max_cyclomatic: 10
          max_cognitive: 25
          
      - id: "min-test-coverage"
        severity: "medium"
        params:
          min_coverage: 60
          new_code_min_coverage: 80
  
  # 门禁2: PR 合并检查
  pull_request:
    enabled: true
    fail_on: ["critical"]
    block_merge_on: ["critical", "high"]
    
    rules:
      - id: "quality-score-threshold"
        severity: "block"
        params:
          min_score: 75
          new_code_min_score: 85
          
      - id: "no-quality-regression"
        severity: "block"
        description: "不允许质量分数下降超过 5 分"
        params:
          max_regression: 5
          
      - id: "security-scan"
        severity: "block"
        params:
          scan_dependencies: true
          scan_code: true
          
      - id: "test-coverage-check"
        severity: "warn"
        params:
          min_line_coverage: 70
          min_branch_coverage: 60
          
  # 门禁3: 发布前检查
  release:
    enabled: true
    fail_on: ["critical", "high", "medium"]
    
    rules:
      - id: "release-quality-gate"
        params:
          min_quality_score: 85
          zero_critical: true
          zero_high: true
          max_medium_per_file: 3
          
      - id: "documentation-complete"
        severity: "medium"
        params:
          require_api_docs: true
          require_readme_update: true
          require_changelog_entry: true
          
      - id: "performance-baseline"
        severity: "warn"
        params:
          max_regression_percent: 5
          benchmark_tests_required: true

# 自定义规则
custom_rules:
  - name: "business-rule-validation"
    pattern: "所有涉及金额计算的函数必须有精度控制"
    severity: "high"
    check: |
      检查所有包含 amount/price/money 参数的函数:
      - 是否使用了 Decimal 而非 float
      - 是否有 round() 调用
      - 是否有范围校验
      
  - name: "error-message-standardization"
    pattern: "所有对外暴露的错误消息必须使用统一格式"
    severity: "medium"
    check: |
      错误消息格式要求:
      - 包含错误码 (ERR_xxxx)
      - 包含用户友好的中文描述
      - 不包含内部技术细节

三、质量度量与可视化

3.1 质量仪表盘数据

// ===== monkeycode/quality/dashboard.ts =====
/**
 * MonkeyCode 质量度量仪表盘
 */

export interface QualityMetrics {
  // 整体指标
  overallScore: number;           // 0-100
  trend: 'improving' | 'stable' | 'degrading';
  trendPercent: number;           // 变化百分比
  
  // 分维度指标
  dimensions: {
    correctness: DimensionMetric;
    security: DimensionMetric;
    performance: DimensionMetric;
    maintainability: DimensionMetric;
    reliability: DimensionMetric;
    testability: DimensionMetric;
    documentation: DimensionMetric;
    architecture: DimensionMetric;
  };
  
  // 项目健康度
  health: {
    technicalDebt: TechnicalDebtMetric;
    codeCoverage: CoverageMetric;
    duplicationRate: number;       // 0-1
    avgComplexity: number;
    hotspots: HotspotFile[];
  };
  
  // 团队效率
  teamEfficiency: {
    meanTimeToFix: number;        // 平均修复时间(小时)
    reviewTurnaround: number;      // Review 周转时间(小时)
    escapeRate: number;            // 生产环境 Bug 逃逸率
  };
}

interface DimensionMetric {
  score: number;                  // 0-100
  issueCount: number;
  criticalCount: number;
  trend: string;
}

interface TechnicalDebtMetric {
  hours: number;                  // 技术债务(人时)
  ratio: number;                  // 占新开发时间的比例
  categories: Record<string, number>;
}

interface CoverageMetric {
  lineCoverage: number;
  branchCoverage: number;
  functionCoverage: number;
}

interface HotspotFile {
  path: string;
  score: number;                  // 变更频率 × 复杂度
  churn: number;                  # 近期变更次数
  complexity: number;
  riskLevel: 'critical' | 'high' | 'medium';
}


// 质量趋势分析算法
export function analyzeQualityTrend(
  historicalScores: Array<{ date: string; score: number }>
): { trend: string; percent: number } {
  if (historicalScores.length < 3) {
    return { trend: 'stable', percent: 0 };
  }
  
  const recent = historicalScores.slice(-5);
  const older = historicalScores.slice(-10, -5);
  
  const recentAvg = recent.reduce((s, x) => s + x.score, 0) / recent.length;
  const olderAvg = older.reduce((s, x) => s + x.score, 0) / older.length;
  
  const change = ((recentAvg - olderAvg) / olderAvg) * 100;
  
  let trend: string;
  if (change > 3) trend = 'improving';
  else if (change < -3) trend = 'degrading';
  else trend = 'stable';
  
  return { trend, percent: Math.abs(change) };
}

3.2 质量报告输出格式

## 📊 MonkeyCode 质量检查报告

**项目**: my-awesome-project  
**扫描时间**: 2026-06-30 11:45:00  
**扫描版本**: commit a1b2c3d  

---

### 🎯 总体评分: **78/100** 🟡

| 维度 | 得分 | 问题数 | 趋势 |
|------|------|--------|------|
| 正确性 | 82 | 12 | 📈 +3% |
| 安全性 | 65 | 5 | 📉 -5% ⚠️ |
| 性能 | 88 | 3 | ➡️ 稳定 |
| 可维护性 | 71 | 18 | 📈 +8% |
| 可靠性 | 79 | 6 | ➡️ 稳定 |
| 可测试性 | 62 | 8 | 📉 -2% |
| 文档完整 | 74 | 11 | 📈 +12% |
| 架构合规 | 85 | 2 | ➡️ 稳定 |

---

### 🔴 Critical Issues (2)

| # | 文件 | 行号 | 问题 | 建议 |
|---|------|------|------|------|
| 1 | `src/auth/login.py` | 45 | **SQL 注入漏洞**: 用户输入直接拼接到 SQL | 使用参数化查询 |
| 2 | `src/config.py` | 12 | **硬编码 API 密钥**: AWS AK 泄露风险 | 迁移至环境变量/KMS |

### 🟠 High Issues (8)

| # | 文件 | 行号 | 问题 | 建议 |
|---|------|------|------|------|
| 1 | `src/api/orders.py` | 134 | N+1 查询: 循环内数据库查询 | 批量查询优化 |
| 2 | `src/utils/cache.py` | 28 | 竞态条件: 全局缓存字典无锁保护 | 使用线程安全容器 |
| ... | ... | ... | ... | ... |

---

### 📈 趋势分析

质量分数趋势 (近30天):
100 ┤
90 ┤ ╭──╮
80 ┤ ╭──╮ ╭──╯ ╰──╮
70 ┤ ╭──╮ ╭──╮ ╭──╯ ╰──╯ ╰──╯ ╰── 📍 当前
60 ┤ ╭──╯ ╰──╯╭──╯
50 ┤╯
└──────────────────────────────────────
6/01 6/05 6/10 6/15 6/20 6/25 6/30


---

### 💡 AI 改进建议

#### 优先级 P0 (本周完成)

1. **修复 SQL 注入漏洞** (`src/auth/login.py:45`)
   - 预计影响: 消除严重安全风险
   - 预计工时: 30 分钟
   - MonkeyCode 已生成修复方案,一键应用

2. **移除硬编码密钥** (`src/config.py:12`)
   - 预计影响: 符合安全合规要求
   - 预计工时: 15 分钟
   - ⚠️ 请立即轮换已泄露的密钥!

#### 优先级 P1 (两周内完成)

3. **优化 N+1 查询** → 预期提升 API 响应速度 40%
4. **补充核心模块单元测试** → 目标覆盖率从 62% → 80%
5. **降低高复杂度函数圈复杂度** → 3 个函数需要拆分

---

### 🏆 团队质量排行榜

| 开发者 | 本周提交 | 平均质量分 | 修复及时率 |
|--------|---------|-----------|-----------|
| @alice | 15 | 92 | 95% |
| @bob | 12 | 85 | 88% |
| @charlie | 8 | 78 | 72% ⚠️ |

四、与 DevOps 流水线集成

4.1 质量门禁流水线

# ===== .github/workflows/quality-gate.yml =====
name: MonkeyCode Quality Gate

on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches: [main]

jobs:
  quality-check:
    name: AI Quality Analysis
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Run MonkeyCode Quality Check
        uses: monkeycode-ai/quality-action@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          config_file: .monkeycode/quality-gate.yaml
          output_format: |
            sarif
            markdown-summary
            json-detailed
          
      - name: Upload SARIF to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: monkeycode-quality.sarif
          category: monkeycode-quality
          
      - name: Comment PR with Quality Report
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('quality-report.md', 'utf8');
            
            github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: report
            });
      
      - name: Quality Gate Decision
        run: |
          echo "Checking quality gate status..."
          
          # 读取质量结果
          RESULT=$(cat quality-result.json)
          SCORE=$(echo $RESULT | jq '.overall_score')
          CRITICAL=$(echo $RESULT | jq '.critical_count')
          HIGH=$(echo $RESULT | jq '.high_count')
          
          echo "Quality Score: $SCORE"
          echo "Critical: $CRITICAL"
          echo "High: $HIGH"
          
          # 门禁判断
          if [ "$CRITICAL" -gt 0 ]; then
            echo "::error::❌ Quality Gate FAILED: $CRITICAL critical issues found!"
            exit 1
          fi
          
          if [ "$HIGH" -gt 5 ]; then
            echo "::warning::⚠️ Quality Gate WARNING: $HIGH high issues found"
          fi
          
          if [ "$SCORE" -lt 75 ]; then
            echo "::warning::⚠️ Quality Score ($SCORE) below threshold (75)"
          fi
          
          echo "✅ Quality Gate PASSED!"

五、最佳实践总结

5.1 质量保障金字塔

                    ┌──────────────┐
                    │   AI Review   │ ← MonkeyCode 核心
                    │  (语义理解)   │
                    ├──────────────┤
                    │  静态分析     │ ← Lint / Type Checker
                    ├──────────────┤
                    │  单元测试     │ ← 测试框架
                    ├──────────────┤
                    │  集成测试     │ ← API 测试
                    ├──────────────┤
                    │  E2E 测试     │ ← 端到端测试
                    └──────────────┘

5.2 DO & DON'T

## ✅ 推荐

1. **在 CI 中强制执行质量门禁**
   - Critical 问题阻断合并
   - High 问题需要审批
   
2. **定期审查质量趋势**
   - 每周查看质量仪表盘
   - 关注技术债务增长率
   
3. **让 AI 成为编码伙伴而非警察**
   - 利用 MonkeyCode 的修复建议快速改进
   - 将常见问题转化为团队培训材料

4. **建立质量文化**
   - 质量分数纳入团队 KPI(但不作为唯一指标)
   - 表彰持续改进的开发者

## ❌ 避免

1. **追求 100 分而忽视业务价值**
   - 80 分以上即可,重点解决 Critical/High
   
2. **忽略误报**
   - 定期调整规则减少噪音
   - 对误报进行标记帮助 AI 学习
   
3. **只在发布前检查**
   - 左移:在编写时实时反馈
   - 每次提交都运行检查

结语

"好的代码不是写出来的,是不断打磨出来的——而 AI 让打磨变得高效且有趣。"

MonkeyCode 的质量管理能力不是为了替代开发者判断,而是为了放大你的专业能力。当繁琐的模式匹配交给机器,你就可以专注于真正重要的事情:设计优雅的架构、写出清晰的业务逻辑、创造有价值的功能。

开源的力量在于共同进步。欢迎通过 GitHub Issue 和 Discussions 分享你的质量管理经验!


💡 快速开始:

MonkeyCode — 让每一行代码都经得起考验。 ✅🛡️

posted on 2026-06-30 11:46  MonkeyCode  阅读(8)  评论(0)    收藏  举报