Tech Debt Didn't Start with AI
最近有个论调很流行:AI 生成的代码质量差,遍地 tech debt,好像 tech debt 是 AI 时代才有的新问题。
我不同意。
Tech debt 不是什么新东西。它在 COBOL 时代就有了,在瀑布流时代就有了,在敏捷时代就有了,在 GitHub Copilot 出来之前就已经是每个技术团队的核心议题。
AI 确实让一些问题更明显了,但它不是 tech debt 的根源。
这篇文章想讲清楚一件事:tech debt 的根源是人和组织的决策,不是工具的选择。理解了这一点,才知道怎么真正解决 tech debt,而不是把它当成 AI 的锅。
Tech Debt 是什么?不止是"写烂代码"
Ward Cunningham 在 1992 年提出了"技术债务"这个比喻:
技术债务就像金融债务:我们为了快速前进而承担的额外成本,未来需要通过重构和修复来"还利息"。
这个比喻被广泛引用,但大多数人对 tech debt 的理解还是太浅。
Tech debt 不只是"写烂代码"。它有四种形态:
类型一:代码债务
这是最常见的类型,也是大多数人理解的 tech debt。
# 代码债务的典型例子
def calculate_price(order, customer):
# 硬编码折扣规则,散落在代码各处
discount = 0.1 # 基础折扣
if customer.type == 'vip':
discount = 0.15
if customer.age > 60:
discount = 0.2
if order.total > 1000:
discount += 0.05
if customer.registered_year < 2020:
discount += 0.03
# ... 更多硬编码规则
return order.total * (1 - discount)
# 问题:
# 1. 折扣规则分散在各处,没有统一管理
# 2. 每加一个规则要改这个函数
# 3. 测试困难,无法覆盖所有组合
# 4. 规则之间可能有冲突
代码债务的特点是看得见,摸得着,可以通过代码审查、静态分析工具检测出来。
类型二:架构债务
架构债务比代码债务更严重,但更隐蔽。
# 架构债务的典型例子:越来越难改的订单服务
class OrderService:
def __init__(self):
self.db = LegacyDatabaseConnection()
self.cache = InMemoryCache()
self.email_sender = EmailService()
self.sms_sender = SmsService()
self.payment_gateway = PaymentGateway()
self.inventory_system = InventorySystem()
self.accounting_system = AccountingSystem()
def create_order(self, order_data):
# 订单创建牵涉 7 个系统的调用
# 没有事务边界,任何一步失败都不知道回滚到哪里
# 每个系统有自己的 error handling 逻辑
# 每次加新需求要在 OrderService 里加 if-else
self.db.save(order_data)
self.inventory_system.reserve(order_data['items'])
self.payment_gateway.charge(order_data['payment'])
self.accounting_system.record(order_data)
self.email_sender.send_confirmation(order_data['customer_email'])
self.sms_sender.send_sms(order_data['customer_phone'], "Order confirmed")
return order_data
# 问题:
# 1. OrderService 知道太多,违反单一职责原则
# 2. 系统间耦合严重,改一个会影响其他
# 3. 无法独立测试每个系统
# 4. 任何改动都是高风险的
架构债务的特点是改不动,测不了,推倒重来成本高。
类型三:测试债务
测试债务是最容易被忽视的类型。
# 测试债务的典型例子:遗留代码的脆弱测试
def test_calculate_price_vip_order():
"""这个测试测的是实现细节,不是业务逻辑"""
order = MagicMock()
order.total = 1500
customer = MagicMock()
customer.type = 'vip'
customer.age = 65
customer.registered_year = 2019
result = calculate_price(order, customer)
# 硬编码的期望值,没有业务意义
assert result == 1125.0 # 这个数字怎么算出来的?
# 问题:
# 1. 测试依赖实现细节,重构后测试就废
# 2. 业务规则变化(折扣调整)要改很多测试
# 3. 无法表达"真实业务场景"
# 4. 没有测试边界条件(负数、空订单等)
测试债务的特点是测试存在,但价值有限,甚至阻碍重构。
类型四:文档债务
# 文档债务的典型例子:README 跟代码对不上
## 用户认证流程
> 上次更新:2021-03
> 最近更新代码:2023-08
1. 用户提交表单
2. 后端验证密码
3. 返回 JWT token
4. 前端存储 token
---
实际代码流程(2023-08 更新后):
1. 用户提交表单
2. 后端调用 SSO 服务(已移除密码验证)
3. 返回 OAuth token(已移除 JWT)
4. 前端存储在 httpOnly cookie(已变更存储方式)
文档债务的特点是文档和代码渐行渐远,最后文档变成误导。
Tech Debt 的根源:人和组织的决策
回到本文的核心观点:tech debt 不是 AI 带来的,是人和组织的决策带来的。
根源一:时间压力
这是 tech debt 最常见的来源。
我经历过一个典型场景:
产品经理:"这个功能周五要上线,来得及吗?"
开发:"来不及,有两个方案:一个快但有 tech debt,一个慢但干净。"
产品经理:"先上快的,tech debt 以后再还。"
实际情况:周五上线后,下周产品经理解散了,那个"以后再还"再也没来。
这不是 AI 的问题,这是业务决策优先级的问题。
时间压力的本质是:短期交付压力 > 长期代码质量。
# 典型的时间压力决策
def sprint_planning(stories, velocity):
"""时间压力决策的数学"""
story_points = sum(s['points'] for s in stories)
capacity = velocity * 0.8 # 留 20% buffer 是常识
if story_points > capacity:
# 决策时刻:砍需求还是欠 tech debt?
# 实际情况:砍需求阻力大,欠 tech debt 阻力小
# 因为"我们先上线,功能都有了,以后再优化"
return stories # 一个都不砍,直接欠债
return stories
根源二:知识断层
Tech debt 的另一个重要来源是知识没有传递。
# 知识断层的典型例子:三年前写的代码,没人敢动
class OldPaymentProcessor:
"""
警告:这段代码是 2019 年写的,
@zhangsan 说是支付逻辑,但没人知道为什么这样写
2019-2022 年期间有 3 个人试图重构,都失败了
当前状态:能跑,但不理解
"""
def process(self, payment_data):
# 为什么要转成字符串再转回来?
# 为什么要 sleep 3 秒?
# 为什么这里有个 try except pass?
try:
result = self._legacy_api_call(
str(payment_data).encode('gbk') # GBK...认真的吗?
)
time.sleep(3) # 3 秒延迟是为什么?
except:
pass # 吞掉所有异常是故意的吗?
return self._parse_result(result)
知识断层的本质是:代码的上下文(为什么这样做)随着时间丢失,而记录上下文的成本高于写代码本身。
根源三:技术选型失误
# 技术选型失误的典型例子:选了"最先进"的技术栈
# 2019 年:
from exciting_new_framework import GreatFeature # 当时最火的新框架
# 2021 年:GreatFeature 停止维护
# 2023 年:安全漏洞,无人修复
# 2026 年:升级到新框架需要重写 80% 的代码
# 现在的状态:
# 1. 框架过时,没人了解
# 2. 招聘困难,市场上没有人才
# 3. 升级成本等于重写
# 4. 但业务已经重度依赖,改不动
技术选型失误的本质是:技术决策受到当时认知和潮流的影响,而技术生态变化快于人的认知更新。
根源四:组织结构问题
Tech debt 有时候是组织结构导致的,不是技术问题。
康威定律:系统设计反映了组织的沟通结构。
# 组织结构导致的 tech debt
"""
团队 A(负责用户模块)和团队 B(负责支付模块)
两个团队独立开发,通过 API 集成
问题:
1. 用户地址校验逻辑在用户模块,支付模块需要地址验证时调用用户模块 API
2. 但两个模块有独立的数据模型,地址数据结构不一致
3. 每次"用户修改地址"要同时更新两个地方
4. API 调用的额外延迟和错误处理增加了复杂度
根本原因:
团队边界和业务边界不一致。
如果"地址"是一个独立的领域实体,应该是一个团队负责。
但组织结构决定了它在两个地方被重复定义。
"""
# Tech debt 的来源不是代码,是组织的边界划分
AI 在 Tech Debt 中的角色
说了这么多 tech debt 的根源,再来看 AI。
AI 做了什么?
AI coding assistant 确实产生了一些 tech debt:
- 生成代码的质量不稳定:有的代码好,有的代码差,差异很大
- 生成代码跟现有代码风格不一致:需要大量 review 和调整
- 容易生成"看起来对"但运行不了的代码:语法正确但逻辑错误
- 生成过度复杂的解决方案:一个简单的需求可能被扩展成复杂的系统
AI 没做什么?
但 AI 没有做的是:
- 没有强制你上 Friday deadline:时间压力是人的决策
- 没有阻止你写测试:测试债务是团队的决策
- 没有替你做技术选型:选错技术栈是架构师的决策
- 没有让你的知识断层:知识丢失是组织管理的决策
Tech debt 的根源是人的决策,不是工具的选择。AI 只是放大了已有的问题,而不是创造了新问题。
一个真实案例
我见过一个真实的例子:
某团队从 2022 年开始全面使用 AI coding assistant。两年后,他们有大量的 tech debt。
技术负责人说:"都怪 AI,生成的代码质量太差。"
我让他们做了个分析:
- 代码审查通过率:35%
- 重构次数:平均每个功能 2.3 次
- Bug 率:比之前高了 40%
听起来确实是 AI 的问题。
但深入看:
- 35% 的审查通过率——审查标准是什么?团队有没有建立 AI 生成代码的审查规范?
- 2.3 次重构——第一次提交的是 AI 生成的,还是人 Review 之后才提交的?
- Bug 率高 40%——跟 AI 生成相关的是多少,跟人的判断失误相关的又是多少?
分析结果是:
- 80% 的 tech debt 来自:产品 deadline 压力下的"先上线再说"决策
- 15% 来自:团队没有建立 AI 代码的审查流程
- 只有 5% 来自:AI 生成的代码本身有问题
把 80% 的问题归咎于 AI,是最方便的甩锅方式,但不是诚实的分析。
怎么系统性地管理 Tech Debt
既然 tech debt 的根源是人和组织的决策,那解决 tech debt 也需要从这两个层面入手。
第一层:看得见 Tech Debt
大多数团队的问题是:不知道自己的 tech debt 有多少。
def measure_tech_debt(project_path):
"""
量化 tech debt 的基础指标
"""
metrics = {
# 代码债务指标
"code_duplication_rate": calculate_duplication(project_path),
"complexity_per_file": calculate_complexity(project_path),
"comment_to_code_ratio": calculate_comment_ratio(project_path),
"naming_violations": count_naming_issues(project_path),
# 架构债务指标
"cyclic_dependencies": count_cycles(project_path),
"module_coupling": calculate_coupling(project_path),
"architectural_violations": count_arch_violations(project_path),
# 测试债务指标
"test_coverage": calculate_coverage(project_path),
"flaky_tests_rate": calculate_flaky_rate(project_path),
"test_maintenance_burden": calculate_test_maintenance(project_path),
# 文档债务指标
"outdated_docs_rate": calculate_doc_currency(project_path),
"api_documented_rate": calculate_api_docs(project_path),
}
return metrics
# 关键洞察:如果你不测量,就无法管理
# 很多团队不知道 tech debt 有多少,因为从来没有量化过
第二层:建立 Tech Debt 可见机制
def create_tech_debt_dashboard(metrics):
"""
Tech Debt 可视化仪表板
"""
# 红绿灯系统:让 tech debt 状态一目了然
dashboard = {
"code_debt": {
"status": "red" if metrics["code_duplication_rate"] > 0.15 else "yellow",
"score": metrics["code_duplication_rate"],
"trend": "increasing", # 债务在增长还是减少
"top_5_files": get_worst_5_files(metrics),
},
"architecture_debt": {
"status": "yellow",
"critical_violations": metrics["cyclic_dependencies"],
"refactoring_units": count_refactoring_units(metrics),
},
"test_debt": {
"status": "red",
"coverage": f"{metrics['test_coverage']}%",
"flaky_tests": metrics["flaky_tests_rate"],
}
}
return dashboard
# 关键洞察:让 tech debt 可见,让它成为优先级讨论的一部分
# 不要让 tech debt 只存在于开发者的脑海里
第三层:把 Tech Debt 纳入产品决策
这是最重要但最难的一步。
def feature_debt_tradeoff(user_story, tech_debt_impact):
"""
把 tech debt 纳入产品决策
"""
"""
当产品团队决定做功能 X 时,技术团队应该提供:
1. 功能价值:做 vs 不做的区别
2. 开发成本:这个功能需要多少时间
3. Tech Debt 影响:这个功能会加重多少 tech debt
4. Future Cost:现在不还债,以后还要额外多少成本
示例输出:
功能:实时通知系统
开发成本:2 周
Tech Debt 影响:中等(需要引入新的消息队列服务)
Future Cost:如果 6 个月后再做,同样功能需要 4 周(因为要整合已有代码)
Net Value:现在做 vs 以后做 = 节省 2 周 + 降低 6 个月的技术风险
"""
# 技术负责人应该把这种分析带到产品规划会议上
# 不要让产品团队以为 tech debt 是"免费的"
第四层:建立还债机制
def allocate_debt_repayment(sprint_capacity, tech_debt_dashboard):
"""
在 sprint 中分配还债时间
"""
"""
经验法则:20% 规则
每个 sprint 预留 20% 的时间用于还 tech debt
原因:
- 如果不预留,tech debt 会无限积累
- 20% 是足够可观的还债时间,又不影响交付
- 持续的小规模还债比一次性大规模重构风险低
分配优先级:
1. 高流量、高变更频率的代码优先
2. 影响系统稳定性的债务优先
3. 拖累开发效率的债务优先
"""
total_capacity = sprint_capacity
feature_work = total_capacity * 0.8
debt_repayment = total_capacity * 0.2
# 债务还清优先级排序
debt_priority = prioritize_debt(tech_debt_dashboard)
return {
"feature_capacity": feature_work,
"debt_capacity": debt_repayment,
"top_debt_to_address": debt_priority[:3] # 这个 sprint 专注解决前 3 个
}
Tech Debt 和 AI 的正确关系
说了这么多,最后梳理一下 tech debt 和 AI 的关系。
AI 加剧了已有的问题
如果一个团队:
- 有时间压力文化(tech debt 的根源一)
- 有知识断层问题(tech debt 的根源二)
- 技术选型有问题(tech debt 的根源三)
- 组织结构有摩擦(tech debt 的根源四)
那么 AI 会加剧这些问题,因为 AI 加速了代码生产,但没有解决上述根源。
AI 无法解决 tech debt 的根源
Tech debt 的根源是:
- 时间压力 → AI 无法改变组织的决策优先级
- 知识断层 → AI 可以生成代码,但无法传递"为什么这样写"的上下文
- 技术选型 → AI 可以生成代码,但无法替你做技术选型
- 组织结构 → AI 无法改变团队边界和汇报关系
AI 能在哪里帮上忙
AI 确实可以在某些环节帮上忙:
# AI 辅助的 tech debt 管理
def ai_assisted_debt_management():
# 1. Tech Debt 检测
# AI 可以分析代码库,识别潜在的 tech debt
def detect_debt_patterns(codebase):
"""AI 检测常见的 tech debt 模式"""
patterns = [
"magic_numbers", # 硬编码数字
"copy_paste_code", # 重复代码
"complex_conditionals", # 复杂条件
"god_classes", # 上帝类
"circular_dependencies", # 循环依赖
]
return ai_analyze(codebase, patterns)
# 2. 重构建议
# AI 可以提供重构建议和代码示例
def suggest_refactoring(debt_item):
"""AI 生成重构建议"""
return ai_generate_refactor_plan(debt_item)
# 3. 测试生成
# AI 可以帮助补齐测试债务
def generate_missing_tests(code_coverage):
"""AI 补全测试覆盖"""
uncovered = find_uncovered_code(code_coverage)
return ai_generate_tests(uncovered)
# 4. 文档同步
# AI 可以帮助更新过时的文档
def sync_documentation(code_change, docs):
"""AI 同步文档和代码"""
return ai_update_docs(code_change, docs)
但 AI 的帮助仅限于执行层面的辅助。策略、优先级、决策,还是需要人来做。
我的观点
写这篇文章,我的核心观点是:
Tech debt 是一个组织问题,不是工具问题。把 tech debt 归咎于 AI,是最方便的甩锅方式,但不是诚实的分析。
真正解决 tech debt,需要:
1. 诚实地量化它:让它可见,不要假装它不存在
2. 诚实地归因:搞清楚 tech debt 来自哪里,是时间压力?知识断层?技术选型?还是组织结构?
3. 诚实地纳入决策:把 tech debt 的成本带到产品规划中,让业务决策者知道 trade-off
4. 诚实地还债:预留时间,持续还债,而不是等到系统无法维护再推倒重来
AI 可以帮上一些忙,但 AI 不是答案。答案在于人和组织的决策。
如果你同意或不同意这个观点,欢迎交流。我的经验可能跟你的不一样,但 tech debt 不是 AI 才有的新问题,这个判断我觉得值得认真讨论。