“技术债务”这个比喻最早是 Ward Cunningham 在 1992 年提出的。他的原意是:为了快速交付,你可以暂时走捷径,但要像欠债一样记住它,并在未来偿还。就像金融债务一样,适度的杠杆可以加速发展,但失控的债务会压垮你。
问题在于,大多数团队既不记录技术债,也没有偿还的计划。技术债就这样无声地堆积,直到有一天你发现:改一个按钮的颜色需要三天。
技术债的四象限
Martin Fowler 提出过一个技术债的四象限分类,我觉得非常实用:
| 审慎的(Deliberate) | 鲁莽的(Reckless) | |
|---|---|---|
| 有意识的 | ”我们知道这不是最优方案,但为了赶 deadline 先这样做,之后再重构" | "我们没时间做设计,先写能跑的代码” |
| 无意识的 | ”做完之后才发现有更好的方案" | "什么是设计模式?” |
左上角(审慎 + 有意识)是健康的技术债:你清楚地知道自己在做什么权衡,有计划在未来偿还。
右上角(鲁莽 + 有意识)是危险的技术债:你知道代码质量差,但选择了不管不顾。
左下角(审慎 + 无意识)是不可避免的技术债:随着你对问题域理解的深入,早期的设计自然会变得不够好。
右下角(鲁莽 + 无意识)是最可怕的技术债:团队能力不足,写出来的代码本身就有问题,而且没有人意识到。
什么时候必须还债
不是所有技术债都需要立即偿还。但以下几种情况,拖得越久代价越高:
1. 影响交付速度的债
如果一个模块已经脆弱到”每次改它都要修三个 Bug”,那你在这个模块上的每一次迭代都在为之前的技术债支付利息。当利息超过了偿还成本,就该重构了。
一个简单的判断方法:如果一个功能的开发时间中,超过 50% 花在”理解和绕过遗留代码”上,那这笔债该还了。
2. 安全相关的债
跳过了认证、用了明文存密码、没做输入验证——这类债务没有”慢慢还”的选项。安全相关的技术债是定时炸弹,一旦被利用,损失不是代码层面的,而是业务层面甚至法律层面的。
3. 影响团队招聘和留存的债
如果你的代码库糟糕到新人入职三个月都无法独立提交代码,或者资深工程师因为”代码太烂”而离职,那技术债已经在影响你最重要的资产——人。
什么时候可以欠着
1. 即将废弃的模块
如果一个功能计划在三个月内下线,花两周去重构它就是浪费。用注释标记清楚”此模块将在 X 版本废弃”,把时间花在更有价值的地方。
2. 探索期的代码
产品方向还不确定的时候,写”完美”的代码是一种浪费。因为你大概率会在方向明确后重写它。探索期的代码要做到”能跑、能读、能删”——而不是”能扩展、能复用、能维护十年”。
3. 边缘场景的处理
如果一个 edge case 每年只出现一次,而修复它需要重构核心模块,那用 try-catch 包住然后记个日志就够了。完美主义在边缘场景上的 ROI 极低。
如何管理技术债
可见性是第一步
大多数技术债之所以失控,是因为它是”隐性”的。只有写代码的人知道,管理层看不到,产品经理不关心。
把技术债显性化:
- 在代码里用
// TODO(tech-debt):标记,附上简短的描述 - 在项目管理工具里建一个”Tech Debt”标签,和功能需求放在同一个 backlog 里
- 定期(比如每个 sprint 回顾会)讨论当前最严重的技术债
20% 规则
Google 曾经有”20% 时间做个人项目”的传统。类似的,一些团队会保留每个 sprint 20% 的时间来偿还技术债。
这个比例不一定精确,但核心思想是对的:技术债偿还不应该是”等有空再做”的事情,而应该是开发流程的常规组成部分。
重构的粒度
不要做”大重构”。大重构意味着长期分支、大量冲突、无法增量交付、风险不可控。
更好的做法是”持续小重构”:
- 每次改一个文件时,顺手改进它的结构
- 每个 PR 包含一点点重构,和功能改动混在一起
- 设定一个”童子军规则”——离开时代码比来时更干净
Martin Fowler 说过:“重构应该像刷牙一样——每天做一点,而不是等牙疼了再去看牙医。“
度量技术债
几个有用的度量指标:
- 代码变更频率 × Bug 率:经常改又经常出 Bug 的文件就是高利率的技术债
- PR Review 时间:如果 Review 时间越来越长,可能是代码复杂度在上升
- 新人上手时间:新工程师从入职到第一个有意义的 PR 需要多久?这个时间在变长吗?
- 部署信心:团队对”随时部署”有多大信心?信心在下降说明系统的可靠性在被技术债侵蚀
最后
技术债不是敌人,失控的技术债才是。
最好的工程团队不是”零技术债”的团队——那种团队通常交付太慢。最好的团队是能够有意识地承担技术债、清楚地记录技术债、有节奏地偿还技术债的团队。
借用金融的话说:关键不是不借债,而是借得明白、还得起、利息可控。