10 正交性
让组件的变化彼此独立,用边界、耦合证据和局部测试限制隐藏状态与时序假设的扩散。
学习目标
- 能把“10 正交性”解释为职责、依赖、隔离、局部变化和影响验证的边界合同
- 能用“提示17:消除不相关事物之间的影响”识别共享状态、时序假设、环境耦合和不必要的变化扩散
- 能在真实项目中只改变一个组件,观察首个副作用,执行局部测试和恢复,并让独立复核者重放结论
为什么 10 正交性不是完全没有依赖
本页依据 David Thomas、Andrew Hunt《程序员修炼之道:通向务实的最高境界(第2版)》,云风译,电子工业出版社,2020年4月,ISBN 9787121384356 的公开完整中文目录,独立重构 10 正交性。本页不复制原书正文、插图、练习答案或代码,而把目录命题转写成边界合同、耦合矩阵、局部变化实验和复核证据。
系统不可能没有依赖。正交性要解决的是:当一个组件改变时,不相关组件是否被迫改变;共享的数据、控制、时间或环境假设是否让变化越过了应该停止的边界。必要的依赖可以通过协议、所有权和失败条件被看见;隐藏依赖则会让局部修订变成全局回归。
本单元把问题拆成 职责 → 依赖 → 隔离 → 局部变化 → 影响验证。每一步记录输入、输出、所有者、可接受的影响范围、拒绝条件和恢复动作,不把“测试通过”当作已经证明没有耦合。
10 正交性:边界合同
| 节点 | 要问的问题 | 可观察产物 | 失败动作 |
|---|---|---|---|
| 职责 | 组件负责什么,不负责什么 | 输入、输出、owner 和不变量 | 缩小职责或拆分命令与查询 |
| 依赖 | 哪些数据、控制、时间和环境是必要的 | 依赖方向与协议 | 移除隐式全局状态 |
| 隔离 | 变化在哪条边界停止 | Orthogonality Boundary 与耦合矩阵 | 增加适配或消息边界 |
| 局部变化 | 只改变一个实现会影响谁 | 受影响节点和首个副作用 | 停止扩散并恢复原状态 |
| 影响验证 | 怎样证明无关组件未变 | 局部测试、契约测试和回归 | 补最早分叉的检查 |
Orthogonality Boundary 不是物理文件边界,而是变化责任和失败传播能被明确观察的边界。
↡可以独立改变、测试和回滚的一条变化方向,例如支付供应商、通知渠道或存储实现。Independent Axis 帮助团队把多个变化拆开;如果两条轴必须一起发布,应记录这个耦合而不是假装它们独立。
↡记录组件之间数据、控制、时间和环境依赖强度、方向与所有权的对照表。Coupling Matrix 让“这里有点耦合”变成可定位的节点、边和首个副作用。
↡一次组件操作在声明输出之外改变的状态、调用、顺序或外部资源。Side Effect 可以是必要的,但必须有所有者、时机、失败处理和可测试的边界。
↡只验证一个组件、协议或边界行为的检查,能在较短反馈路径中指出局部实现的变化结果。Local Test 不能替代端到端测试,但能帮助定位首个分叉,避免每次小修订都等待整个系统。
提示17:消除不相关事物之间的影响
提示17:消除不相关事物之间的影响。先把“相关”说清楚:如果两个组件共享同一领域不变量,它们可能需要通过协议协作;如果只是碰巧读取同一全局配置或依赖某个调用顺序,就不应让一方的内部变化影响另一方。
检查四类泄漏:数据泄漏指内部结构被外部直接修改;控制泄漏指调用者必须知道内部步骤;时间泄漏指结果依赖未声明的先后顺序;环境泄漏指测试或生产的全局状态改变了组件行为。每一种泄漏都应在 Coupling Matrix 中有边、有 owner、有失败证据。
从命题到可失败的因果链
本页主链是 职责 → 依赖 → Orthogonality Boundary → 局部变化 → Local Test。每条边要写明传递的是数据、控制、时间、环境还是反馈;每个节点要有输入、输出、接受条件、拒绝动作和恢复路径。只有把“正交”写在标题中而没有变化实验,不能证明它成立。
先写预测,再执行正常样本、边界样本和一次故障样本。三类样本共享项目版本、输入和观察窗口,只改变一个 Independent Axis 或一个依赖条件;结果出现差异时记录首个 Side Effect,不用最终红灯覆盖前面的分叉。
读懂耦合矩阵
矩阵的每一格都回答“谁依赖谁、依赖什么、谁负责解除”。数据依赖可以通过稳定 DTO 或查询接口隔离,控制依赖可以通过命令和结果协议隔离,时间依赖可以通过显式状态或消息确认隔离,环境依赖可以通过注入配置和独立测试隔离。
矩阵不是越空越好。若支付服务必须知道订单金额,这是声明的业务依赖;若支付服务必须知道通知模块刚刚完成了哪一步,且没有协议说明,则是隐藏的时间耦合。用同一变化请求逐格检查,能区分必要协作与不相关扩散。
局部变化与首个副作用
选择一条 Independent Axis,例如替换通知供应商,只改适配器实现。先预测业务规则、结算和用户结果不应变化,再运行 Local Test、契约测试和低风险端到端演练。若出现共享缓存、全局配置或顺序相关的 Side Effect,停在第一个受影响节点,保存输入、状态和调用轨迹。
恢复时先回到原始状态,解除泄漏的依赖,再重放相同样本。修复后的测试应证明“变化停在边界”,而不是只证明当前样本最后返回了成功。
三步完成一次正交性检查
先声明职责与独立轴
为目标组件写清输入、输出、不变量、owner 和不负责的事情,选择一条 Independent Axis。用 Coupling Matrix 标出数据、控制、时间和环境依赖,并标记哪些关系是必要的。
可重放的正交性记录
orthogonality_record:
unit: tpp20-topic-10-orthogonality
independent_axis: 通知供应商适配器
responsibility: 接收通知命令并返回投递结果
declared_boundary: 业务规则只依赖 NotificationPort
coupling_matrix: 数据、控制、时间、环境四类依赖均有 owner
local_test: 适配器错误映射与重试策略测试
injected_change: 只替换供应商 SDK
first_side_effect: 全局重试计数改变结算超时
recovery: 注入重试策略并恢复隔离的计数器
proof: 局部、契约、集成和用户结果检查全部重放这份记录把“无关事物不受影响”变成可重建证据。若 Local Test 通过但结算超时改变,说明局部测试覆盖不足;若全局计数器确实属于共同业务不变量,则应把它写入协议,而不是把它伪装成不存在的依赖。
选择、拒绝与迁移矩阵
| 判断 | 接受证据 | 应拒绝的信号 |
|---|---|---|
| 职责 | 输入、输出、不变量和 owner 清楚 | 组件可以修改所有共享状态 |
| 数据依赖 | 通过稳定协议传递并校验 | 外部直接读取内部结构 |
| 控制依赖 | 命令、结果和失败动作明确 | 调用者必须记住内部顺序 |
| 时间依赖 | 时序由状态、确认或消息声明 | “通常先调用 A”却无检查 |
| 环境依赖 | 配置注入且测试可隔离 | 全局变量决定测试结果 |
| 影响验证 | 首个副作用可定位并有局部测试 | 只有整套回归最后变红 |
迁移到云服务、数据系统或 AI 辅助开发时,分别记录线程、缓存、配置、权限和自动化反馈。工具可以生成依赖图或测试,但仍需人判断某条边是必要业务协作还是不相关影响。
常见误区
本章回顾
掌握 10 正交性,不是消灭所有依赖,而是让必要依赖有边界、方向、所有者和失败合同,让不相关变化停在局部。按 提示17:消除不相关事物之间的影响 识别数据、控制、时间和环境泄漏,用 Coupling Matrix 预测扩散,再用 Local Test 和首个 Side Effect 证据证明恢复后的变化确实被隔离。
可验证练习
练习
本组练习覆盖 10 正交性 和 提示17:消除不相关事物之间的影响,要求提交 Orthogonality Boundary、Independent Axis、Coupling Matrix、Side Effect 和 Local Test 的证据。
问题 1: 替换通知供应商后,订单结算偶尔超时。如何使用 Coupling Matrix 定位不相关影响?
问题 2: 如何实践“提示17:消除不相关事物之间的影响”,而不是复制所有依赖?
问题 3: 怎样设计局部测试来捕获隐藏的时序耦合?
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Orthogonality Boundary
定义数据、控制、时间和环境可见范围的变化与失败边界。
- Independent Axis
可以独立改变、测试和回滚的一条变化方向。
- Coupling Matrix
记录组件之间依赖类型、方向、强度和所有权的对照表。
- Side Effect
组件在声明输出之外改变的状态、调用、顺序或外部资源。
- Local Test
在较短反馈路径中验证一个组件、协议或边界行为的检查。
前后导航
来源与改写范围
- Pragmatic Programmer 作者页面:核对 Topic 10 与提示17的版本位置和主题范围。
- 中文目录页面:核对 10 正交性、提示17:消除不相关事物之间的影响的公开目录范围。
- 出版社书目信息:交叉核对中文译本的出版信息与版次边界。