技术债务评估 technical-debt-assessment

技术债务评估是一个系统性识别、量化和管理技术债务的过程,用于辅助代码质量投资的决策。关键词包括:技术债务、代码质量、架构决策、重构优先级、遗留代码评估。

架构设计 0 次安装 0 次浏览 更新于 3/4/2026

技术债务评估

概览

系统地识别、量化和管理技术债务,以便对代码质量投资做出明智的决策。

何时使用

  • 遗留代码评估
  • 重构优先级
  • 冲刺计划
  • 代码质量计划
  • 收购尽职调查
  • 架构决策

实施示例

1. 技术债务计算器

interface DebtItem {
  id: string;
  title: string;
  description: string;
  category: 'code' | 'architecture' | 'test' | 'documentation' | 'security';
  severity: 'low' | 'medium' | 'high' | 'critical';
  effort: number; // 小时
  impact: number; // 1-10 规模
  interest: number; // 如果不修复,每个冲刺的成本
}

class TechnicalDebtAssessment {
  private items: DebtItem[] = [];

  addDebtItem(item: DebtItem): void {
    this.items.push(item);
  }

  calculatePriority(item: DebtItem): number {
    const severityWeight = {
      low: 1,
      medium: 2,
      high: 3,
      critical: 4
    };

    const priority =
      (item.impact * 10 + item.interest * 5 + severityWeight[item.severity] * 3) /
      (item.effort + 1);

    return priority;
  }

  getPrioritizedList(): Array<DebtItem & { priority: number }> {
    return this.items
      .map(item => ({
        ...item,
        priority: this.calculatePriority(item)
      }))
      .sort((a, b) => b.priority - a.priority);
  }

  getDebtByCategory(): Record<string, DebtItem[]> {
    return this.items.reduce((acc, item) => {
      acc[item.category] = acc[item.category] || [];
      acc[item.category].push(item);
      return acc;
    }, {} as Record<string, DebtItem[]>);
  }

  getTotalEffort(): number {
    return this.items.reduce((sum, item) => sum + item.effort, 0);
  }

  getTotalInterest(): number {
    return this.items.reduce((sum, item) => sum + item.interest, 0);
  }

  generateReport(): string {
    const prioritized = this.getPrioritizedList();
    const byCategory = this.getDebtByCategory();

    let report = '# 技术债务评估

';

    // 摘要
    report += '## 摘要

';
    report += `- 总项数: ${this.items.length}
`;
    report += `- 总工作量: ${this.getTotalEffort()} 小时
`;
    report += `- 每月利息: ${this.getTotalInterest()} 小时

`;

    // 按类别
    report += '## 按类别

';
    for (const [category, items] of Object.entries(byCategory)) {
      const effort = items.reduce((sum, item) => sum + item.effort, 0);
      report += `- ${category}: ${items.length} 项 (${effort} 小时)
`;
    }
    report += '
';

    // 优先级最高的项
    report += '## 优先级最高的项

';
    for (const item of prioritized.slice(0, 10)) {
      report += `### ${item.title} (优先级: ${item.priority.toFixed(2)})
`;
      report += `- 类别: ${item.category}
`;
      report += `- 严重性: ${item.severity}
`;
      report += `- 工作量: ${item.effort} 小时
`;
      report += `- 影响: ${item.impact}/10
`;
      report += `- 利息: ${item.interest} 小时/冲刺
`;
      report += `
${item.description}

`;
    }

    return report;
  }
}

// 使用方法
const assessment = new TechnicalDebtAssessment();

assessment.addDebtItem({
  id: 'debt-1',
  title: '遗留 API 端点',
  description: '仍在使用的旧 API v1 端点,需要迁移',
  category: 'architecture',
  severity: 'high',
  effort: 40,
  impact: 8,
  interest: 5
});

assessment.addDebtItem({
  id: 'debt-2',
  title: '缺少单元测试',
  description: '30% 的代码库缺少测试覆盖',
  category: 'test',
  severity: 'medium',
  effort: 80,
  impact: 7,
  interest: 3
});

console.log(assessment.generateReport());

2. 代码质量扫描器

import * as ts from 'typescript';
import * as fs from 'fs';

interface QualityIssue {
  file: string;
  line: number;
  issue: string;
  severity: 'info' | 'warning' | 'error';
  debtHours: number;
}

class CodeQualityScanner {
  private issues: QualityIssue[] = [];

  scanProject(directory: string): QualityIssue[] {
    this.issues = [];

    const files = this.getTypeScriptFiles(directory);

    for (const file of files) {
      this.scanFile(file);
    }

    return this.issues;
  }

  private scanFile(filePath: string): void {
    const sourceCode = fs.readFileSync(filePath, 'utf-8');
    const sourceFile = ts.createSourceFile(
      filePath,
      sourceCode,
      ts.ScriptTarget.Latest,
      true
    );

    // 检查反模式
    this.checkForAnyTypes(sourceFile, filePath);
    this.checkForLongFunctions(sourceFile, filePath);
    this.checkForMagicNumbers(sourceFile, filePath);
    this.checkForConsoleStatements(sourceFile, filePath);
    this.checkForTodoComments(sourceFile, filePath);
  }

  private checkForAnyTypes(sourceFile: ts.SourceFile, filePath: string): void {
    const visit = (node: ts.Node) => {
      if (ts.isTypeReferenceNode(node) && node.typeName.getText() === 'any') {
        const { line } = ts.getLineAndCharacterOfPosition(
          sourceFile,
          node.getStart()
        );

        this.issues.push({
          file: filePath,
          line: line + 1,
          issue: '使用 any 类型降低了类型安全性',
          severity: 'warning',
          debtHours: 0.5
        });
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
  }

  private checkForLongFunctions(sourceFile: ts.SourceFile, filePath: string): void {
    const visit = (node: ts.Node) => {
      if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
        if (node.body) {
          const lines = node.body.getFullText().split('
').length;

          if (lines > 50) {
            const { line } = ts.getLineAndCharacterOfPosition(
              sourceFile,
              node.getStart()
            );

            this.issues.push({
              file: filePath,
              line: line + 1,
              issue: `函数有 ${lines} 行,应该重构`,
              severity: 'warning',
              debtHours: Math.ceil(lines / 10)
            });
          }
        }
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
  }

  private checkForMagicNumbers(sourceFile: ts.SourceFile, filePath: string): void {
    const visit = (node: ts.Node) => {
      if (ts.isNumericLiteral(node)) {
        const value = parseFloat(node.text);

        // 忽略常见常数
        if (![0, 1, -1, 2].includes(value)) {
          const { line } = ts.getLineAndCharacterOfPosition(
            sourceFile,
            node.getStart()
          );

          this.issues.push({
            file: filePath,
            line: line + 1,
            issue: `魔术数字 ${value} 应该是一个命名常量`,
            severity: 'info',
            debtHours: 0.1
          });
        }
      }

      ts.forEachChild(node, visit);
    };

    visit(sourceFile);
  }

  private checkForConsoleStatements(sourceFile: ts.SourceFile, filePath: string): void {
    const text = sourceFile.getFullText();
    const lines = text.split('
');

    lines.forEach((line, index) => {
      if (line.includes('console.log') || line.includes('console.error')) {
        this.issues.push({
          file: filePath,
          line: index + 1,
          issue: '控制台语句应该使用适当的日志记录器',
          severity: 'info',
          debtHours: 0.1
        });
      }
    });
  }

  private checkForTodoComments(sourceFile: ts.SourceFile, filePath: string): void {
    const text = sourceFile.getFullText();
    const lines = text.split('
');

    lines.forEach((line, index) => {
      if (/\/\/\s*TODO/.test(line)) {
        this.issues.push({
          file: filePath,
          line: index + 1,
          issue: 'TODO 注释表明工作不完整',
          severity: 'warning',
          debtHours: 2
        });
      }
    });
  }

  private getTypeScriptFiles(dir: string): string[] {
    // 实现
    return [];
  }

  getTotalDebt(): number {
    return this.issues.reduce((sum, issue) => sum + issue.debtHours, 0);
  }

  generateReport(): string {
    let report = '# 代码质量报告

';

    const bySeverity = this.issues.reduce((acc, issue) => {
      acc[issue.severity] = acc[issue.severity] || [];
      acc[issue.severity].push(issue);
      return acc;
    }, {} as Record<string, QualityIssue[]>);

    report += `## 摘要

`;
    report += `- 总问题数: ${this.issues.length}
`;
    report += `- 估计债务: ${this.getTotalDebt()} 小时

`;

    for (const [severity, issues] of Object.entries(bySeverity)) {
      report += `### ${severity.toUpperCase()} (${issues.length})

`;

      for (const issue of issues.slice(0, 10)) {
        report += `- ${issue.file}:${issue.line} - ${issue.issue}
`;
      }

      report += '
';
    }

    return report;
  }
}

最佳实践

✅ 要做

  • 量化债务影响
  • 按 ROI 优先级排序
  • 跟踪债务随时间变化
  • 将债务包含在冲刺中
  • 记录债务决策
  • 设置质量门

❌ 不要

  • 忽视技术债务
  • 一次性修复所有问题
  • 跳过影响分析
  • 做出情绪化的决策

债务类别

  • 代码质量: 复杂代码,重复
  • 架构: 糟糕的设计,紧密耦合
  • 测试: 缺少测试,不稳定的测试
  • 文档: 缺少文档,过时
  • 安全: 漏洞,过时依赖
  • 性能: 低效代码,N+1 查询

资源