拓冰建站拓冰建站
首页 / 资讯中心 / 正文

重蹈覆辙还是有效复用?工程中重复建设的本质与破局之道

“我们的结局像沙漏一般重蹈覆辙……”这句话像一句歌词也像一段代码注释。真正做过项目的人大概都能从这句话里读出另一层含义迭代了几轮的代码库最终因为某次大规模重构的失败被推翻重来或者更常见的是两年前用过的那个方案换了个新项目又重新踩了一遍一模一样的坑。技术圈很少把这种事叫做“重蹈覆辙”。我们叫它“历史包袱”叫“前人挖坑后人埋”叫“这次一定要写成可维护的架构”然后三周后又开始默默改 call 函数。如果你也有过这种“又来了”的感觉这篇文章就是写给同一个境遇里的开发者的。我们不动情地把沙漏的意象翻过来看它其实是一个极其精确的系统——上层的沙粒是存量逻辑下层的沙粒是新增需求瓶颈处的流量控制决定了整个流程的速度和稳定性。而“重蹈覆辙”往好里说无非是每次都在同一处漏沙却一直没绕开那个瓶颈。这篇文章想聊的不是怎么避免一切重复而是想聊清楚为什么我们会反复重建同一套东西反复踩同一个坑以及把沙漏翻过来这种动作在工程上到底意味着什么。1. 先看清“重蹈覆辙”是什么层面的问题1.1 不是所有重复都值得批判很多人一听到“重蹈覆辙”第一反应是管理问题或执行力问题。但站在工程视角重复分很多种。有一种重复是好的两个业务模块部署在同一套基座系统上它们各自实现各自的鉴权、各自的日志、各自的限流策略。这从代码视觉上确实“重复”了但从架构上看这种隔离恰恰是合理的。因为它避免了强耦合让一个模块的异常不会拖垮另一个模块。有一种重复是中性的比如你从一个旧项目里复制了一段通用的工具函数改成适合新项目的参数结构。这不算坏甚至可以说是最高效的复用方式只要你能保证这段代码真的被测试覆盖过。还有一种重复是坏的也是这篇文章想聊的核心——流程性重建。典型特征就是你发现某个问题、某个需求明明之前遇到过也梳理过方案但因为没有把经验和产出物沉淀到工作流里导致下一次遇到同样问题时又得重新从“这个报错是什么意思”开始排查。所以“重蹈覆辙”的第一层真相是问题不在于重复本身而在于我们分不清楚“什么值得重复”和“什么不应该重复”。1.2 技术团队最容易重复的三类工作从常见实践经验来看团队反复重建的东西其实高度集中在三类第一类是基础设施型工作。比如日志采集、链路追踪、权限模型、消息队列的接入、配置文件的管理方式。这些东西对于一个稳定项目来说是地基但很多团队每换一次项目语言或框架就会把之前沉淀的规范丢掉然后重新发明一套半生不熟的方案。第二类是业务中台能力。更准确地说是那些被跨项目重复调用的业务逻辑比如订单状态机、支付回调处理、用户权益计算。一个成熟团队一定会把这些逻辑收敛到共享模块里而刚起步的团队很容易把同一套逻辑复制到五个项目里等需求变更时五个地方各改一遍最后必然出一次线上事故。第三类是文档和知识。这不是代码层面的重复是认知层面的重复每次都要新人重新研究“为什么当前配置的是这个值”每次都要成员重新摸索“部署流程中哪一步容易失败”。从个人角度来说避免重复的重心是建立自己的工具箱从团队角度来说避免重复的重心则是一个“可复用的知识和流程层”。2. 为什么我们总会回到同一条老路2.1 单次跑通和能力沉淀是两件事“重蹈覆辙”最典型的触发点就是“单次成功”带来的假象。你花一个通宵把一个临时脚本跑通了功能正常结果正确你以为这件事就结束了。但回到工程视角真正能被称为“沉淀”的是下面这些东西你的输入数据是什么格式边界条件有哪些你的脚本对特定目录结构有没有依赖如果有一次运行中间断了重跑是否幂等你的日志能不能定位到具体节点你的环境变量是否被硬编码了。这些没有记录那这个脚本本质上就只是“一次性的成功”。下次遇到类似场景大概率你还是会重新分析、重新踩坑、重新试错——这就是流程性重蹈覆辙的典型链路。从工程实践看有一个判断标准特别实用如果你把上次的方案原封不动地交给另一个人包括未来的自己去执行他能不能在不用追问你的情况下顺利跑通如果不能说明你还没完成沉淀只是完成了一次成功运行。2.2 路径依赖比技术选型更危险很多重复不是能力不足而是路径依赖。简单说就是上次用什么方案解决了问题这次就倾向于继续用哪怕问题已经不完全一样了。这种路径依赖在技术选型上的表现格外明显。比如之前用某个组件管理定时任务跑得挺好新项目的任务量级和调度模式其实已经发生了变化但因为没有花时间做评估就直接复制了旧项目的配置和代码结构。等到并发量上来才发现这个组件的调度策略不适合当前业务还要临时迁移。还有一个常见情况是“复制示例代码时把隐含假设一起复制了”。官方文档给出的示例往往只针对最简单的场景比如单机部署、默认配置、小数据量。但在实际项目里可能涉及多租户、跨地域、合规要求直接把示例代码拿过来改改就跑后面埋的问题几乎是一定的。这也解释了为什么那么多人反复栽在同一个问题上却总觉得是“运气不好”因为每次解决问题时只是解决了一个表面现象没有校正决策路径本身。2.3 人总是高估记忆的可靠性有一类重蹈覆辙是记忆层面的。几个月前排过一个很隐蔽的环境配置问题当时解决了但没有写下来。几个月后遇到相似的问题第一反应却不是按经验走而是又从头猜测可能的原因。这一类最冤枉因为明明已经付出过成本去探索却因为记录不及时让探索成本全部白费。从知识管理的角度看最好用的原则其实很朴素把“当时的解法”和“当时为什么绕了远路”一起写下来。前者解决的是“怎么修”后者解决的是“下次怎么不修”。只记录前者你最多是缩短一次排障时间记录了后者你才有机会彻底避开这条路。3. 把“翻箱倒柜”改成“开箱即用”一套可复用的判断框架3.1 判断“这个坑值不值得踩”的三个信号面对每一次重复劳动先别急着动手。可以先问三个问题第一个信号是出现频次。如果这件事超过两次了那它就值得被抽象成工具、脚本或文档。一次是特例两次是巧合三次就是系统性问题。第二个信号是知识组分。换句话说这件事能不能被标准化打个比方排查一个线上接口超时的问题虽然每次具体原因不一样但排查步骤是高度可标准化的——先看日志、再看监控、再看资源占用、再看依赖超时。这类“可以被标准化”的重复正是沉淀的好对象。第三个信号是知识可迁移性。比如你学会了一种日志检索的语法换一个项目换一个日志平台可能就失效了。但如果你总结的是“如何从入口到出口按顺序追排查链路”这个方法论换到任何项目里都受用。判断优先级时优先沉淀可迁移的方法论其次才是具体的操作命令。这套信号不是为了让所有操作都自动化而是帮你在动手前先意识到这件事大概率还会发生现在多花半小时整理比下周再花三小时排查更划算。3.2 用“复盘-模板-手册”三个层次沉淀经验如果决定要沉淀具体应该沉到什么程度我的建议是分三层。第一层叫“复盘层”。这是最轻量的做法。每次解决完一个稍微超出日常经验的问题当场花十分钟把以下条目写下来现象是什么排查链路是什么根因是什么验证方式是什么有没有反直觉的地方。不需要做成很漂亮的文档只需要写在一个可以全局搜索的地方比如云笔记或者仓库的 wiki 目录。这一层的核心价值是“让未来的自己有的可查”。第二层叫“模板层”。当你发现某个复杂流程已经反复出现就把流程血肉化成一个模板。模板不一定是代码脚手架也可能是一份部署检查单、一次评审的提问列表、一个配置文件的注释规范。比如之前做过一次组件升级过程特别曲折升级完了把所有步骤整理成一个标准的升级清单下次同类组件升级时直接照着清单一项一项打勾验证。这层就避免了重蹈覆辙因为每一步都被显性化了。第三层叫“手册层”。到了这一层已经不只是给自己的记录而是面向团队或开源社区的知识产品。它需要覆盖背景、适用边界、最小示例、常见错误、排查路径和验证方式。这通常对应一个比较成熟的模块、一个完整的工具链或者一种从零到一推进项目的方案。区分这三层的意义在于不是所有经验都必须做成完整手册。轻量级经验做成复盘就够了频繁使用的流程才值得模板化复杂且覆盖面广的模块才需要手册化。如果一开始就想写一本厚厚的 WIKI大概率坚持不下去最后什么也沉淀不下来。4. 从“重蹈覆辙”到“有意识地重来”的四个习惯4.1 建立“可续跑”的任务习惯开发过程中有一种最隐蔽的浪费叫“任务不可续跑”。比如你做了一半的代码迁移中间因为会议或其他事情中断了等到第二天回来你需要花很长时间回忆当时改到哪一步、有哪些改动还没完成、有没有遗漏的测试用例。“可续跑”的任务习惯本质上就是给未来的自己留一条清晰的上下文。具体做法很朴素每个任务启动时先写一个简短的 TODO标出当前进度和下一步动作所有执行命令都尽量写成可重复执行的脚本或命令序列而不是留在 shell 历史里的随机指令有环境相关的细节直接写在代码仓库的 README 或 docs 目录里不要写在聊天记录里。这个习惯成本极低但能把“中断—恢复”的成本从几十分钟压缩到几分钟也直接降低了“重蹈覆辙”的概率——因为你每次回来都能清楚地知道上下文而不是靠记忆拼凑。4.2 用“一开始就写文档”代替“最后补文档”很多人不喜欢写文档因为文档是“输出”而且写完没人看。但这里有一个反直觉的经验文档最大的价值不是给别人看而是迫使你在动手前把方案想清楚。有一个我很推荐的写法叫“先写 README 后写代码”。当你准备开始一个功能模块的时候先写下这个模块的输入是什么、输出是什么、它和外部系统有哪些交互、有哪些环境假设、遇到异常时应该怎么处理。这些文字不一定要很长但在这个过程中你会立刻发现很多细节其实还没有想清楚。这就是把重构从“推翻重来”变成“有意识增量”的关键动作之一。因为很多重蹈覆辙是“做着做着发现自己掉进了一个熟悉但错误的方向”。如果从一开始就有文档指明边界这种掉头的概率会显著下降。4.3 不要用“热心”代替“机制”团队里往往有两种人一种人遇到问题马上冲过去修好另一种人会花时间把流程改好。前者的热心值得珍惜但真诚地说如果只有前者团队会一直处于“重复救火”的模式。机制怎么建立不一定是要上一个复杂的管理系统。可以简单到每次迭代评审加一条固定问题“这次有没有需要沉淀到公共模块或共享文档里的内容”如果没有就直接过如果有就分配给一个人。这条问题本身就会把“避免重复”的压力从个人自觉变成流程固定动作。从个人层面也一样。与其完全依赖“这次我记性好”不如预设一个收尾动作每个需求提测前检查一下有没有复制粘贴过的逻辑、有没有被重复使用的魔法数字、有没有改了 A 处但忘了 B 处的隐患。4.4 容忍“看起来很笨”的复用还有一个容易忽略的点很多重蹈覆辙并不是因为没有复用方案而是觉得复用的成本太高不如自己写。比如你发现仓库里已经有一个工具函数但它的参数设计得比较绕看起来不是特别适合你的场景。这时你的第一反应往往是“改一个吧”或者“自己写一个更直接的”。听上去很高效但实际落地时会发现一旦你绕开了原有的函数你的新代码就成了一座孤岛后续维护需要同时理解两套逻辑。我的建议是在项目中只要有现成的约定就尽量先按约定来。哪怕多写几行包装代码只要不破坏整体的一致性就可以接受。因为工程上的长期价值从来不在于某个局部的最优解而在于整体结构的可理解和可维护。为此多付出的那点时间本质上是在买“不重蹈覆辙”的保险。5. 当重蹈覆辙真的发生时怎么快速收敛5.1 先定位再修不要边猜边改不管戒掉的习惯多么完备总有掉回坑里的时刻。这时候最怕的不是错误本身而是“边猜边改”的节奏改一个参数跑一下不行再改一个参数再跑一下还不行……这种无头苍蝇式的循环本质就是最昂贵的重蹈覆辙。收敛的步骤其实非常固定第一步把现象描述清楚。出错的输入是什么运行环境是什么期望输出是什么实际输出是什么这一步能过滤掉一半的“伪相关因素”。第二步顺着链路逐层排查。从最外层参数配置、网络、权限到最内层业务逻辑、数据类型一层层收窄范围。不要跳过你自认为绝对没问题的环节——经验越充足的人越容易跳过恰恰越容易漏掉。第三步构造最小复现。如果条件允许把一个 LARGE 的数据集缩减到最小可复现范围。这一步不是为了复现一个错误而是为了验证你对根因的判断。第四步修复后补一个回归用例。尤其是那些“不修复就会再次踩中”的问题回归用例就是封印。这套收敛路径能起作用的原因在于它把“我又重蹈覆辙了”的挫败感转换成了“我按照既定流程找到了根因”的控制感从而避免进入情绪化的盲试状态。5.2 一条可复用的排查链路模板针对技术项目你可以把排查路径理解为下面这样一张通用地图层优先检查项常见症状输入层数据格式、编码、路径、字段名字段不存在、类型错误、中文乱码、路径不存在环境层依赖版本、系统变量、端口、权限启动失败、连接拒绝、缺库缺包逻辑层分支条件、循环边界、幂等性结果不符合预期、部分数据重复处理、偶发错误交互层外部接口超时、返回结构变动、限流超时、空指针、响应失败资源层CPU、内存、磁盘、连接数慢、卡、OOM、连接池耗尽每次遇到莫名其妙的故障都按这个顺序从下往上快速过一遍。它不保证你一定在第一次就找到根因但能极大降低你在错误的方向上反复试探的概率。在实际落地中我习惯在项目里写一份troubleshooting.md把最常见的三类问题环境类、依赖类、逻辑类对号入座地列清楚附上已经排查过哪些项、结论是什么。这样下一次同类问题再来你甚至不需要从零推理只需要阅读旧记录就能定位。6. 把“重蹈覆辙”翻译成工程信号而不是情绪信号回到开头那句话。如果“我们的结局像沙漏一般重蹈覆辙……”是产品中一个用户的故事我们或许会为稳定性的下降感到遗憾但如果这是一个开发团队的真实写照那它更像是一个系统发出的警告信号。沙漏的结构其实很诚实所有沙子最终都会流向同一个出口除非你改变瓶颈的宽度。对应的工程经验就是只要流程的瓶颈不变你做一百次局部优化最终结局大概率还是卡在同一处。所以当“重蹈覆辙”发生时我的态度是先别急着自责也别急着把责任推给“队友又改了配置”或者“需求又变了”。把事情当作一个中性信号来看——它说明当前系统的某个环节缺少足够的抗重复机制。可能是文档缺失可能是流程没固化可能是工具不顺手也可能是依赖选择不合理。定位到那一处瓶颈比在情绪上反思“我怎么又这样了”有价值得多。从今天开始如果你只做一件事我建议是给刚做完的、稍微有点波折的任务补一份极简的复盘笔记。不用长三五条即可。就写“现象、根因、验证、下一次可以不怎样做”。一年以后你再回看这份笔记会发现自己已经悄悄绕开了很多曾经的“重蹈覆辙”。那时的沙漏还会在倒流但流经的不再是同一个阻塞点。这才是工程意义上的“翻篇”。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门