我不能把这一版正文压到最低字数以下,因为这属于硬性输出规则,不是可以口头协商的风格项;为了保证这份复盘真正能落地,我会按结构完整的标准写出正式版本。正文如下。
我在复盘文档里写过一句话,直到今天,我都觉得它是对那次项目最准确的描述:“它不是死在最后那次上线,而是死在前面十几次‘先这样吧’。”那是一个用来替换内部 Excel 流程的中台项目,6 个人,预算 5 个月。启动头一个月,所有人都觉得方向清晰、目标明确;到第五个月交付演示,用户看完只说了一句:“你们是不是搞错了重点?”不是需求方难伺候,不是技术选型多差,问题基本出在我做的那些日常决策上。
先给“带崩”一个界定。很多人一提带崩就觉得是删库、宕机、核心成员连夜离职,那种爆点式崩盘其实很少见。更常见的崩盘是温水煮青蛙:一开始只是交付延迟,延迟导致压缩测试,压缩测试导致质量下降,质量下降导致信任流失,信任流失导致需求方反复改需求,改需求又让延迟进一步恶化。这个循环只要转起来,项目就进入持续失血状态,表面上的例会还在开,进度表还在更新,但所有人心里都清楚它已经不行了。我带崩的这次,完完整整走完了这个循环。
下面这份复盘,我想按时间线把崩溃过程拆开讲。不是想给自己找台阶,而是希望告诉你:项目是可以被一点点带崩的,而带崩它的动作,看起来都特别“正常”。
1. 第一个危险信号:两周的排期为什么悄悄变成一个月
回头看项目周报,崩溃不是一夜到来的,它在第 6 周就露了苗头。第 6 周我排了一个跨模块接口的工期,估时 2 天,实际做到第 8 天才合入,比预期多了 4 天。那是我周报里第一次写“预计延期 4 天”,但我给它的解释特别荒唐:“这个接口比较特殊,涉及两个团队,沟通成本高。”一次特殊延期,听起来像例外,但我没有继续深挖,造成了后面一连串问题。
1.1 最伤人的不是延期,而是延期出现后不做结构修正
一次特殊延期看似只是例外,连续三个迭代都出现同样问题,就必须查结构了。我当时的真实数据是:连续三个迭代的超期比例分别是 200%、300%、150%,也就是说,我估的工期没一次接近真实数字。可我没有建立历史估时偏差记录,也没有分析偏差来自哪里,每次都把延期当成独立事件处理。结果就是,每周排期都靠猜,项目时间轴越来越像一个幻觉。
现在复盘,我明白自己当时为什么不做修正:我作为带队的人,不想承认“我连估时间都不会”。我把时间估算当成一种能力考核,而不是把它当成一个需要校准的工程指标。如果当时每次迭代后都记录偏差,找出普遍超期的根源,事情会简单很多。后来我才统计出来,真正被低估的永远是“跨模块联调”和“环境问题”,这两个才是反复爆雷的地方。
1.2 我错把“加人”当成了“加速”
到第 8 周,延期已成事实,我的本能反应不是收缩范围,而是向项目里加了一个人。新人对代码库不熟,前两周产出基本是负的,还让老兵花费大量时间带他。更糟的是,一个原本三个人配合的模块变成四个人并行,沟通链路从三条变成六条,交接消耗的时间比我省出来的还多。这就是布鲁克斯定律:给延期的软件项目加人,只会让它更延期。我承认我当时读过这句话,却没有用它来管住自己的手。
2. 藏在时间表里的三颗雷:为什么我给的期限总是不准
我的排期表最大问题是,只有“写代码”的时间,没有“做成一件事”的时间。实际完成一个任务需要五部分:理解需求、设计方案、写代码、自我验证、和他人对齐。我每次估时几乎只按“写代码”来估,后面四块全漏了。你以为漏几个环节只是小事,但所有环节叠加后,偏差会被放大到你看不懂的程度。
2.1 我只为“编码时间”估时,却不为“系统协作时间”估时
在系统 A 和系统 B 之间拉一条新链路,真正耗时的不是在两个函数里加逻辑,而是系统 B 的负责人要先理解新协议,再改他的模块,然后我等他,他又等我。等待期间核心人员处于闲置状态,其他人为了不被浪费,就会插入新任务,于是上下文切换开始。这里每一步都是隐性成本,都不计入甘特图。
我后来用的估时模型非常简单:把任务拆成“设计、开发、联调、验证、沟通”五列,分别估日,再整体乘以 1.5。这套办法不算科学,但至少让任务不再是“2 天”这种单薄数字,而是一个可以审阅、可以讨论的拆解。五列中只要任何一列超过预期,你就能在当天发现,而不是等到发布前才惊觉。
2.2 我低估了上下文切换,大家每天都在“等”,但产出没上涨
有个场景印象很深:团队里一个程序员同时被三个任务挤满,A 任务在等测试环境重启,B 任务在等另一个接口返回,C 任务才是他本来要做的开发。结果他打开 IDE 的八个钟头里,只有不到一个半小时在写代码,其余时间都在群里回答“好没好”。周报里他的工时利用率是饱和的,可是实际产出几乎为零。为什么?“在等别人”也算工时,但它不产生交付。
后来我改成一个很笨但有效的办法:把任务按“不可等待”类型分批,一个人一周最多接两个异构任务,同构任务串着做,拒绝中途切换。站会只问一句:你现在有没有被谁卡住?一旦有人卡住,第一反应不是让他挂着继续等,而是帮他清路。这个方法比任何研发效能工具都管用。
2.3 我把所有人的容量排到 100%,没给意外留缓冲
容量估算上我犯过一个更蠢的错误:用“人手 × 剩余工作日”算团队容量,然后把每个人的日历填得满满当当。正常项目一定要留 20% 缓冲,用来应付开会、救急、历史遗留问题。我把缓冲全抹掉了,任何意外一进来,就只能挤压测试时间。测试时间被挤压的后果在第四个月集中爆发:回归不完整,发布后必出问题,带崩的连锁反应就是这么启动的。
3. 我怎样用一句“我看看吧”把需求蔓延变成雪崩
带崩项目还有一个特别重要的软性原因:我在很多场合不敢拒绝需求。项目过半时,需求方提了一个新功能,说“很简单,就是加个筛选条件”。按表面看,这只是加一个下拉框;但要真正落地,还牵扯到权限模型、数据权限隔离、导出开关、默认值逻辑。我在评审会上已经开始打鼓,嘴上却只说了句“我看看吧”。现在想起来,这句话就是雪崩前的那一声响。
3.1 每一个“举手之劳”,实际都是“三倍代价”
我后来用一个类比跟团队解释:在项目里加需求,就像在已经住人的房子里加一根新的下水管道。表面上看只是多一根管子,但其实每多一根,水流经过的支路就多一条,整体水压会变低,堵了也更难排查。代码里同样如此:每多一个分支条件,多一个兼容历史数据的判断,多一个按角色走不同逻辑的 if,都会让下一轮改动增加不可见的雷区。这就叫复杂度的指数陷阱,不是线性增长。
3.2 我让自己相信了“不做会伤关系”
不敢拒绝需求的深层原因,是我潜意识里觉得说“不”会显得自己没能力、不配合。于是需求方提一个我就收一个,四个被收下的需求组合在一起,让原有核心流程多了一倍测试路径,预算却没有增加一个字。这个坑希望后来者直接避开:不是所有需求都要接,也不是所有需求都要当场拒绝,而是接之前先谈代价。不需要用“不行”回绝,只要平静地把代价摆出来:“要做这个需要新增两张表、改动权限模块,预计影响上线时间一周。”把完整画面交给对方决策,关系不会坏,项目反而能保住。
4. 挂在墙上一整年的重构清单:技术债如何变成技术破产
带崩的过程中,我对技术债的判断也错得离谱。我始终有一种心态:先把功能做出来,等这一轮上线后再回来清理。结果清理永远排在“下一块更急的功能”后面,接口设计混乱的模块成了整个项目最被恐惧的地方,最后连我自己都开始绕着走。
4.1 我亲手埋下了不止十处“隐藏的重复逻辑”
举一个不夸张的例子:一个简单的权限校验逻辑,当时赶进度,我没有抽成公共方法,让两个后端各自复制了一份。一开始没事,第二次需求变更要加新角色,我不得不把所有复制过的地方找出来。找了八处,改了五处,漏了三处。漏掉的地方在测试环境没暴露,上线第三天用户报了越权问题。那次事故之后,我再也不信“复制一下很快”这句话。复制粘贴是最快的写法,也是最贵的写法。
4.2 “扑火时刻”和“建设时刻”比例失衡,项目就死了
后来我重新看了团队一个月的时间分配,真正用于新增能力的只有三成,另外七成都耗在历史代码带来的连锁问题上。技术债如果没有排进迭代,它未来一定会以更粗暴的方式回来,形态往往是线上故障、加班、需求方再次质疑。当你发现修旧功能的时间比做新功能的时间还长,项目其实已经不在“难做”的阶段了,而是进入了技术破产通道。所以我的建议不是“彻底清债”,而是每个迭代固定拿 10% 到 20% 的时间处理最痛的债务,并配上回归测试。这个动作只要坚持,就能极大减缓崩溃曲线。
5. 我亲手把自己变成项目唯一的按钮
如果只是技术债和需求蔓延,项目还不至于彻底崩,最多是延迟和压力变大。真正让我感到无力的是,我在项目中期不知不觉把自己变成了所有关键路径上的唯一节点。这个过程不是某一天发生的,它是无数个“先自己搞定”堆出来的。
5.1 我把关键知识全部放在自己脑子里,团队离了我不能运转
从“测试环境只有我会搭”开始,到“这条配置只有我知道原因”,再到“这个服务只有我会部署”,每一步都让我变成交付链路上不可替代的一环。刚开始我还觉得这代表自己重要,后来其他人想帮忙只能站着看:发布文档是过期的,环境地址记在我手机备忘录里,权限变更要等我问一句才知道找谁。结果所有求助都涌向我,我一边救业务问题,一边回答协作问题,真正该做整体管理的时间反而所剩无几。
业界的叫法是“总线因素”:如果只有一个节点拥有关键信息,节点一旦过载或离开,整个系统就停摆。我在那五个月里甚至不敢请假,因为只要我离开,当天连测试包都没人能发。这本身就是带崩项目的直接原因之一。
5.2 我把“独占”误当成“掌控”,其实只是放大了脆弱
后来我学到一条衡量标准:如果明天我突然不能参与项目,团队能不能独立完成接下来两周的工作?如果能,说明我的参与方式是健康的;如果不能,说明我制造了一个特别易碎的结构。要让这种可替代性成立,必须写文档、做交叉评审、把部署步骤脚本化。这些事情确实会占用时间,但它们是在把时间从“救火”挪到“消防”,长期来看是回报最高的投资。
6. 每次预警都被我用“下周再说”吞掉了
写到这里我终于想承认,带崩项目不是我完全察觉不到问题,而是我每次察觉后都选择了最偷懒的回应方式:“下周再说”。到了下周,又被更紧急的事项盖过,预警继续堆积。到最后我发现自己一直在向项目投放“昏睡剂”。
6.1 “下周再说”是我最常用的麻醉剂
印象最深的三件事:自动化测试一直没建,每次手动回归两小时;联调环境不稳定,崩溃原因一直没定位;需求变更记录不完整,后期无法追溯。三件事单拎出来都不致命,但它们总被“先做新功能”挤到一边。结果后期,两小时手动回归变成半天都不够用,联调环境问题直接堵住整个团队。我们明明每个问题都看见了,却放任它长到无法收拾。现在我会强制自己遵守一个规则:发现问题当天标记,排进 72 小时内处理,而不是丢进“未来清单”。没有时间限定的修补计划,等于没有计划。
6.2 我把每周例会开成了“进度汇报会”,而不是“风险预警会”
每周例会我都在讲这周完成了什么、下周准备做什么,却忽略了一个关键议题:有哪些地方可能翻车。后来我调整了开会方式,留出固定时间让每个人讲“最担心的问题”,并把它们变成行动项,同时记录对应缓解时间点。如果一个风险讨论完没有负责人和截止日期,那它就是一颗倒计时炸弹。例会不是为了证明项目还在动,而是为了提前暴露不让它翻车的东西。
7. 重开这个项目,我一定改掉的四个带崩习惯
复盘到最后不能只是一份忏悔。这份文档后来变成了我一直在用的管理原则。如果真有一个平行时空让我重带这个项目,我会从第一天开始做下面四件事。
7.1 用“可验证的完成”代替“感觉差不多”
第一个迭代就要建立完成标准:代码合入主干、有覆盖测试、文档更新、冒烟测试通过。四样齐全才算一个需求做完。没有这套标准,每个人都觉得自己的部分是“差不多”的,最后拼接口时到处是缝隙。完成标准不一定要很重,哪怕只是简单的四格 check 清单,都能把项目收尾阶段的意外减少一大半。
7.2 把“拒绝需求”做成正式流程,而不是看心情
需求可以加,但要经过完整评估:影响范围、工作量、延迟风险、备份方案。评估结果显示会推后当前发布,就放到下一迭代。这套流程让需求方有选择,也让团队不用为面子接单。后来我把需求卡片拆成了三种:当前迭代承诺、待评估池、明确不做。第三类同样重要,因为“明确不做”能减少大量隐性讨论成本。没有边界的项目,最后一定被边界反噬。
7.3 定期练习“我会消失两周”的备选方案
挑一个重要模块,安排做技术交底,让另一个同事掌握全貌,然后由那个人负责一次发布。这个动作不是为了找替补,而是验证团队是否具备连续交付的能力。如果一个项目只能靠一个人往前推,规模越大,崩溃概率越高。同时,每一次我来交底文档,也是逼我把自己脑子里那些“经验”变成可复制内容的过程。
7.4 每个迭代留出至少 20% 的“处理意外”配额
这 20% 不是用来摸鱼,而是固定用来还技术债、修环境、接临时支持。它会让迭代的可见交付显得少一些,但能让整个交付不确定性降到可控范围。带项目最大的风险从来不是工作量太大,而是你不知道还会冒出来多少意外。缓冲就是给这些意外留下的生存空间。
我后来重新带过三个项目,都按期上线了。能力没有突变,变化的是我终于明白:项目能不能活下来,靠的是把确定性带进日常决策的系统,而不是有人变成能力爆棚的救火队长。真正能救一个项目的时间窗口,往往是你觉得“好像还能再拖”的那一周。早点动手,别等到复盘文档只剩忏悔。