技术债不用全还:我是如何带着60%负债跑赢业务的

五年前,我是一个有代码洁癖的架构师。那时候,看到任何硬编码(Hardcode)或者没有抽象好的逻辑,我都会在Code Review时毫不留情地打回。我的口头禅是:“现在不改,以后就是雷。”

直到那次惨痛的教训:为了追求完美的微服务拆分,我们把原本两周上线的营销系统拖了一个半月。等我们那个“架构完美、无耦合”的系统上线时,竞品已经抢占了市场,公司直接损失了该季度30%的预期营收。

那一刻我才明白:在中小团队,技术债不是洪水猛兽,它是一种金融杠杆。 就像企业贷款经营一样,合理借贷能加速发展,只要不违约(系统崩溃),我们就应该学会与债共舞。

今天,我想聊聊如何在资源有限的情况下,做“可控的妥协”。

一、 承认吧,有些“屎山”是公司的印钞机

很多资深开发最痛苦的莫过于维护一套跑了三年的老系统。逻辑纠缠不清,变量名全是拼音缩写,改一行代码要战战兢兢半天。

但我想说个反常识的观点:如果一段烂代码跑了三年还没崩,而且每天都在产生流水,那它就是核心资产。

2020年,我们团队接手了一个负责订单结算的“祖传项目”。里面有一个长达800行的switch-case语句,用来处理不同渠道的支付折扣。新来的高级开发小李看了一眼就炸了,当天就写了一份详细的重构方案,计划引入策略模式(Strategy Pattern)加规则引擎。

我否决了那个方案。

原因很简单:这个模块虽然丑,但它极其稳定。它承载了公司80%的收入来源。重构的风险在于,我们根本没有足够的测试用例覆盖所有边缘场景。一旦少算了一分钱,财务那边就能把我们撕了。

我的处理方式是“围栏策略”:

  1. 封存核心烂代码:那800行代码,我禁止任何人修改其内部逻辑。
  2. 增量隔离:新的支付渠道,不要塞进那个switch里,而是写一个新的处理类。
  3. 门面模式(Facade):在调用层做一个路由,旧渠道走老逻辑,新渠道走新模式。

结果: 两年过去了,那个800行代码依然在那里,像一块丑陋但坚硬的基石。而我们的新业务跑得飞快,完全没有被老系统拖累。

核心逻辑:对于高价值、低频修改的遗留代码,“不触碰” 往往比 “重写” 性价比更高。

二、 别在只有500日活的时候搞“未来架构”

技术人很容易陷入一种“简历驱动开发”的陷阱。看到大厂在搞中台、搞Service Mesh,就觉得自己那一套单体应用(Monolith)太Low了。

我亲历过一个反面教材。当时我们在做一个内部报销系统,大概也就200个员工使用。团队里的架构师坚持要引入一套复杂的分布式事务框架(Seata),理由是“为未来扩展做准备”。

结果呢?

  • 配置地狱:每次部署环境,光是配Nacos、Seata、Sentinel就要花半天。
  • 调试困难:一个简单的状态更新,要跨三个服务排查日志。
  • 资源浪费:原本一台2核4G服务器就能跑得飞起的应用,硬是开了5台虚机。

三个月后,那个系统因为维护成本太高被废弃了,换成了第三方SaaS服务。

这也是一种技术债,我称之为“过度设计的债”。 而且这种债比代码写得烂更难还,因为它涉及基础设施和架构层面的剥离。

我现在的判断标准非常粗暴:如果在未来6个月内,业务量级不会增长10倍,就不要引入新的中间件。

与其花时间搞分布式事务,不如在数据库里多写几个try-catch和简单的本地事务表。在中小团队,“够用” 永远比 “完美” 重要。

三、 动态还债:给你的代码贴上“保质期”

说“可控妥协”不代表我们要放任自流。关键在于**“可控”**。

我每周五下午都会花一小时扫视Git提交记录,我发现很多时候,我们是为了赶上线而不得不写烂代码。这没问题,但问题是上线后大家就忘了。

为了解决这个问题,我强制推行了一个**“技术债签证”**制度。

当你因为赶进度必须写Hardcode,或者必须复制粘贴代码时,你不需要感到羞愧,但你必须在代码里加上特殊的注释标记。

我们约定的格式是这样的:

// TODO [DEBT] [2024-06-30] 为了赶双11活动暂时硬编码,活动结束后需重构为配置项读取
if (user.getType() == "VIP_11_11") {
    discount = 0.8;
}

这行注释包含三个关键信息:

  1. 类型:DEBT(明确这是债)
  2. 死线:2024-06-30(过了这个时间不改,它就是Bug)
  3. 原因:为了赶双11(让后人知道当时的无奈,减少骂你的概率)

配图

我们在CI/CD流水线上挂了一个脚本。每当编译时,脚本会扫描所有[DEBT]标记。一旦发现有超过“保质期”的债务,构建直接报警,甚至在低优先级项目中阻断发布。

这个机制运行了两年,效果出奇的好。它把隐性的心理压力转化成了显性的系统通知。开发人员不再担心“由于妥协而变烂”,因为他们知道,系统会提醒他们回来还债。

四、 落地工具与行动指南

技术债务管理不是要消灭债务,而是维持收支平衡。对于中小团队的架构师或资深开发,这里有一套我验证过的落地组合拳。

配图

1. 拿来即用的“债务登记卡”

不要搞复杂的Jira流程,就在代码库根目录建一个 DEBT.md,或者直接用Issue打Label。

推荐模板:

标题:[债务] 订单模块-金额计算逻辑硬编码 风险等级:高(影响核心金额)/ 中(影响维护效率)/ 低(仅仅是丑) 当前收益:节省了3天开发时间,保证了XX活动按时上线 利息成本:每次修改优惠规则都需要重新发版 偿还方案:引入规则引擎或抽取策略类 最后还款日:202X-XX-XX

2. 只有三个具体的行动步骤

如果你觉得上面的都太虚,明天上班只想做三件事,请做这三件:

  • Step 1:止血(Linter即正义) 不管是ESLint还是Checkstyle,把规则调到最严,但只对新代码生效。老代码一个都别动。这是为了防止债务进一步恶化。

  • Step 2:隔离(建立防腐层) 当你不得不调用那个恶心的老模块时,不要直接在该模块里改。在你的新代码和它之间写一个Adapter(适配器)或Facade(门面)。把你觉得恶心的逻辑封装在适配器里,保证你自己的新领地是干净的。

配图

  • Step 3:定额(20%税率) 和产品经理达成一个君子协定:每个迭代(Sprint),必须预留20%的时间用于处理技术债。不要问为什么,就说是为了“系统稳定性维护”。把这20%的时间用来清理那些已经过期的 // TODO [DEBT]

写在最后

架构设计的本质,就是在“资源约束”和“业务目标”之间寻找最优解

当我们不再执着于写出教科书般的代码,而是开始思考这段代码能为公司换取多少时间窗口,能节省多少服务器成本时,我们才真正完成了从“程序员”到“技术合伙人”的蜕变。

下次看到一段烂代码,先别急着骂,看看它是不是正在帮公司赚钱。如果是,给它加个注释,然后体面地绕开它。