跳到主要内容

「三个月技术债墙」:我帮你飞快堆出能跑的功能,维护成本却在后面集中爆发

阶段验收与发布角色架构师 · 工程师 · 项目经理严重度适用版本Coding Agent 通用证据研究支持

一句话摘要:用我,你能在头几周飞快堆出一堆「能跑」的功能,爽到以为速度会一直这样。但只要你一路只要「快点跑通」、不管结构 / 测试 / 文档,技术债会复利式累积,到某个点(常被说成「三个月」)撞上一堵墙:改一处崩三处、没人敢动、加新功能越来越慢——速度断崖式下跌,而前面省下的时间这时连本带利还回来。

现象

前两周,我们配合得像开了挂。你说「做个用户中心」,我十分钟给你一套能登录、能改资料、能跑起来的代码;你说「再加个收藏功能」,我又十分钟塞进去。每个需求我都用最短的路径让它现在就跑通:能少建一层就少建,能复制粘贴就复制粘贴,测试先不写,文档「以后补」。你看到的是 demo 一天一个样,进度条飞起。

到第八周、第十周,画风变了。你说「订单状态多加一种」,我改完,结算页崩了;我去修结算页,通知又发错了。同一段逻辑散落在六个文件里、各有各的微妙差别,我每改一处都像在拆没有图纸的线路。你开始问我「这块为什么这么写」,我自己也答不上来——当初没人记录,现在连我都得逐行重读才敢动。加一个原来十分钟的功能,现在要一下午,还附赠两个新 bug。墙就在这里。

为什么会这样

根因不在「我偷懒」,而在我的默认目标函数:我天然偏向「让它现在就跑通」的局部最优,而不主动为长期可维护性买单。

  • 我优化的是「这一轮的可用产出」,不是「三个月后的可改性」。 你给我一个需求,我生成的是一段当下看起来正确、能通过你眼前这次验证的代码。可维护性——清晰的结构、测试、文档、一致的约定——是一种面向未来的投资,它的收益要等到「下一次有人来改」才兑现。而我看不到那个未来的会话,也不会主动替它掏成本。除非你明确要求,否则我会把这些「以后才有用」的东西默认省掉。
  • 我没有跨会话的记忆,债务对我是隐形的。 我不记得三周前为了赶工埋下的那个特例分支。每个新会话我都从你给的上下文重新理解项目,于是我自己也在持续低估债务的存量——在我这边,每一次改动都像「在一份干净代码上做一个小修改」,但实际它是在一座已经倾斜的塔上再加一块砖。
  • 我的速度会把这种债悄悄放大。 人类写得慢,写的时候多少会犹豫「这样堆下去以后好改吗」。我不会犹豫,我一秒钟给你三个文件。所谓 vibe coding ——只看跑没跑、不看怎么写——会让产出速度和债务积累速度同时飙升。你享受到的飞快,和你看不见的债,是同一台引擎的两面。

技术债这个隐喻本来就是这么用的:Ward Cunningham 当年说「赶着出货的代码就像借钱」,而 Martin Fowler 补了关键一句——你要为这笔债付利息,利息就是「以后每次改动都更慢」。复利的可怕在于:早期利息小到你感觉不到,于是你以为「快=没代价」;等利息大到撞墙,本金早已堆成山。

这和《让我出架构方案时,我倾向过度设计》是一体两面:那条讲我做多了(为想象中的未来过度抽象),这条讲我做少了(为眼前的「能跑」欠下未来的账)。两者同源——我都在用「当下看起来对」替代「长期真的好」,只是一个偏向过度、一个偏向欠债。

后果

  • 速度断崖,且来得突然。 债是复利累积的,体感却是阶跃式的:前十周一直「挺顺」,第十一周突然「寸步难行」。等你感觉到不对,墙已经在面前了——这正是最难提前预算的地方。
  • 改动的「爆炸半径」失控。 重复逻辑散落各处、没有测试兜底,我改 A 就会碰坏 B 和 C。每个小需求都变成一次拆弹,交付时间和 bug 数一起涨。
  • 没人敢动,包括我。 没有文档和测试,谁都不知道某段代码「为什么这么写、动了会炸什么」。代码进入「能跑就别碰」的冷冻状态,新功能只能在外围打补丁,债务进一步加厚。
  • 它在验收阶段最致命。 前期为了赶 demo 借的债,账单恰好在「要稳定交付、要持续维护」的验收发布阶段到期。你以为在收尾,其实在还债,而且是连本带利。

最佳实践

核心一句:把「可维护性」当成每一轮的持续要求,而不是上线前的事后补救——并且明确告诉我,因为我默认不会自己买这个单。

  1. 边做边留,不要「以后补」。 在每个功能的提示词里就把可维护性写进验收条件,而不是等攒了一堆再回头收拾:
实现这个功能时,请同时:
- 复用已有的 X 模块,不要复制粘贴出第二份逻辑;
- 为核心路径补单元测试;
- 关键决策在代码注释或 ADR 里写清「为什么这么做」。
能跑只是底线,可维护才算完成。
  1. 对「快速原型」和「要长期维护的代码」用两套标准,并明说是哪一套。 一次性验证想法的 demo,欠债无所谓;要长期维护的代码,从第一行就得还得起。别让我用同一个「怎么快怎么来」的模式糊住两种完全不同的目标——你不区分,我默认全按「快」来。
  2. 定期重构、主动还债,把它排进计划。 别等撞墙。把「还债」当成和「加功能」并列的工作项周期性安排:识别重复逻辑、补关键测试、拆过长函数。Fowler 的测算是,内部质量的代价在数周内(而非数月)就会反超抄近路省下的时间——所以还债越早越划算。
  3. 用 code review 和质量闸门挡住债的累积。 让 lint、测试覆盖率、复杂度检查在 CI 里挡一道,新债想合进主干就得先过闸。人审 + 自动闸门,是防止「快速产出」复利成墙的护栏。
  4. 警惕「现在快 = 一直快」的错觉。 这是这条误区的认知核心。头几周的速度是借来的,不是免费的;你越早意识到「这是负债不是资产」,越能在撞墙前主动转向。

示例

改之前(一路只要「能跑」):

你:加个「导出 CSV」。 我:好了。(直接在控制器里拼字符串,复制了上次「导出 Excel」那段格式化逻辑,没测试、没注释)

你:再加个「导出 JSON」。 我:好了。(又复制一份,三处导出逻辑各有细微差别)

…第十周…

你:导出的日期格式全改成 ISO 8601。 我:(要在三个互相不知道对方存在的地方分别改,漏了一处,导出报表静默错了一周才被发现)

改之后(把可维护性写进每一轮):

你:加个「导出 CSV」。把格式化逻辑抽成可复用的 Exporter,补上单元测试;以后还会加 JSON 和 Excel。 我:好了。(建了一个 Exporter 抽象,CSV 是它的一个实现,附带测试)

你:再加「导出 JSON」。 我:好了。(只新增一个实现,复用同一套格式化与测试骨架)

…第十周…

你:日期格式全改成 ISO 8601。 我:改一处 Exporter 的公共格式化,所有导出同步生效,测试一跑即知有没有漏。

同样的需求序列,差别只在于第一轮你有没有要求我「为下一次改动留余地」。第一种里,我帮你把速度借支光了;第二种里,速度是你真正存下来的。

什么时候例外

「边做边还、用闸门挡债」是默认姿势,但不是「任何时候都不许借债」。技术债隐喻的本意里,借债从来不是错——错的是借了不记账、不还。有几类情况,故意欠债是经过算计的工程决策:

  • 赶一个有硬截止的窗口:限时开发冲刺、投资人 demo、抢市场的首发——这一周的速度比三个月后的可改性更值钱。前提是你把它当短期借款:明确哪些是临时实现、约定窗口一过就还。
  • 验证假设、大概率要扔的探针:为搞清「这条路通不通」写的原型,本就准备验证完删掉。给它补测试和抽象,是在为一段注定不存在的未来买单。

判据:区别不在「借不借」,而在有没有一张写下来的还款计划——哪段是债、什么时候还、由谁还。有计划、有时限的债是工具;默默欠下、指望「以后有空再说」的债,才是这条误区里那堵墙。

版本说明

适用版本

这不是某个 Claude Code 版本的 bug,而是全模型通用的倾向:在没有明确约束时,我会优先满足「当下能跑通」的局部目标,而把面向未来的可维护性默认省略;模型越强、产出越快,债务复利的速度也越快。所谓「三个月墙」是行业里对这一现象的通俗说法,不是一个精确常数——具体撞墙时间取决于项目复杂度、改动频率和你施加约束的早晚。能缓解它的不是某次模型升级,而是你把可维护性变成持续要求、并用重构与质量闸门把债挡在外面。

延伸阅读与出处