# 工程质量与重构 > 代码烂掉不是一次写坏的,是**每次「先这样,以后再改」**累积出来的。工程质量的核心指标只有一个:**改一个需求要付多少代价**。 ## 一、可维护性到底指什么 | 维度 | 好的状态 | 坏的状态 | | --- | --- | --- | | 认知负荷 | 新人一天能改一个小需求 | 改一行要读十个文件 | | 改动成本 | 改动集中在少数文件 | 一个小改要动五六个模块 | | 可验证性 | 有测试,改完能快速确认 | 只能靠手工点页面 | | 可回滚 | 发布可灰度、可回退 | 发布靠停机、回滚靠恢复备份 | | 一致性 | 同类问题一种写法 | 同一个功能三种实现 | 判断标准很朴素:**上一次需求,你改了几个文件、花了几小时、有没有把握?** ## 二、复杂度从哪来 1. **状态**:可变状态越多,组合爆炸越严重; 2. **耦合**:模块之间通过共享数据库、共享全局变量、隐式约定耦合; 3. **隐含约定**:代码没写、但「大家都知道」的规则(比如必须按某顺序调用); 4. **重复**:同一逻辑散落多处,改一处漏三处; 5. **过度抽象**:为了复用造了没人看得懂的框架。 减少复杂度的顺序是:**先消灭隐含约定(写进代码与文档),再解耦,再抽象**。反过来做,通常只是把复杂度搬家。 ## 三、重构:小步、有保护、可回滚 | 原则 | 做法 | | --- | --- | | 小步 | 每次只改一件事,提交可回滚;一次重构不超过半天工作量 | | 有保护 | 先补测试或特征开关,再动结构;没有验证手段的重构是赌博 | | 可回滚 | 灰度发布、双写/双读、开关随时关闭 | | 不停机 | 用绞杀者模式:新逻辑旁路接入,逐步切流量,最后删旧代码 | | 有终点 | 明确「重构完成」的标准,否则永远在路上 | **绞杀者模式**特别适合老系统:新实现与旧实现并行,按流量比例切换,出问题切回,稳定后删除旧路径。比「推翻重写」风险低一个数量级。 ## 四、代码评审看什么 评审时间有限,优先级应该是: 1. **正确性**:边界条件、并发、异常路径、空值; 2. **数据与兼容性**:数据库变更是否向后兼容、接口是否破坏调用方; 3. **可维护性**:命名是否表达意图、是否有隐含约定、是否重复; 4. **测试**:关键路径是否有测试,测试是否真的能失败; 5. **可观测性**:出错时能不能定位(日志、指标、错误码)。 风格问题交给格式化工具与 lint,不要在人身上消耗。 ## 五、技术债:量化,然后排序 技术债不是「代码不好看」,而是**未来要付利息的决策**。量化方式: - **改动频率**:这个模块每月改几次?(改得多的才值得重构) - **改动成本**:每次改要多久、要几个人、出过几次事故? - **影响面**:影响一条业务线还是全部? - 用「频率 × 成本 × 影响」排序,先还高息债。 不常改的代码,即使丑也可以不动;**重构要跟着业务价值走**。 ## 六、测试策略 | 层级 | 数量 | 关注 | | --- | --- | --- | | 单元测试 | 多 | 纯逻辑、边界、异常 | | 集成测试 | 中 | 数据库/缓存/消息的真实交互 | | 契约测试 | 适量 | 服务间接口兼容性 | | 端到端 | 少 | 关键业务链路,慢而脆弱 | 原则:**测试要能在出问题时失败**。覆盖率是参考,不是目标;一个 100% 覆盖但不断言的测试,只是执行了一遍代码。 ## 七、自检清单 - [ ] 我能说出当前系统最该重构的三个地方,以及理由(频率 × 成本 × 影响) - [ ] 我的重构有测试或开关保护,能随时回滚 - [ ] 数据库变更向后兼容,旧版本服务仍能运行 - [ ] 代码里没有「必须按顺序调用」这类隐含约定,或已写进文档与断言 - [ ] 同类问题只有一种写法,没有三套实现并存 - [ ] 评审时我更关注设计与正确性,而不是格式 - [ ] 我知道哪些技术债决定不还,以及为什么 - [ ] 新人能在一天内独立完成一个小需求