Appearance
TDI 算法
TDI(Technical Debt Index)是 Git Doctor 创新的技术债务量化算法。
算法概述
TDI = Σ(Churn × BugFactor × Complexity × TimeDecay)TDI 综合考虑四个维度,将抽象的"技术债务"转化为可量化的指数。
各维度详解
1. Churn(修改频率)- 权重 40%
文件的修改次数反映其稳定性。
typescript
function calculateChurn(fileChurnCount: number, maxChurnCount: number): number {
// 归一化到 0-1
return fileChurnCount / maxChurnCount
}理论依据:
- 频繁修改的文件往往存在设计问题
- 修改次数越多,引入 Bug 的概率越高
- "热点"文件通常是重构的重点目标
2. BugFactor(Bug 关联度)- 权重 35%
与 Bug 修复相关的提交占比。
typescript
function calculateBugFactor(bugFixCount: number, totalChanges: number): number {
if (totalChanges === 0) return 0
return bugFixCount / totalChanges
}
function isFixCommit(message: string): boolean {
const lowerMessage = message.toLowerCase()
// Conventional Commits 格式
if (/^(fix|hotfix|bugfix)(\(.+\))?:/.test(lowerMessage)) {
return true
}
// 关键词匹配
const fixKeywords = [
'fix', 'bug', 'issue', '修复', '修正', '解决',
'patch', 'hotfix', 'bugfix'
]
return fixKeywords.some(kw => lowerMessage.includes(kw))
}理论依据:
- Bug 修复率高说明代码质量有问题
- 反复修复同一区域说明存在深层设计缺陷
- 高 BugFactor 文件应优先重构
3. Complexity(复杂度)- 权重 15%
文件大小作为复杂度的近似指标。
typescript
function calculateComplexity(fileSize: number): number {
const baseSize = 10 * 1024 // 10KB 作为基准
const ratio = fileSize / baseSize
// 限制在 0-1 范围,超大文件封顶
return Math.min(ratio, 3) / 3
}理论依据:
- 大文件通常包含更多逻辑,更难维护
- 文件过大可能违反单一职责原则
- 未来可扩展为圈复杂度等更精确指标
4. TimeDecay(时间衰减)- 权重 10%
近期修改的权重更高。
typescript
function calculateTimeDecay(lastModified: Date): number {
const now = new Date()
const msPerDay = 1000 * 60 * 60 * 24
const daysSinceModified = (now.getTime() - lastModified.getTime()) / msPerDay
// 180天内线性衰减:1.0 → 0.5
if (daysSinceModified <= 180) {
return 1.0 - (daysSinceModified / 180) * 0.5
}
return 0.5 // 180天后保持最低权重
}理论依据:
- 近期活跃的问题更紧迫
- 长期未修改的文件可能已稳定
- 但也不能完全忽视历史问题
完整计算流程
typescript
interface FileStats {
path: string
churnCount: number
bugFixCount: number
fileSize: number
lastModified: Date
}
function calculateTDI(
file: FileStats,
maxChurnCount: number
): number {
const weights = {
churn: 0.40,
bugFactor: 0.35,
complexity: 0.15,
timeDecay: 0.10
}
const churn = calculateChurn(file.churnCount, maxChurnCount)
const bugFactor = calculateBugFactor(file.bugFixCount, file.churnCount)
const complexity = calculateComplexity(file.fileSize)
const timeDecay = calculateTimeDecay(file.lastModified)
const tdi = (
churn * weights.churn +
bugFactor * weights.bugFactor +
complexity * weights.complexity +
timeDecay * weights.timeDecay
) * 100 // 转换为 0-100 分数
return Math.round(tdi * 10) / 10 // 保留一位小数
}风险级别划分
| TDI 范围 | 风险级别 | 颜色 | 行动建议 |
|---|---|---|---|
| 0-30 | 低 | 🟢 | 正常维护 |
| 31-60 | 中 | 🟡 | 持续关注 |
| 61-80 | 高 | 🟠 | 计划重构 |
| 81-100 | 严重 | 🔴 | 优先处理 |
计算示例
示例文件
文件: src/core/parser.ts
修改次数: 45 次(最大 50 次)
Bug 修复: 12 次
文件大小: 25KB
最后修改: 15 天前计算过程
Churn = 45/50 = 0.90
BugFactor = 12/45 = 0.27
Complexity = min(25/10, 3)/3 = 0.83
TimeDecay = 1.0 - (15/180)*0.5 = 0.96
TDI = (0.90×0.40 + 0.27×0.35 + 0.83×0.15 + 0.96×0.10) × 100
= (0.36 + 0.09 + 0.12 + 0.10) × 100
= 67.0
风险级别: 🟠 高风险聚合分析
模块级 TDI
typescript
function calculateModuleTDI(files: FileStats[]): number {
const tdis = files.map(f => calculateTDI(f, getMaxChurn(files)))
return tdis.reduce((sum, t) => sum + t, 0) / tdis.length
}仓库级 TDI
typescript
function calculateRepoTDI(allFiles: FileStats[]): {
average: number
critical: FileStats[]
trend: 'improving' | 'stable' | 'degrading'
} {
const tdis = allFiles.map(f => ({
file: f,
tdi: calculateTDI(f, getMaxChurn(allFiles))
}))
return {
average: tdis.reduce((sum, t) => sum + t.tdi, 0) / tdis.length,
critical: tdis.filter(t => t.tdi > 80).map(t => t.file),
trend: calculateTrend(tdis)
}
}与其他指标对比
| 指标 | 优点 | 缺点 |
|---|---|---|
| 代码行数 | 简单 | 不反映质量 |
| 圈复杂度 | 精确 | 计算成本高 |
| 代码覆盖率 | 直观 | 不反映修改频率 |
| TDI | 综合、基于历史 | 需要提交历史 |
局限性
- 依赖提交历史质量 - 如果提交消息不规范,BugFactor 可能不准确
- 文件大小近似复杂度 - 不如圈复杂度精确
- 无法检测未修改的坏代码 - 只关注活跃区域
扩展方向
未来可考虑引入:
- 圈复杂度(需要代码解析)
- 代码重复率
- 测试覆盖率
- 依赖复杂度