代码质量、重构与模块设计
把可读性、可修改性、模块边界、代码评审、重构节奏和自动化护栏接成日常代码库治理闭环
阅读边界: 这一页不讲大规模架构迁移,也不把“代码风格”误当成代码质量。它关注的是日常代码层的可维护性:模块是否有边界,改动是否局部,重构是否可验证,代码评审是否能识别风险,自动化护栏是否能防止复杂度回潮。
降低认知负担
收窄改动半径
保护模块边界
支撑安全重构
提升交付信心
| 表面问题 | 更深层原因 | 代码质量判断 |
| 代码看不懂 | 命名、抽象、控制流和业务语义没有把意图表达出来 | 读者需要在脑子里模拟太多上下文,说明认知负担过高。 |
| 小改动牵一片 | 模块边界不清、共享状态过多、调用方向混乱 | 质量问题已经从局部可读性扩散到可修改性。 |
| 不敢重构 | 测试缺失、评审只看功能、缺少回归信号 | 代码库没有给修改者提供足够反馈和安全网。 |
| 代码评审总是争风格 | 团队没有共同的质量标准和风险语言 | 评审应围绕边界、复杂度、可测试性和运行风险,而不是偏好争论。 |
| 层级 | 关键问题 | 健康信号 | 坏味道 |
| 表达层 | 命名、函数长度、控制流、注释是否帮助理解 | 读者不用追太多上下文就能知道意图 | 神秘缩写、反复嵌套、注释复述代码。 |
| 模块层 | 职责是否单一,边界是否稳定,接口是否吞掉复杂度 | 调用方只需要知道少量概念 | 万能 Service、参数对象膨胀、内部状态外泄。 |
| 依赖层 | 调用方向、共享库、公共工具和横切逻辑是否受控 | 依赖图有方向,核心域不被外围细节污染 | 循环依赖、工具类泛滥、跨层直连。 |
| 测试层 | 修改是否有快速反馈,重构是否能被保护 | 单测、契约测试和关键回归能覆盖主要风险 | 只能靠人工点测,测试脆弱又慢。 |
| 演进层 | 代码库能否持续吸收新需求而不失控 | 重构常态化,小步改善能进入日常交付 | 每次都说以后统一重构,但从不发生。 |
深模块
- 少量接口承接足够多内部复杂度。
- 调用方少知道内部流程、状态和特殊情况。
- 不是“大模块”,而是“好用且能隐藏复杂度的模块”。
稳定边界
- 边界应该围绕变化原因和业务语义,而不是文件夹喜好。
- 经常一起变的东西可以靠近,经常独立变的东西应该隔开。
信息隐藏
- 缓存策略、重试细节、状态机、数据结构不应随意泄漏。
- 接口暴露越多,未来演进越难。
依赖方向
- 核心业务不应依赖外围实现细节。
- 依赖倒置、端口适配器和反腐层都是为了保护核心边界。
| 重构类型 | 适合场景 | 最小安全网 | 退出条件 |
| 整理表达 | 命名混乱、函数过长、重复分支 | 单测或局部回归 | 读者能更快理解意图,行为不变。 |
| 抽取模块 | 职责混杂、复用逻辑散落 | 调用方回归 + 接口测试 | 调用方知识减少,模块边界更清楚。 |
| 收敛特例 | if/else 到处补丁,异常路径散落 | 场景用例表 + 回归测试 | 特例被收进少数策略点。 |
| 隔离依赖 | 外部 API、数据库、消息和第三方逻辑侵入核心代码 | 契约测试 + mock / fake | 核心逻辑能脱离外部细节测试。 |
| 渐进替换 | 旧模块难改但不能停服重写 | 双轨验证、灰度、监控 | 旧路径可逐步下线,新路径行为可信。 |
| 评审维度 | 关键问题 | 不够好的评审方式 |
| 行为正确性 | 核心路径、边界条件、失败路径是否被覆盖 | 只看 happy path 是否能跑。 |
| 改动半径 | 为什么要改这些文件,是否有无关扩散 | 只看 diff 是否小,不问改动是否局部。 |
| 模块边界 | 新增接口是否暴露了不该暴露的内部细节 | 只看代码风格,不看依赖方向。 |
| 可测试性 | 这段逻辑能否被快速、稳定、局部验证 | 接受“太难测了”的解释。 |
| 运行风险 | 是否影响性能、并发、数据、权限或发布回滚 | 只看编译和单测通过。 |
格式化 / Lint
处理低价值风格争论,把评审注意力留给边界、行为和风险。
类型检查
让接口变更、空值、数据结构不匹配在更早阶段暴露。
复杂度规则
圈复杂度、函数长度、依赖方向和重复率不是最终答案,但能提供预警。
架构适应度函数
把“某层不能依赖某层”“核心域不能直连外部 SDK”等规则自动化。
测试分层
用单测保护局部逻辑,用契约测试保护边界,用少量 E2E 保护关键旅程。
质量趋势
关注复杂度、失败率、构建时长、 flaky 测试和重构频率的长期趋势。
| 坏味道 | 真正风险 | 优先处理方式 |
| 万能 Service | 职责混杂,任何需求都往同一个类里堆 | 按业务能力和变化原因拆出边界。 |
| 参数泛滥 | 调用方必须理解太多内部开关 | 收敛成明确命令对象、策略对象或更高层 API。 |
| 工具类泛滥 | 公共逻辑没有业务归属,依赖会越来越脏 | 把通用能力和领域能力分开,避免所有东西进 Utils。 |
| 复制粘贴业务分支 | 修一个漏一个,规则漂移 | 先统一规则表达,再抽取复用点。 |
| 隐式状态 | 读代码看不出前置条件和状态转移 | 显式建模状态机、生命周期和不变量。 |
| 测试只能端到端验证 | 反馈慢,重构成本高 | 把核心逻辑从框架和 IO 中剥离出来做局部测试。 |
| 重构准入字段 | 要写清楚什么 | 缺失后的风险 |
| 触发原因 | 是为了支撑当前需求、修复缺陷、降低重复、隔离依赖,还是为后续迁移铺路;不要只写“顺手优化”。 | 重构变成无边界的大扫除,范围越来越膨胀,业务交付被拖住。 |
| 行为边界 | 哪些外部可见行为必须保持不变,哪些行为允许被明确修正,哪些接口和数据结构不能动。 | 评审无法区分“行为等价重构”和“顺便改逻辑”,回归风险被藏进结构调整里。 |
| 改动半径 | 涉及的模块、调用方、测试、配置、数据迁移和发布路径;说明为什么这些文件必须一起改。 | 小重构变成跨层改造,问题出现后无法快速定位责任边界。 |
| 安全网 | 现有测试、新增测试、契约样例、快照、灰度指标、人工验收脚本和回滚方式。 | 只能靠“我看过了”证明正确,下一次修改者仍然不敢动。 |
| 退出条件 | 复杂度下降、重复收敛、接口减少、依赖方向修正、测试反馈变快或调用方知识减少。 | 重构没有完成定义,最后只留下新的抽象和新的债。 |
| 模块边界证据 | 最低要求 | 评审时追问 |
| 职责声明 | 能用一句话说明模块负责什么、不负责什么,以及最主要的变化原因。 | 如果下个需求来了,应该改这里还是改别处?为什么? |
| 公开接口 | 接口数量少,概念稳定,调用方不需要知道内部状态机、缓存、重试或第三方细节。 | 这个参数或返回值是在表达业务语义,还是把内部实现泄漏给调用方? |
| 依赖方向 | 核心业务不直接依赖外围框架、数据库、消息 SDK 或第三方 API,跨层调用有适配层。 | 如果外部实现替换,核心逻辑需要改多少? |
| 测试形态 | 核心规则可脱离 IO 做局部测试,边界用契约或集成测试保护,少量 E2E 覆盖关键旅程。 | 这个模块坏了时,最快哪个测试会失败? |
| 演进记录 | 重要边界调整有 PR 说明、ADR 或维护记录,后续人能知道为什么这样切分。 | 半年后新人能否从记录里理解这个边界,而不是靠问老同事? |
| 相关页 | 连接关系 |
| 《软件设计的哲学》 | 提供复杂度、深模块、信息隐藏和战略编程的核心判断。 |
| 架构偿债 | 当代码质量问题上升到边界、数据、契约和交付层,就进入架构债范围。 |
| 测试与质量工程 | 重构能否持续发生,取决于测试反馈和质量门禁是否可靠。 |
| 开发者体验 | 代码质量会直接影响开发内循环、修改信心、onboarding 和评审效率。 |
| 架构决策 / 评审 | 当代码层改动形成长期结构承诺,应进入 ADR 或架构评审。 |
| 性能工程 | 有些“代码质量优化”会影响性能,必须用画像和基线验证。 |
| 误区 | 为什么危险 | 修正方式 |
| 代码质量就是格式统一 | 格式只是最低层,真正质量体现在可理解、可修改、可验证 | 格式自动化,评审重点转向边界和风险。 |
| 重构必须等业务空窗期 | 空窗期很少出现,债会继续扩大 | 把小步重构嵌入日常需求和缺陷修复。 |
| 抽象越多越高级 | 错误抽象会制造更高认知负担 | 用调用方知识减少程度判断抽象价值。 |
| 代码评审越严格越好 | 没有标准的严格会变成偏好争论和交付阻塞 | 明确评审清单,把自动化能做的交给工具。 |
| 检查项 | 最低要求 |
| 模块边界 | 能说清这个模块负责什么、不负责什么、调用方不需要知道什么。 |
| 改动半径 | 常见需求能局部修改,不需要跨很多无关模块。 |
| 测试反馈 | 核心逻辑有快速反馈,重构后不完全依赖人工验收。 |
| 评审标准 | 团队对命名、边界、复杂度、测试和运行风险有共同清单。 |
| 持续偿债 | 每次需求允许带走一小块复杂度,而不是只新增功能。 |