用了 AI Coding 半年,代码量翻倍但维护变难:我们团队的「技术债决策矩阵」
我们团队 12 个人,用 AI 辅助开发 6 个月后,代码量涨了 2.3 倍,但下半年的迭代速度反而下降了 40%。这篇是我们怎么诊断问题、建立技术债决策框架的过程记录。
问题是怎么暴露的
去年下半年,我们引入了 AI 辅助编码工具。前三个月,所有人都很兴奋——Story Points 完成速度快了,功能上线节奏也快了。但到第五个月,有个工程师跟我说了一句话,让我一直记到现在:
“以前改一个地方我知道会影响哪些地方。现在我不知道了。”
我拉了一下数据:
| 指标 | 引入 AI 前(Q3) | 引入 AI 后(Q4) | 变化 |
|---|---|---|---|
| 月均代码提交行数 | ~18,000 行 | ~41,000 行 | +128% |
| 平均 PR Review 时间 | 1.2 天 | 2.8 天 | +133% |
| 线上 Bug 修复耗时(P2 级别) | 4.1h | 9.3h | +127% |
| 新功能迭代周期(Sprint) | 2 周 | 2.7 周 | +35% |
| 单元测试覆盖率 | 67% | 58% | -9pp |
数据很清楚:代码量翻倍,但系统可维护性显著下降。
原因也不难猜。AI 生成代码的速度远快于人工 Review 的速度。当 Review 成为瓶颈时,团队的自然反应是"先合并,后面再优化"。"后面"永远不来,技术债就这样积累了。
为什么传统技术债管理方法在 AI 时代失效了
传统技术债管理通常有几个假设:
- 债务是线性积累的——你写多少烂代码,债就增加多少
- Review 能过滤大部分问题——只要 Review 认真,技术债可控
- "有空了再还债"的窗口期比较长——一两个 sprint 的债不会酿成大问题
AI 辅助开发把这三个假设全打碎了。
AI 生成代码的速度是指数级的,不是线性的。我们团队 12 人,月均代码量从 1.8 万行涨到了 4.1 万行。这意味着 Reviewer 的认知负荷也同步翻倍——但人的 Review 能力不会翻倍,时间也没增加。
Review 开始走形式。当 PR 数量增加 2 倍,每个 Reviewer 能投入的时间只有原来的一半,Review 质量必然下降。很多 AI 生成的代码——逻辑上能跑、测试能过,但命名混乱、边界条件未考虑、与现有架构有隐式耦合——就这样溜进了 main 分支。
债务复利效应被放大了。一段糟糕的架构,在 AI 辅助开发环境下,可能在两周内被引用 20 次(因为工程师会让 AI 参考已有代码生成类似代码)。债主动在繁殖。
我们建立的「技术债决策矩阵」
花了大概两个月时间,我和团队 Senior 一起讨论、调整、落地了一个决策框架。核心是一个2×2 矩阵,两个维度:
- X 轴:传播风险(Contagion Risk)——如果不还这笔债,它会不会"感染"其他代码?AI 辅助开发环境下,AI 会参考现有代码生成新代码,所以烂代码的传播速度比以前快 3-5 倍。
- Y 轴:系统耦合度(Coupling Score)——这段代码被多少其他模块直接依赖?耦合度高 = 还债成本高,但不还的危险也高。
高耦合度 (High Coupling) │ [立刻还] [计划还] Q2 象限 Q1 象限 │ ────────┼──────── │ [放行] [观察] Q3 象限 Q4 象限 │ 低耦合度 (Low Coupling) 低传播风险 高传播风险四个象限的决策规则:
| 象限 | 传播风险 | 耦合度 | 决策 | 行动 |
|---|---|---|---|---|
| Q1 高传播 + 高耦合 | 高 | 高 | 立刻还 | 本 Sprint 内处理,不能 defer |
| Q2 低传播 + 高耦合 | 低 | 高 | 计划还 | 排进下两个 Sprint Backlog |
| Q3 低传播 + 低耦合 | 低 | 低 | 放行 | 接受,不主动还 |
| Q4 高传播 + 低耦合 | 高 | 低 | 观察 | 标记,若被引用超过 3 次升级到 Q1 |
如何量化两个维度
这是最关键的部分。矩阵好不好用,取决于你能不能快速、一致地给一段债务打分。我们用了以下方法:
传播风险评分(0-10)
# 传播风险计算逻辑(伪代码,实际用 AST 分析 + 人工校准)defcontagion_risk(debt_item):score=0# 1. 是否是被 AI 频繁引用的"样板代码"# 检查该文件/模块在过去 30 天内被 git blame 引用的频次ifai_reference_count(debt_item.path)>5:score+=4# 高危:AI 会学坏习惯elifai_reference_count(debt_item.path)>2:score+=2# 2. 是否是 utility / helper / base class# 越底层,传播风险越高ifdebt_item.layerin['utils','helpers','base','common']:score+=3# 3. 是否有清晰的接口隔离# 如果接口不清晰,调用方会 import 内部实现ifnotdebt_item.has_clear_interface:score+=2# 4. 过去 2 周是否有新文件 import 了这个模块ifnew_importers_in_2weeks(debt_item.path)>0:score+=1returnmin(score,10)简化版人工评分规则:
+4:这段代码在团队的"AI Snippet Library"里(团队会把常用代码教给 AI 参考)+3:工具类 / 基类 / 公共 helper+2:接口模糊,调用方会 import 内部实现+1:过去 2 周有新文件开始引用它
超过 6 分 = 高传播风险
耦合度评分(0-10)
# 用 madge 分析 JS/TS 项目的依赖图npx madge--jsonsrc/|python3 -<<'EOF' import json, sys deps = json.load(sys.stdin) # 计算每个模块被多少其他模块直接 import reverse_deps = {} for module, imports in deps.items(): for imp in imports: reverse_deps.setdefault(imp, []).append(module) # 输出被依赖超过 5 次的模块(高耦合候选) for module, dependents in sorted(reverse_deps.items(), key=lambda x: -len(x[1])): if len(dependents) >= 5: print(f"{len(dependents):3d} dependents: {module}") EOF对于后端项目(Python/Java/Go),可以替换成对应的静态分析工具:
# Python:用 pydeps 或 importlabpydeps src/ --max-bacon=3--show-deps --no-output2>&1|grep-E"^\s+[0-9]+"# Go:用 go mod graph + 自定义脚本go mod graph|awk'{print $2}'|sort|uniq-c|sort-rn|head-20# Java:用 ArchUnit 或 jdepend耦合度得分参考:
| 被依赖模块数 | 耦合分 | 解读 |
|---|---|---|
| 0-2 | 1-2 | 叶子节点,低风险 |
| 3-5 | 3-4 | 中度耦合,可接受 |
| 6-10 | 5-7 | 高耦合,还债成本高 |
| 11+ | 8-10 | 核心节点,非常危险 |
实际案例:三笔具体的债
我用我们实际遇到的三个案例说明矩阵怎么用。
案例 A:AI 生成的「万能 formatDate 工具函数」
背景:一个工程师让 AI 生成了一个formatDate函数,处理了 7 种格式转换。看起来很好用,于是全团队的 AI 都开始把这个函数当作参考样本。3 周后,这个函数被 23 个文件 import,但其中有 2 种格式转换有 bug(时区处理有问题)。
打分:
- 传播风险:9/10(在 AI Snippet Library 里,工具函数,接口模糊)
- 耦合度:8/10(23 个文件依赖)
矩阵判断:Q1,立刻还。
我们的处理:暂停使用该函数(加@deprecated注释),修复 bug,重构接口,一周内迁移所有调用方。
代价:约 2 个工程师 × 3 天 = 6 人天。但如果再等 2 个月(届时可能被 50+ 文件依赖),成本会是 3-5 倍。
案例 B:某 API 模块的「临时错误处理占位符」
背景:上线时为了赶 deadline,一个 API 模块的错误处理是这样的:
try:result=process_payment(data)exceptExceptionase:# TODO: 添加具体错误处理logger.error(f"Payment failed:{e}")return{"status":"error","message":str(e)}# 直接暴露内部错误信息这段代码的问题:直接把内部异常信息返回给前端(安全隐患),也没有分类处理不同类型的错误。
打分:
- 传播风险:3/10(这是业务逻辑,不是工具函数;AI 不会参考这种特定模块的错误处理作为通用范例)
- 耦合度:7/10(支付模块,被订单、退款、账单三个模块依赖)
矩阵判断:Q2,计划还。
我们的处理:排进下个 Sprint,写了一个标准化的PaymentException类族,重构了错误处理逻辑。不紧急,但不能无限期推迟。
案例 C:AI 生成的「没用上的数据处理管道」
背景:一个工程师探索性地让 AI 生成了一套数据处理管道代码,后来发现业务方向变了,这套代码没有被集成,但也没有删除。
打分:
- 传播风险:2/10(孤立文件,没有被其他模块引用,AI 也不会参考没有入口的代码)
- 耦合度:1/10(没有任何外部依赖)
矩阵判断:Q3,放行(直接删掉更干净)。
我们的处理:直接删除了这些文件,没有花时间"重构"它们。
在 AI 辅助开发环境下的特殊规则
传统技术债框架到这里就够了。但 AI 辅助开发有几个独特的点,需要额外的规则覆盖:
规则 1:AI Snippet Library 必须定期审计
大多数团队会有意无意地建立一套"告诉 AI 参考这个"的习惯。不管是.cursorrules、Copilot 的 Custom Instructions,还是团队内部的 prompt 模板,这些都是AI 的认知"地基"。
地基烂了,AI 生成的代码都会往烂的方向走。
我们的做法:每个 Sprint 末,专门检查一次 Snippet Library 和 Custom Instructions:
# 检查 .cursorrules 中的代码示例是否仍然符合当前最佳实践cat.cursorrules|grep-A10"example\|示例\|sample"# 检查团队 prompt 模板ls-la~/.config/cursor/prompts/规则:Snippet Library 中的代码,传播风险直接默认为 8/10。发现问题立即移除或修正,不等到债务扫描周期。
规则 2:AI 生成代码的首次合并要打「传播标记」
我们在 PR 模板里加了一个字段:
## PR 信息 - [ ] 包含 AI 生成代码(比例估算:____%) - 传播风险自评:[ ] 低(不太可能被复用/参考)[ ] 中 [ ] 高(工具类/基类/通用逻辑) - 如果是高传播风险:[ ] 已确认符合当前团队最佳实践这不是为了管控,而是为了让 Reviewer 分配注意力。高传播风险的 AI 代码需要比低传播风险的人工代码更严格的 Review。
规则 3:技术债"还款窗口"要主动调度
在 AI 辅助开发之前,我们是"有空了再还"。现在我们强制规定:
- 每个 Sprint 保留 15% 的容量给技术债偿还(不能被需求挤占)
- 每季度一次"债务审计":跑一遍依赖分析,重新给所有已知债务打分,升级/降级象限
这 15% 起初被业务方抵制。我们的解释是:“我们现在用 AI 每个月多写了 2 万行代码,如果不还债,6 个月后所有人都会把时间花在理解旧代码上,新功能的速度会归零。”
数据说话:执行这套框架的第三个月,我们的 PR Review 时间从 2.8 天降回到了 1.6 天,P2 Bug 修复时间从 9.3h 降到了 6.1h。没有完全回到 AI 引入之前,但趋势在好转。
一个容易踩的坑:「测试债」是最危险的
在我们的经验里,测试债是所有技术债里传播风险最高的一类,而且在 AI 辅助开发环境下被严重低估。
原因:AI 生成代码的测试覆盖率通常很低(AI 更擅长生成业务代码,测试代码质量相对差),而且当测试缺失时,AI 生成代码时无法验证其正确性。没有测试的代码 = AI 的"盲区",AI 看不到约束,就会生成看起来合理但实际破坏了某个边界条件的代码。
我们的规则:任何传播风险 ≥ 6 的代码,如果单元测试覆盖率 < 80%,自动升级到 Q1(立刻还)。
这条规则我们用 CI 强制执行:
# .github/workflows/debt-check.ymlname:Tech Debt Gateon:pull_request:branches:[main]jobs:coverage-check:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v4-name:Run tests with coveragerun:|pytest --cov=src --cov-report=json-name:Check high-risk modules coveragerun:|python scripts/check_high_risk_coverage.py \ --coverage-report coverage.json \ --high-risk-modules config/high-risk-modules.txt \ --threshold 80# scripts/check_high_risk_coverage.pyimportjsonimportsysdefcheck_coverage(coverage_file,high_risk_file,threshold):withopen(coverage_file)asf:coverage=json.load(f)withopen(high_risk_file)asf:high_risk_modules=[l.strip()forlinf.readlines()]failures=[]formoduleinhigh_risk_modules:# 在 coverage report 中查找对应模块forfile_path,file_dataincoverage['files'].items():ifmoduleinfile_path:pct=file_data['summary']['percent_covered']ifpct<threshold:failures.append(f"{file_path}:{pct:.1f}% <{threshold}%")iffailures:print("❌ 高风险模块覆盖率不足,需先补全测试才能合并:")forfinfailures:print(f" -{f}")sys.exit(1)else:print(f"✅ 所有高风险模块覆盖率达标(≥{threshold}%)")if__name__=="__main__":importargparse parser=argparse.ArgumentParser()parser.add_argument('--coverage-report',required=True)parser.add_argument('--high-risk-modules',required=True)parser.add_argument('--threshold',type=float,default=80)args=parser.parse_args()check_coverage(args.coverage_report,args.high_risk_modules,args.threshold)框架落地的三个前提
光有矩阵不够,还需要三个前提才能让框架真正运转:
1. Tech Lead 要亲自定义"高传播风险"的边界
不同团队、不同项目,"高传播风险"的定义不同。我们是 Node.js 全栈,utils 和 hooks 是高风险区。如果你是微服务团队,SDK 层和 proto 定义是高风险区。矩阵是模板,象限的边界要你自己校准。
2. 还债任务要和功能需求一起估点、一起排优先级
不能把技术债放进一个"有空再说"的 Backlog 里。Q1 债务要出现在当前 Sprint 的 board 上,有 Assignee,有 Story Point,有 DoD(完成定义)。
我们的 DoD 模板:
- [ ] 修复了原始问题(代码/测试) - [ ] 更新了 CHANGELOG(技术债标记) - [ ] 如果是工具函数:更新了 AI Snippet Library 中的参考示例 - [ ] Reviewer 确认传播风险已消除或降低3. 不要试图把所有债还清
这是最重要的认知调整。AI 辅助开发会持续产生技术债,速度可能永远快于还债速度。目标不是"零债务",而是控制 Q1(高危债务)的数量,让它保持在团队可管理的范围内(我们的目标是每时每刻 Q1 债务不超过 5 条)。
Q3 的债可能永远不还,这是可以接受的。
常见问题
Q:矩阵里的评分是主观的,怎么保证团队打分一致?
A:我们用了两个方法:一是每季度做一次"校准会议",拿 3-5 个历史案例重新打分,对齐理解;二是对于边界情况(传播风险 5-6 分),我们直接规定"疑似高风险按高风险处理",宁可多还一点债,不要因为争议而放行。
Q:15% Sprint 容量真的够用吗?AI 产生的债速度太快了。
A:对于大多数团队,15% 是起点,不是终点。我们第一个月是 10%,发现不够用,升到了 15%,目前稳定在 15-18%。如果你的 Q1 债务持续积压,说明还款速度不够,要继续调高。反过来,如果 Q1 债务每个 Sprint 都能清零,你可以试着降到 12%。这个数字本身要跟债务积压速度动态调整。
Q:PM/产品不理解技术债,每次都被业务需求挤掉,怎么办?
A:我们的做法是把技术债的成本翻译成业务语言。不说"技术债很严重",说"上个季度我们有 37% 的工程师时间在理解和修复已有代码,而不是开发新功能;如果这个比例继续增长,6 个月后新功能交付速度会下降 50%"。数据比抽象概念有说服力。
给其他 Tech Lead 的建议
用了 6 个月 AI 辅助开发,我最大的体会是:AI 改变了技术债积累的速度和模式,但不改变它最终需要被管理的事实。
如果你现在带的团队也在用 AI 辅助开发,我的建议是:
- 先跑一次依赖分析,摸清你的代码库里有多少"高耦合节点"
- 检查一下你的 AI Snippet Library 和 Custom Instructions,里面的代码是否仍然是最佳实践
- 在下个 Sprint 里留出 10% 的容量专门还债,哪怕只是先试一试
- 建立一个简单的 Q1 债务跟踪表(哪怕只是一个 GitHub Issue Label)
不需要一开始就建一套完整的系统。先从最危险的 Q1 债开始,其他的慢慢来。
