[{"data":1,"prerenderedAt":261},["ShallowReactive",2],{"\u002Fblog\u002Frefactor-without-stopping":3},{"id":4,"title":5,"author":6,"body":7,"category":250,"date":251,"description":252,"extension":253,"headline":6,"image":6,"meta":254,"minRead":255,"navigation":256,"path":257,"seo":258,"stem":259,"__hash__":260},"blog\u002Fblog\u002Frefactor-without-stopping.md","重构存量系统：不要停下来换引擎",null,{"type":8,"value":9,"toc":239},"minimark",[10,14,17,20,27,31,34,37,53,56,59,62,65,68,71,74,101,104,107,110,113,116,127,130,133,136,139,156,159,162,165,168,185,188,194,197,200,217,220,223,227,230,233,236],[11,12,13],"p",{},"工程师很容易被“重写”吸引。",[11,15,16],{},"旧代码缺少边界，页面依赖 jQuery，接口返回不一致，改一个字段会影响三个系统。面对这样的代码库，重新选一套技术、从空目录开始，感觉既干净又高效。",[11,18,19],{},"但长期运行的业务系统通常不能停下来等你换引擎。它每天有预约、支付、报告和运营人员在使用，重构必须与需求交付同时发生。",[11,21,22,23],{},"我曾参与一个长期运行的医疗预约产品线：后端持续使用 Laravel，诊所后台从 jQuery 逐步迁移到 Vue 2，后来再演进到 Nuxt 2。这个过程让我确认了一件事：",[24,25,26],"strong",{},"存量系统重构的核心，不是替换技术，而是逐步重建边界。",[28,29,30],"h2",{"id":30},"先理解系统为什么变成现在这样",[11,32,33],{},"旧代码并不一定来自糟糕的工程能力。它往往是多年的业务压力、人员变化和当时技术条件共同留下的结果。",[11,35,36],{},"在修改之前，我会先画出四类关系：",[38,39,40,44,47,50],"ul",{},[41,42,43],"li",{},"哪些模块直接创造业务价值；",[41,45,46],{},"哪些数据被多个系统共同读写；",[41,48,49],{},"哪些流程与外部平台强绑定；",[41,51,52],{},"哪些代码虽然难看，却已经稳定运行多年。",[11,54,55],{},"最后一类尤其重要。重构不是以“代码是否优雅”排序，而应该综合考虑变更频率、故障影响和维护成本。",[11,57,58],{},"一个半年不会变化的旧页面，即使内部不够漂亮，也可能不值得优先处理。相反，一段每周都要修改、每次都容易出错的订单逻辑，才是应该先建立边界的地方。",[28,60,61],{"id":61},"不要同时改变所有变量",[11,63,64],{},"一次大重写通常同时改变语言、框架、数据模型、接口和部署方式。任何问题出现时，团队很难判断究竟是哪一层造成的。",[11,66,67],{},"渐进式重构要刻意控制变量。",[11,69,70],{},"以页面迁移为例，可以先保持后端接口和部署不变，只把一个高频模块从 jQuery 改为 Vue；等组件结构与状态管理稳定后，再统一接口；最后才考虑路由和服务端渲染框架。",[11,72,73],{},"我常用的顺序是：",[75,76,77,83,89,95],"ol",{},[41,78,79,82],{},[24,80,81],{},"先包住变化","：为最混乱的外部调用加适配层；",[41,84,85,88],{},[24,86,87],{},"再明确契约","：稳定请求参数、响应结构和错误语义；",[41,90,91,94],{},[24,92,93],{},"然后迁移页面","：新需求只写在新结构中，旧页面按价值逐步替换；",[41,96,97,100],{},[24,98,99],{},"最后清理旧入口","：确认流量和功能都已迁移，再删除旧实现。",[11,102,103],{},"这不是最快的写法，但它让每一步都可以验证、回退，也让业务持续运行。",[28,105,106],{"id":106},"用新需求支付重构成本",[11,108,109],{},"单独申请几个月“只做重构”，在大多数业务团队里并不现实。更可行的方式，是把重构和真实需求绑定。",[11,111,112],{},"如果下一个需求要改预约表单，就顺便把表单校验从散落的 DOM 操作收进组件；如果要增加新的报告类型，就先抽出报告生成的任务接口；如果要接入一个新客户端，就借此统一已有 API 的认证和错误码。",[11,114,115],{},"这个策略有三个好处：",[38,117,118,121,124],{},[41,119,120],{},"每次重构都有明确业务收益；",[41,122,123],{},"新边界会立即接受真实场景检验；",[41,125,126],{},"团队不需要维护一套“重构中的理想系统”和一套“仍在赚钱的旧系统”。",[11,128,129],{},"当然，不能借每个小需求无限扩大范围。一次迭代只改善与当前变化直接相关的边界，其余问题记录下来，等待合适时机。",[28,131,132],{"id":132},"数据库是最后需要冲动的地方",[11,134,135],{},"前端页面可以逐页替换，数据库结构却连接着报表、脚本、异步任务和多个客户端。一张看起来命名不合理的表，背后可能有很多隐形依赖。",[11,137,138],{},"对于数据层，我更倾向于：",[38,140,141,144,147,150,153],{},[41,142,143],{},"先增加新字段，而不是立刻修改旧字段语义；",[41,145,146],{},"使用迁移脚本补齐历史数据；",[41,148,149],{},"在一段时间内同时读写新旧结构；",[41,151,152],{},"用校验任务比较两套数据结果；",[41,154,155],{},"确认所有消费者迁移后，再停止旧结构写入。",[11,157,158],{},"这是一种“扩展—迁移—收缩”的过程。它会产生短期重复，但把高风险的一次切换拆成了多个可观察步骤。",[28,160,161],{"id":161},"接口是最值得优先稳定的边界",[11,163,164],{},"当系统拥有 Web、管理后台和移动端时，接口质量会直接决定重构难度。",[11,166,167],{},"一个稳定的接口边界至少应该回答：",[38,169,170,173,176,179,182],{},[41,171,172],{},"字段是否有明确含义和单位；",[41,174,175],{},"缺失、空值与零是否能区分；",[41,177,178],{},"错误是否有机器可读的代码；",[41,180,181],{},"客户端是否需要理解数据库实现；",[41,183,184],{},"新字段加入时是否破坏旧客户端。",[11,186,187],{},"我不追求一开始就设计出永远不变的接口，而是让变化显式发生。必要时保持版本兼容，或者由服务端提供过渡字段，而不是要求所有客户端在同一天更新。",[189,190,191],"blockquote",{},[11,192,193],{},"存量系统里，好的架构不是从未改变，而是改变时不会把所有人绑在同一个发布窗口里。",[28,195,196],{"id":196},"每一步都要能证明更好了",[11,198,199],{},"“代码看起来更现代”不是充分的重构结果。每个阶段都需要可验证信号，例如：",[38,201,202,205,208,211,214],{},[41,203,204],{},"同类线上故障是否减少；",[41,206,207],{},"新功能需要修改的文件是否变少；",[41,209,210],{},"页面加载和交互性能是否改善；",[41,212,213],{},"新成员理解模块需要多久；",[41,215,216],{},"发布是否更小、更频繁、更容易回退。",[11,218,219],{},"自动化测试当然重要，但存量系统很难一开始就拥有完整覆盖。可以先保护最关键的业务规则，再增加接口契约测试、迁移校验和少量端到端流程。",[11,221,222],{},"测试也应该跟着边界一起生长。",[28,224,226],{"id":225},"接受重构没有完成的一天","接受重构没有“完成”的一天",[11,228,229],{},"长期产品不会在某一天宣布“技术债已经清零”。业务会继续变化，今天的新结构也可能成为未来的旧系统。",[11,231,232],{},"比彻底重写更重要的，是让团队形成持续改善的节奏：新代码遵循新的边界，高频旧模块随需求迁移，低价值区域保持稳定，对危险依赖保留清晰记录。",[11,234,235],{},"重构的目的不是获得一份完美代码，而是恢复团队安全改变系统的能力。",[11,237,238],{},"就像一辆仍在行驶的车，我们不需要停车换掉所有零件。我们需要知道先换哪一个、如何确认它工作，以及出现问题时怎样回到上一站。",{"title":240,"searchDepth":241,"depth":241,"links":242},"",2,[243,244,245,246,247,248,249],{"id":30,"depth":241,"text":30},{"id":61,"depth":241,"text":61},{"id":106,"depth":241,"text":106},{"id":132,"depth":241,"text":132},{"id":161,"depth":241,"text":161},{"id":196,"depth":241,"text":196},{"id":225,"depth":241,"text":226},"架构思考","2026-08-09","真正困难的重构，不是在新项目里选择更好的技术，而是在业务继续运行时，让旧系统一步步拥有新的边界。","md",{},8,true,"\u002Fblog\u002Frefactor-without-stopping",{"title":5,"description":252},"blog\u002Frefactor-without-stopping","WcRRfKNxzL_n31hy7qNmVGw61GrC1UKqj5VeL4uHk5o",1789624776302]