"这季度我们要集中还技术债。"

技术 Leader 说这话的时候,会议室里大部分人点头。有道理——代码库里那几个出了名的烂模块,每个人都受过它们的罪。三个月后,重构做了不少,代码确实干净了。但业务最重要的三个需求,只交付了一个。

做错了吗?不一定——重构可能确实有价值。但更可能的是,你还了不该优先还的债。那些代码虽然丑,但不阻碍业务、不制造错误、不影响排障。你还了它,感觉很好。最危险的债还在那里,没人注意到它。

"还技术债"不能成为口号。还什么、什么时候还、不还会怎样——这三个问题比"全部还掉"更需要判断力。

技术债不是什么

先说清楚技术债不是什么。因为大部分"还债"的精力,花在了不是债的东西上。

技术债不是"你不喜欢的代码风格"。一个项目用 tabs 还是 spaces、用 class 还是用函数、变量名够不够"优雅"——这些是偏好,不是债。偏好不影响系统行为,改了也不会让系统更好。

技术债不是"老的技术栈"。一个跑在 Java 8 上的服务,如果它稳定工作、变更需求低、团队熟悉——它不是债。它只是没在用最新的东西。把它升级到 Java 21 的成本可能是几周的人力和不确定的兼容性问题,收益是"我们现在用的是最新版本"。这不是还债,这是追新。

技术债也不是"别人写的代码"。一段你不理解的代码不等于有问题的代码。它可能是你在那个时间点也会做出的选择。读不懂先试着理解上下文,不要急着改写。

技术债是会影响你做事速度或系统正确性的东西。 更准确地说:它是今天的决策(或过去的决策)降低了未来变化速度、提高了修复成本、或引入了正确性隐患的结构性问题。它和"代码丑"的区别是:代码丑你不一定需要修,技术债不修会在某个时间点咬你。

还有一点值得说:技术债不一定是因为当初写错了。很多时候,是因为你对问题的理解变了。半年前基于当时的理解做的设计,现在业务变了、问题变了,原来的设计就成了债。这不是"当初写错了",是信息更新了。这个区分不是文字游戏——它影响你怎么对待写那段代码的人。如果是"写错了",归因是人的能力。如果是"信息更新了",归因是时间和变化的必然性。后者的心理负担更轻,也更接近事实。

三个维度

判断技术债优先级,我倾向于用三个维度评估:

  1. 演进速度 —— 这笔债是否正在拖慢你最重要的业务需求交付?
  2. 正确性 —— 这笔债是否已经导致或即将导致数据错误?
  3. 可观测性 —— 这笔债是否让你在线上出问题时是瞎子?

不是所有维度对每笔债都适用。但大部分值得还的债,至少在一个维度上得分很高。

演进速度

每次改一个业务逻辑要改 15 个文件——这是债。不是因为 15 个文件多,是因为变更一个业务概念的代价被放大了 15 倍。一个"商品上架"的需求,改了 ProductService、InventoryService、SearchService、CacheService、NotificationService,每个里面都有几行和商品状态相关的代码——没人记得全。漏改了一个,线上就不一致。

每次加一个新功能必须先理解一个 3000 行的函数——这是债。不是因为 3000 行多(虽然确实多),是因为理解成本变成了做任何事情的固定前置开销。新人来了,光读懂这个函数就要一周。这周不是在学习业务,是在学习某个人的编码习惯和过去三年积累的 hack。

但反过来也要看频率。如果"那坨代码"虽然丑但一年只需要碰一次,它的优先级就比天天被改动的烂代码低很多。债的利息和触碰频率成正比。一年碰一次的东西,重构它的收益最多每年兑现一次。每天都在碰的东西,重构的收益每天兑现。不是说一年碰一次的永远不重构——是说今天不该优先重构它。

还有一个容易被忽略的角度:有些代码改起来慢不是因为结构差,是因为业务本身就复杂。把"复杂业务逻辑"误判为"代码债"然后去重构,结果重构完了改起来一样慢——因为慢的原因不是代码结构,是业务规则本身就多。这种"重构"没有还债,只是把代码重写了一遍。

正确性

在多数业务系统里,这个维度应该放在最高优先级。

状态机有洞,数据可能进入非法状态——这类债应该优先还。不是"下个版本再说",而是要尽快排进当前迭代或最近一次修复窗口。数据进了非法状态,下游所有依赖这个数据的逻辑都在基于错误的前提运行。你以为只是一个小洞,但数据是有惯性的——错误数据会生成更多错误数据,直到有一天你发现整个表的状态分布都不可信了。

事务边界不清晰,数据可能不一致——这类债也应该靠前处理。写 A 成功了写 B 失败了,A 没回滚,数据就脏了。这种债不还,不是"以后会更慢",而是迟早会把问题从代码层面扩大到数据修复层面。修数据通常比修 bug 贵得多。

线上已经出现过一次数据错误、靠人工修复回来的——这类问题应该趁它还没有重复发生先处理掉。第一次是警告。第二次是事故。第三次是系统性的不信任。如果业务方已经开始说"你们的数据不可信",你欠的已经不是技术债了,是信任债。

涉及安全的同理——明文存储敏感信息、SQL 拼接、未鉴权的内部接口暴露在公网。这些不只是"技术债",而是明确的风险项。叫不叫"技术债"都无所谓,但它们必须出现在优先级列表上,而且位置要靠前。

正确性维度的判断标准很简单:这笔债如果不还,数据会不会变脏?系统会不会在某个时刻不可用?如果会,它就是最高优先级。

可观测性

出问题时你是靠监控发现还是靠用户投诉发现的?

如果是后者,你在可观测性上欠了债。不是说你没有日志——你可能日志打得很好。是说关键路径上没有结构化的 tracing,没有关联请求上下游的能力,日志在排查问题时"永远缺少关键字段"。每次排查问题,第一步不是看监控,是"先让我猜猜可能是哪里的原因"。猜到了算运气,猜不到就花几小时人肉排查。

可观测性债的隐蔽之处在于:它不影响正常功能的交付。happy path 照跑,测试照过,上线没问题。但它的影响在你最需要速度的时候出现——线上出问题了,用户在投诉了,你需要在 10 分钟内定位原因,但花了 40 分钟才找到。多出的 30 分钟不是因为你技术不行,是因为系统没给你足够的信息。

还有一个维度容易被忽略:可观测性债会影响你对其他债的判断。你不知道一笔债有多严重,因为你没有数据。你不知道某个模块的失败率是 0.1% 还是 5%,因为你没有在那条链路上埋点。你的"优先级排序"可能从一开始就是盲人摸象——因为你只能基于感觉判断,不是基于数据。

这比很多"代码丑"的债优先级高。代码丑改起来慢,但不影响出问题时的响应速度。可观测性缺失让你在问题发生时是瞎子。哪个更危险,不需要想太久。

什么时候值得借

不是所有债都不该欠。有意识地欠下的债,和不知不觉欠下的债,是两回事。

为了验证商业模式快速上线,故意简化了设计,跳过了抽象层,一个函数里写完了所有逻辑——如果这让产品提前两个月上线、拿到了关键的市场反馈,这笔债借得值。

但这种"战略债"有两个前提。

第一,你知道欠了什么。不是"我知道这里写得不好"这种模糊认知。是有一张清单:哪些地方做了简化、哪些边界条件没处理、哪些场景没覆盖。没有这张清单,"以后还"就是"永远不会还"。

第二,你有还债的窗口和计划。不是说"以后有空了还"——永远不会有空。是说"在 X 之前必须还掉,否则 Y 会发生"。比如"在用户量达到一万之前必须给支付接口加幂等,否则重复扣款的风险不可接受"。有 deadline 的债才有可能被还。

没有这两个前提的"先这样吧以后再说",不是战略债——是惰性。它和战略债的区别不是结果(都是欠了债),是你有没有在欠债的时候做过判断。

怎么和管理层沟通

很多技术团队在还债这件事上和业务方冲突,不是因为技术债不重要,是因为沟通方式错了。

"这段代码太烂了需要重构"——管理层听到的不是"需要重构",是"工程师想花时间写他们觉得好看的代码"。这个翻译是有损耗的,但工程师经常意识不到。

换一种说法:"这个模块目前的状态,让我们每次改相关业务逻辑需要多花两天时间,而且上线后出 bug 的概率比其他模块高 30%。如果我们用一周时间重构,之后每个相关需求的交付周期可以缩短两天。" 两天乘以每个季度的需求数,算出省出来的人天。一周重构投入的人天。两边一比,ROI 就出来了。

把债翻译成业务影响。 不是"代码丑",是"这个债每个月让我们多花两天处理线上问题"。不是"架构需要优化",是"如果这个服务挂了,恢复时间是现在的两倍,因为我们没有足够的观测能力快速定位问题"。

给选项,不要只给要求。A:花一周重构,后续交付加速。B:不重构但加监控,至少出问题时能快速定位。C:什么都不做,维持现状。让管理层在具体选项之间做判断,比让他们在"要不要还技术债"这种抽象问题上做判断有效得多。

不是零技术债

你的目标不是零技术债的代码库。

没有任何一个在运行的生产系统是零技术债的。如果你的系统在运行、在变化、在服务用户,你就在不断地做出取舍——取舍就会产生债。

目标是债被管理住。你知道哪些债存在。你知道哪些是高优先级的。你有一个不是"有空再说"而是"在 X 之前必须处理"的节奏。你知道哪些债在拖慢你、哪些在威胁正确性、哪些让你在出问题时是瞎子。

剩下的,可以等。

不是所有债都必须还。但所有债都必须被看见。