技术债务的量化管理
技术债务不是抽象概念,可以测量、追踪、还清。
什么是技术债务
定义
“为了短期利益而选择不理想的解决方案,后期需要’还债’。“
类型
| 类型 | 例子 | 成本 |
|---|
| 无意的 | 赶工期写脏代码 | 高 |
| 有意的 | 先 MVP 后重构 | 中 |
| 比特腐烂 | 代码随时间退化 | 高 |
| 架构偏移 | 不符合原设计 | 中 |
测量方法
1. 代码质量指标
# 圈复杂度(Cyclomatic Complexity)
def calculate_complexity(function_ast):
"""McCabe 复杂度 = 判断节点数 - 1"""
return count_decision_nodes(function_ast) - 1
# 阈值
# < 10: 简单
# 10-20: 中等
# > 20: 复杂(需要拆分)
| 指标 | 工具 | 阈值 |
|---|
| 圈复杂度 | Lizard、radon | < 20 |
| 代码重复 | SonarQube、jscpd | < 5% |
| 文件长度 | cloc、自定义脚本 | < 500 行 |
| 函数长度 | 自定义 Linter | < 50 行 |
2. 静态分析得分
# SonarQube
sonar-scanner -Dsonar.projectKey=myproject
# 输出
- Bugs: 23
- Code Smells: 156
- Vulnerabilities: 7
- Technical Debt Ratio: 12.3% # 债务比率
3. 时间追踪
# 记录债务
class DebtTracker:
def __init__(self):
self.debts = []
def add(self, description, interest_hours, priority):
"""添加新债务"""
self.debts.append({
"description": description,
"interest_hours": interest_hours,
"priority": priority, # high/medium/low
"created_at": datetime.now(),
"status": "unpaid"
})
# 使用
tracker = DebtTracker()
tracker.add(
description="临时拼接 SQL",
interest_hours=2, # 每周多花 2 小时
priority="medium"
)
4. 团队共识评分
# 团队评估
def assess_debt_item(item):
"""1-10 分评分"""
scores = {
"business_impact": rate(1, 10), # 业务影响
"fix_effort": rate(1, 10), # 修复工作量
"urgency": rate(1, 10), # 紧迫性
"maintainability": rate(1, 10), # 可维护性
}
return sum(scores.values())
# 示例
debt_score = assess_debt_item({
"description": "没有测试的支付模块",
"business_impact": 8, # 影响大
"fix_effort": 3, # 容易修
"urgency": 6, # 较急
"maintainability": 9, # 难维护
})
债务分类框架
优先级矩阵
低修复成本 高修复成本
低业务影响 LOW MEDIUM
高业务影响 MEDIUM HIGH
立即处理:HIGH + 低修复成本
计划处理:MEDIUM
技术债务池:LOW + 高修复成本
量化评估表
| 债务项 | 影响分数 | 修复成本 | 优先级 | 还债时间 |
|---|
| 没有类型检查 | 8 | 2 | HIGH | 本周 |
| 硬编码配置 | 6 | 5 | MEDIUM | 下周 |
| 未使用的依赖 | 3 | 4 | LOW | 下月 |
| 过时的 API | 4 | 8 | LOW | 下季度 |
还债策略
1. 防止新债
# PR 检查
def pr_check(pr):
checks = []
# 复杂度
complexity = calculate_complexity(pr.diff)
if complexity > 20:
checks.append(f"复杂度 {complexity} > 20,请拆分")
# 覆盖率
if pr.coverage < 80:
checks.append(f"覆盖率 {pr.coverage}% < 80%")
# 技术债务注释
if has_tech_debt_comments(pr.diff):
checks.append("发现 TODO/FIXME,请创建 Issue")
return checks
2. 20% 规则
# 每个 Sprint 20% 时间用于还债
def sprint_planning(total_days, debt_items):
work_days = total_days * 0.8 # 80% 新功能
debt_days = total_days * 0.2 # 20% 还债
# 选择高优先级债务
selected = sorted(debt_items, key=lambda x: x.score)[:debt_days]
return work_days, selected
# 使用
work, debts = sprint_planning(10, all_debts)
print(f"新功能: {work} 天")
print(f"还债: {debts}")
3. 重构 vs 重写
| 场景 | 决策 |
|---|
| < 1000 行,逻辑清晰 | 重构 |
| > 5000 行,架构错误 | 重写 |
| 核心路径,频繁修改 | 重写 |
| 边缘功能,少用 | 重构 |
工具
SonarQube
# sonar-project.properties
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=tests
sonar.sourceEncoding=UTF-8
sonar.python.coveragePlugin=coverage
sonar.python.pylint.reportPath=report-pylint.txt
# 运行分析
sonar-scanner
Code Climate
# .codeclimate.yml
languages:
Python:
eslint:
enabled: true
JavaScript:
eslint:
enabled: true
ratings:
paths:
- "src/**"
exclude_paths:
- "tests/**"
- "dist/**"
GitHub Actions + Debt Tracker
# .github/workflows/tech-debt.yml
name: Tech Debt Report
on:
push:
branches: [main]
jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run SonarQube
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Update Debt Board
uses: actions/github-script@v7
with:
script: |
const debts = await sonarAPI.getDebts();
await githubAPI.createIssues(debts);
可视化
债务燃尽图
import matplotlib.pyplot as plt
def plot_debt_burndown(debts_history):
"""绘制还债燃尽图"""
dates = [d.date for d in debts_history]
hours = [d.hours_spent for d in debts_history]
plt.plot(dates, hours)
plt.title("Tech Debt Burndown")
plt.xlabel("Date")
plt.ylabel("Hours Spent")
plt.show()
债务雷达图
import numpy as np
import matplotlib.pyplot as plt
def plot_debt_radar(categories, values):
"""雷达图:各维度债务分布"""
angles = np.linspace(0, 2 * np.pi, len(categories), endpoint=False)
values += values[:1]
ax = plt.subplot(111, polar=True)
ax.plot(angles, values)
ax.fill(angles, values, alpha=0.25)
ax.set_xticks(angles, categories)
plt.title("Tech Debt by Category")
plt.show()
# 使用
categories = ['Complexity', 'Duplication', 'Security', 'Test', 'Docs']
values = [7, 4, 8, 6, 3]
plot_debt_radar(categories, values)
案例:从 1000 小时还债到 200
初始状态
总债务: 1000 小时
- 代码重复: 300h
- 缺少测试: 200h
- 性能问题: 150h
- 过时代码: 200h
- 安全问题: 150h
还债计划(6 个月)
| 月份 | 新功能时间 | 还债时间 | 还债项 | 累计还债 |
|---|
| 月 1 | 16天 | 4天 | 安全问题(优先) | 60h |
| 月 2 | 16天 | 4天 | 缺少测试 | 120h |
| 月 3 | 16天 | 4天 | 性能问题 | 180h |
| 月 4 | 16天 | 4天 | 代码重复 | 240h |
| 月 5 | 16天 | 4天 | 过时代码 | 300h |
| 月 6 | 16天 | 4天 | 重构 | 360h |
结果
6 个月后:
- 债务: 640h
- 债务率: 64% → 40%
- 团队速度: +20%
- Bug 率: -35%
指标体系
领先指标
| 指标 | 公式 | 目标 |
|---|
| 债务率 | 债务时间 / 总开发时间 | < 20% |
| 圈复杂度平均值 | 平均复杂度 | < 10 |
| 代码重复率 | 重复代码行 / 总行数 | < 5% |
| 测试覆盖率 | 覆盖行 / 总行数 | > 80% |
落后指标
| 指标 | 公式 | 趋势 |
|---|
| Bug 率 | bug 数 / 千行代码 | 下降 |
| 发布周期 | 发布间隔 | 稳定 |
| 新功能速度 | 功能点 / 时间 | 不变或提升 |
总结
技术债务量化管理:
- 测量 - 用复杂度、重复率、覆盖率
- 分类 - 按影响、成本、优先级
- 规划 - 20% 规则、持续还债
- 可视化 - 燃尽图、雷达图
- 迭代 - 持续改进,不一次性重写
“技术债务不是罪,是不偿还的罪。“
资源