引言:头顶的星空与心中的尺度
康德在《实践理性批判》的结尾写下了一段广为流传的话。不同译本的措辞略有差异,按其英文版本意译如下:
我们越是经常、越是持久地思考两件事,心中就越会生出历久弥新的惊叹与敬畏:头顶的星空,以及心中的道德律。
——伊曼努尔·康德,《实践理性批判》
“头顶的星空”把人放回广阔而有秩序的世界:我们能够通过观察和理性认识它,却也必须承认自身经验与认知的边界。“心中的道德律”则把目光转向人的内部:人在行动时不仅面对外部事实,也需要面对由理性、责任和原则构成的自我约束。
康德的哲学并不是为复杂问题提供几句可以直接套用的格言。相反,“批判”首先意味着审查理性的能力和边界:我们凭什么得到一个结论,这个结论在哪些条件下成立,又在哪些地方超出了可以正当使用的范围。对工程师而言,这种态度比任何一条具体“定律”都更加重要。
工程世界当然没有永恒不变的架构,也不能把软件经验与康德所说的道德律等量齐观。但我们同样面对两重尺度:外部是庞大、复杂且不断演化的系统,内部是对正确性、可靠性、诚实证据和工程责任的坚持。我们既要仰望系统所呈现的秩序,也要警惕自己的知识边界和判断偏差。
因此,本文收集这些工程定律,并不是为了寻找计算世界里不可动摇的教条,而是希望保留一组经过时间筛选的思考坐标:面对复杂性时有所敬畏,做出判断时有所依据,承担工程后果时有所约束。
1. 为什么还需要一份“工程定律”手册
工程工作里有一类问题很难只靠局部代码回答:为什么组织调整之后,系统边界也跟着变化?为什么增加机器后,查询速度没有线性提升?为什么一个兼容性改动会影响完全没有记录在文档里的用户?为什么项目的主流程很快完成,最后的工程化却迟迟不能收尾?
这些问题反复出现,于是人们把长期观察浓缩成了定律、原则、效应和箴言。它们不是用来替代分析的“标准答案”,而是帮助我们更早提出正确问题的思维索引。
这份手册主要面向数据库、查询引擎和分布式系统。它希望完成三件事:
- 把散落的工程经验组织成一套可以检索的框架;
- 把抽象表述映射到优化器、执行引擎、存储、协议和项目管理;
- 说明每条规律的边界,避免用一句名言终止讨论。
1.1 先区分“定律”的证据强度
这里收录的内容并不处于同一层次。
| 类型 | 代表条目 | 应该怎样使用 |
|---|---|---|
| 数学模型 | Amdahl、Gustafson | 写清变量和前提,用数据代入验证 |
| 不可能性结论 | CAP | 明确系统模型、故障条件和语义定义 |
| 经验规律 | Conway、Brooks、Lehman | 作为风险提示,再寻找当前项目的证据 |
| 设计原则 | KISS、DRY、YAGNI、SOLID | 用于比较方案,不能机械套用 |
| 认知偏差 | Confirmation Bias、Sunk Cost | 用来约束自己的决策过程,而不是给别人贴标签 |
| 讽刺或文化箴言 | Zawinski、Dilbert、Sturgeon | 用来提醒组织病灶,不能冒充科学结论 |
因此,本文最重要的使用原则是:
定律是发现问题的透镜,不是证明结论的证据。真正的工程结论仍然要落在 workload、指标、故障模型和代码路径上。
2. 架构:边界、抽象与复杂性
2.1 Conway’s Law:架构也是沟通结构的投影
Mel Conway 的原始表述可以概括为:设计系统的组织,最终会产出与组织沟通结构相似的系统。
数据库团队如果分别由 Optimizer、Execution、Storage 和 Cloud Infra 小组负责,系统通常也会形成相似的模块边界。这个映射本身并没有问题,危险在于组织接口不清晰时,模糊也会被写进系统:
- Ownership 分裂,最终表现为跨模块状态重复维护;
- 团队之间只能通过工单协作,最终表现为僵硬而冗长的接口;
- 各团队只优化自己的指标,最终表现为局部最优、全局退化;
- 组织变化很快,架构却保留着已经不存在的边界。
Conway’s Law 的实践价值不是“用组织解释一切”,而是要求架构评审同时检查技术依赖和协作依赖。想得到面向端到端链路的系统,通常也需要能够对端到端结果负责的团队。
检查问题:一个跨模块需求需要多少团队同步?接口的复杂性来自业务语义,还是来自 Ownership 的切割?
2.2 Leaky Abstractions:抽象不会消灭底层差异
Joel Spolsky 将这种现象称为抽象泄漏定律:足够复杂的抽象都会在某些时刻泄漏底层细节。
“统一存储接口”可以把 HDD、NVMe、本地缓存和对象存储都抽象成 read/write/scan,但延迟分布、一致性、Flush 语义、请求粒度和失败模式仍然不同。平稳运行时,上层只看到统一接口;性能抖动和故障发生时,底层差异会穿透抽象。
在数据库中,常见泄漏包括:
- 统一 Join 接口泄漏 Hash Table 布局、Build/Probe 非对称性和 Spill 能力;
- 统一 Cache 接口泄漏 Page Cache、Block Cache 和 Object Cache 的淘汰行为;
- 统一 SQL 方言泄漏类型系统、NULL 语义和函数实现差异;
- 存算分离接口泄漏网络 RTT、带宽竞争和远端限流。
好的抽象不是假装底层完全相同,而是同时提供稳定的公共语义、必要的能力描述和可追踪的诊断通道。
检查问题:故障时能否看到真正的后端、请求和重试?调用方是否能够表达 Locality、Consistency 或 Latency Budget?
2.3 Hyrum’s Law:所有可观察行为都可能成为契约
Hyrum’s Law指出:当 API 用户足够多时,契约中写了什么已经不重要;系统的所有可观察行为最终都会被某些用户依赖。
数据库用户依赖的往往不止 SQL 标准和公开 API,还包括:
EXPLAIN和 Profile 的字段、顺序与文本格式;- 错误码、错误消息甚至大小写;
- NULL 排序、隐式类型转换和结果列顺序;
- Optimizer Rule 的副作用和 Hint 的边缘行为;
- 配置项默认值、资源估算以及查询计划的稳定性。
这解释了为什么一次“内部重构”也可能造成兼容性事故。对于可观察面,应该先建立清单,再决定稳定等级:哪些是正式契约,哪些提供迁移期,哪些明确不保证稳定。无法无限兼容时,要提供版本化、弃用告警和替代路径,而不是假设“文档没写,用户就不会用”。
检查问题:这次改动改变了哪些可观察行为?仓库外是否有脚本、平台或 SDK 在解析它?
2.4 Gall’s Law 与 Second-System Effect:复杂系统只能从可工作的内核长出来
Gall’s Law 强调,能工作的复杂系统通常由能工作的简单系统演化而来;从零设计的复杂系统往往难以工作。Second-System Effect 则提醒我们:第一个系统成功之后,第二版最容易把积压的想法一次性装进去,最终过度设计。
在查询引擎中,“实时与离线统一、跨源联邦、自动物化视图、增量刷新、智能推荐、跨云容灾”都可能是合理目标,但不能同时作为最小闭环。更稳健的演进顺序是:
1正确执行一个核心场景
2 │
3 ├─► 建立可观测性和失败语义
4 │
5 ├─► 覆盖更多数据规模与边界条件
6 │
7 └─► 在真实反馈上增加自动化与通用性
设计第二代 Planner、Storage Format 或 Scheduler 时,先列出第一代真正被验证的核心价值,再列出必须解决的结构性问题。所有“顺便重做”的内容都应该重新证明收益。
2.5 Tesler’s Law:复杂性只能被分配
系统中存在无法消除的必要复杂性。产品可以把分区、Compaction、Rebalance 和故障恢复隐藏起来,让用户获得简单体验,但这些复杂性会转移到内核、控制面和运维系统。
这不是反对易用性,而是要求设计者回答三个问题:
- 哪些复杂性属于问题本身,哪些是当前实现制造的?
- 必要复杂性应该由用户、Planner、Runtime 还是运维系统承担?
- 承担复杂性的那一层是否拥有足够的信息、资源和可观测性?
复杂性转移只有在接收方更有能力处理它时才有价值。把用户侧配置变成系统内不可解释的“智能决策”,只是把显式复杂性换成了隐式风险。
2.6 架构类扩展原则
| 原则 | 核心提醒 | 数据库场景 | 边界 |
|---|---|---|---|
| Premature Optimization | 没有测量就不要提前优化 | 为假想热点引入复杂 SIMD、Cache 或 Codegen 路径 | 不是反对前置容量设计和已知热点优化 |
| Law of Unintended Consequences | 改动会产生连锁副作用 | 调整 Cost Model 后改变 Join 顺序、内存峰值和调度并发 | 用灰度、回滚和观测控制风险,不是停止变化 |
| Zawinski’s Law | 程序倾向不断扩大职责 | 工具变平台,平台再承担 Catalog、调度和治理 | 属于讽刺性提醒,不是经验定理 |
| The Map Is Not the Territory | 模型不是现实本身 | Cost、统计信息和架构图都只是运行时系统的近似 | 模型仍然必要,但要用实际 Profile 校准 |
3. 分布式系统与规模:先写清前提,再谈扩展
3.1 CAP:不是“任意选择两个字母”
CAP 最常见的误读是“一致性、可用性、分区容错三选二”。Gilbert 和 Lynch 对 CAP 的形式化讨论关注的是:在允许网络分区的异步模型中,系统无法同时保证原子一致性和每个请求都能得到响应。具体定义和模型可参见论文 Perspectives on the CAP Theorem。
工程上真正要写清的是分区发生时的行为:
| 问题 | 偏一致性的选择 | 偏可用性的选择 |
|---|---|---|
| 元数据多数派不可达 | 拒绝写入或等待仲裁 | 接受本地写入,之后解决冲突 |
| 跨 Region 读 | 等待确认或只读 Leader | 允许 Stale Read |
| Lease 状态不确定 | 停止服务以避免双主 | 继续服务并承担语义冲突 |
| 分布式事务协调者失联 | 保持 Pending 并恢复决议 | 超时后做补偿或弱化语义 |
CAP 也不是系统的完整分类。现实设计还要考虑延迟、一致性级别、故障持续时间、数据丢失窗口和恢复复杂度。比“我们是 CP 还是 AP”更有价值的问题是:哪种请求在什么故障下返回什么结果,恢复后如何收敛?
3.2 分布式计算的八个谬误:把网络当作故障域
分布式设计经常无意识地假设网络可靠、延迟为零、带宽无限、网络安全、拓扑不变、只有一个管理员、传输零成本且网络同质。这些假设在 Remote Cache、Shared Storage、Remote Spill 和跨 Region 事务中尤其危险。
设计不能只补一个 Retry:
- Timeout 必须绑定端到端 Deadline,避免逐层重试放大延迟;
- Retry 需要退避、抖动、幂等和预算;
- 慢节点要被当作故障,而不只是性能波动;
- 队列和连接池必须有上界,防止局部故障扩散;
- Profile 要区分排队、网络、远端执行和重试时间;
- 降级路径必须定义数据语义,不能只保证“接口还返回 200”。
3.3 Amdahl’s Law:优化上限由串行部分决定
若程序中可并行比例为 P,使用 N 个并行单元时的理论加速比为:
1 1
2S(N) = -------------------
3 (1 - P) + P / N
当 P = 0.95 时,即使 N 无限大,理论加速比也不会超过 20。这 5% 可能是 Coordinator 串行规划、Metadata Fetch、Final Merge、Commit、全局 Barrier,或者无法切分的热点分区。
这条规律改变了性能分析的顺序:不要先问“还能增加多少并行度”,而要先从 Critical Path 上找不可并行、不能重叠或必须全局同步的部分。单个算子 CPU 占比高,不等于它就是端到端加速的第一目标。
3.4 Gustafson’s Law:扩展也可以意味着解决更大的问题
Amdahl 假设问题规模固定,Gustafson 则讨论资源增加时问题规模也随之增长的场景。若串行时间占比为 α,N 个处理单元的缩放加速可写为:
1S(N) = N - α × (N - 1)
两者并不冲突:
- Amdahl 回答“同一份数据最多能跑快多少”;
- Gustafson 回答“在近似相同时间里能否处理更大的数据”。
对 OLAP 系统,Scale-up 测试应同时报告固定数据量的加速效率,以及 1 TB、10 TB、100 TB 等同比扩容下的单位资源效率。只展示其中一种,很容易掩盖串行瓶颈或规模红利。
3.5 Metcalfe’s Law:生态连接有价值,但不是严格平方关系
Metcalfe’s Law 常用参与者两两连接数量解释网络价值。对数据平台而言,Connector、Table Format、Catalog、BI、ETL 与 AI 工具的互操作性确实会放大平台价值。
但 n² 不是普适的产品估值公式:连接质量、参与者活跃度、治理成本和跨系统语义一致性同样重要。十个稳定、可维护的连接通常比一百个“能连上但不能正确演进”的适配器更有价值。
4. 演化与质量:系统不是交付后就静止的作品
4.1 Lehman’s Laws:活着的软件必须演化,也会积累复杂性
Lehman 对现实环境中持续使用的软件进行了长期研究。核心观察是:系统若不随环境和需求变化,就会逐渐失去价值;而持续变化会增加复杂性,除非团队主动投入维护和简化。相关背景可参见论文 Software evolution—Background, theory, practice。
数据库长期演化后会自然出现 Legacy Config、兼容分支、旧 Planner 路径、格式迁移和多代协议并存。问题不在于这些东西“看起来不够优雅”,而在于新增功能、兼容承诺和清理能力是否失衡。
因此,重构、弃用、可观测性和迁移工具不是 Feature 之外的额外工作,而是保持演化能力所需的固定预算。
4.2 Technical Debt:债务的关键是未来利息
技术债最有用的地方是“债务”这个隐喻:当前采用权宜方案换取速度,同时接受未来返工和持续利息。它不等于所有旧代码、丑代码或自己不熟悉的代码。
判断一项问题是否值得优先偿还,可以估算:
1技术债优先级 ≈ 未来变更频率 × 单次额外成本 × 影响范围 × 风险系数
例如 Rule 顺序写死,如果两年都不会改变,它可能只是难看的实现;如果每个新规则都要修改多处、反复引入回归,它就在持续产生利息。债务清单应该记录本金、利息、触发条件、Owner 和偿还方案,而不是堆积没有决策信息的 TODO。
4.3 Postel’s Law:宽容输入并非总是安全
Postel’s Law 常被概括为“发送时保守,接收时宽容”。它曾帮助协议在异构实现间互操作,但过度宽容也可能带来解析歧义、安全漏洞和事实标准分裂:A 接受的非法输入,经过 B 转发后可能产生不同语义。
在现代数据库协议和配置解析中,更稳健的做法往往是:
- 输出遵循明确、版本化的规范;
- 对可安全归一化的差异保持兼容;
- 对歧义、越界和未知关键字段严格失败;
- 弃用行为有告警、指标和截止版本。
兼容性不是“什么都接受”,而是在可预测语义内接受演进。
4.4 从“会出错”推导质量体系
Murphy’s Law 提醒我们,会出错的路径最终可能出现;Testing Pyramid 提醒我们建立不同粒度、反馈速度和成本的测试组合;Pesticide Paradox 则提醒固定测试集会逐渐失去发现新缺陷的能力。
对数据库内核,一套更实用的测试组合是:
| 测试层 | 主要职责 | 典型对象 |
|---|---|---|
| 单元与属性测试 | 快速验证局部不变量 | Expression、编码、Hash/Compare、Rule |
| 组件测试 | 验证模块协作和故障分支 | Spill、Compaction、WAL、统计信息 |
| SQL 回归与差分测试 | 验证语义和兼容性 | 查询结果、类型系统、优化前后等价性 |
| 端到端与升级测试 | 验证真实部署链路 | 扩缩容、升级回滚、跨版本协议 |
| 压力与故障注入 | 发现时序和资源边界 | OOM、磁盘满、丢包、慢节点、重启 |
Test Pyramid 是关于成本和反馈速度的启发式模型,不是要求所有系统都画出相同比例。数据库大量正确性问题横跨 Optimizer、Runtime 和 Storage,组件测试、SQL 差分测试可能比传统 UI 系统占比更高。
4.5 质量类扩展原则
| 原则 | 可执行解释 | 使用边界 |
|---|---|---|
| Broken Windows | 过期注释、失效测试和无 Owner 模块会降低后续维护标准 | 只把它当工程卫生隐喻;其社会学外推存在争议 |
| Linus’s Law | 更多有能力且有动力的评审者能提高缺陷暴露概率 | “看的人多”不等于有效评审,仍需测试和责任闭环 |
| Kernighan’s Law | 写到能力上限的巧妙代码,调试时往往超出能力上限 | 性能敏感代码可以复杂,但要用测试、注释和基准约束 |
| Sturgeon’s Law | 热门内容和工具也需要筛选 | 属于讽刺性概括,不能代替具体评估 |
5. 设计原则:目标是降低变化成本,不是追求形式正确
DRY、KISS、SOLID、Law of Demeter、Principle of Least Astonishment 和 YAGNI 经常一起出现,但它们可能互相拉扯。抽取统一框架符合 DRY,却可能违反 KISS 和 YAGNI;隐藏实现细节符合 Demeter,却可能增加性能关键路径的抽象成本。
| 原则 | 更准确的关注点 | 常见误用 |
|---|---|---|
| DRY | 同一份知识应有唯一权威来源 | 仅因为两段代码长得相似就强行抽象 |
| KISS | 选择满足约束的最简单方案 | 用“简单”掩盖错误或遗漏的边界条件 |
| SOLID | 管理职责、依赖和可替换性 | 在数据导向或性能敏感代码中堆叠对象层次 |
| Law of Demeter | 限制对遥远内部结构的了解 | 为每个字段机械增加转发接口 |
| Least Astonishment | 行为符合目标用户已经形成的模型 | 假设所有用户拥有相同直觉 |
| YAGNI | 不为尚未发生的需求支付实现成本 | 忽略已知容量边界、迁移能力和不可逆格式决策 |
原则冲突时,应该回到变化方向:未来最可能变化的是什么?哪些约束不可逆?性能和可维护性的证据是什么?设计原则提供比较维度,而不是按命中条目数量给方案打分。
6. 团队与项目:软件的瓶颈经常不在代码里
6.1 Brooks’s Law:延期项目不能靠机械加人恢复
Optimizer、Storage Format、分布式事务和 Scheduler 都高度依赖上下文。新人加入后需要学习,核心成员需要带教,沟通路径也随人数增加。因此,给已经延期的项目加人,短期内可能进一步降低有效产出。
这不意味着加人永远无效。当工作可以独立拆分、接口稳定、交付周期足够长时,增加人员仍然有价值。真正的判断顺序应该是:
- 延期来自工作量、关键路径、决策等待,还是质量返工?
- 是否能缩小 Scope 或提前交付闭环?
- 新工作能否与关键路径解耦?
- 谁来承担学习和沟通成本?
6.2 Bus Factor:关键知识不能只存在于一个人的上下文里
如果 CBO、MV Rewrite、存储格式或恢复协议只有一个人敢改,团队拥有的不是“不可替代的专家”,而是高风险的知识单点。
降低风险不能只靠补文档。设计文档、代码评审、Backup Owner、轮值 On-call、故障演练和跨人主导改动需要组合使用。最有效的验证是:让非原作者完成一次真实变更、上线和故障定位。
6.3 规模扩大后的协调损耗
Ringelmann Effect 提醒团队规模扩大后,人均有效贡献可能下降;Dunbar’s Number 提醒稳定的人际关系存在认知上限。这些数字和效应不应被当成精确组织公式,但它们共同指出:组织一旦越过熟人协作规模,就必须依赖清晰接口、书面决策和小队自治。
Price’s Law 常被用于描述贡献分布高度不均,但它不是可靠的通用定量定律,更不能成为长期依赖“英雄”的理由。关键贡献者需要得到保护,同时也必须把系统能力沉淀到团队。
6.4 计划为什么总在最后失真
| 规律 | 它提醒的风险 | 应对方式 |
|---|---|---|
| Parkinson’s Law | 工作会扩张到填满可用时间 | 设 Must-have、Nice-to-have 和 Cut Line |
| Ninety-Ninety Rule | 主流程完成不等于可上线 | 单独估算兼容、可观测、回滚和故障路径 |
| Hofstadter’s Law | 复杂工作存在系统性估时偏差 | 用历史数据、区间估计和阶段性校准 |
| Gilb’s Law | 重要目标需要某种可观测尺度 | 即使不能精确测量,也先定义代理指标和误差 |
| Goodhart’s Law | 指标一旦成为单一目标就会被优化失真 | 使用指标组合、护栏指标和人工复核 |
例如只追求 TPC-DS 总分,可能牺牲真实 Workload、P99、资源公平和可维护性;只追求 Cache Hit Ratio,可能通过缓存低价值对象让指标更好,却增加内存占用。指标设计必须同时回答:它代表什么、遗漏什么、如何被投机优化。
6.5 组织类讽刺原则的正确位置
Putt’s Law、Peter Principle 和 Dilbert Principle 都指向技术能力、管理权力与岗位匹配之间的张力。它们更适合作为组织反思的入口,而不是给个人下结论。
值得检查的是:技术决策是否有真正理解系统的人参与?晋升是否评估新角色所需能力?管理岗位是否成了唯一的成长路径?把问题改写成制度问题,通常比引用讽刺原则评价某个人更有建设性。
7. 决策与排障:先管理认知,再管理复杂系统
7.1 Confirmation Bias:主动寻找能推翻自己的证据
排障最危险的时刻,是在数据不足时先形成了一个生动解释:“一定是网络”“一定是磁盘”“一定是刚合入的 Optimizer 改动”。之后,人会更关注支持该判断的指标,忽略相反证据。
更可靠的排障记录应该同时包含:
| 项目 | 内容 |
|---|---|
| 现象 | 精确到时间、范围、请求类型和异常指标 |
| 候选假设 | 至少列出两个可以竞争的解释 |
| 支持证据 | 当前数据为何提高该假设的可能性 |
| 反证条件 | 看到什么就应该放弃该假设 |
| 下一步实验 | 成本最低、区分度最高的验证动作 |
Confirmation Bias 应首先用来约束自己,而不是在评审中宣称对方“有偏见”。
7.2 Occam 与 Hanlon:是搜索顺序,不是事实裁判
Occam’s Razor 建议在多个解释都能覆盖现象时,优先验证假设更少的解释。系统突然变慢时,先检查 Workload、配置、缓存和后台任务,通常比直接猜测 CPU 微码或编译器错误更高效。
Hanlon’s Razor 则提醒跨团队问题先检查疏漏、信息不对称和边界不清,不要直接归因恶意。两者都只是调查顺序:简单原因和无意错误更常见,但不能因此排除复杂故障或安全攻击。
7.3 Sunk Cost 与 Inversion:让未来收益决定未来投入
一个方向投入半年,并不会自动提高它未来成功的概率。判断是否继续时,可以问:如果今天第一次看到现有证据,我们还会选择这条路吗? 已经花掉的成本只应作为经验输入,而不应成为继续投入的理由。
Inversion 从反面补充这一判断:
- 什么 Workload 一定会把这个方案打崩?
- 哪三条路径最容易造成不可恢复的数据错误?
- 哪个依赖一旦行为变化,我们就无法回滚?
- 如果项目半年后失败,最可能的原因是什么?
先构造失败,再检查方案是否具备隔离、观测和恢复能力,通常比笼统询问“大家觉得方案怎么样”更有效。
7.4 First Principles:在惯性最强的地方重新推导
第一性原理要求把问题拆到基本约束,再重新推导方案。它适合挑战“我们一直这样做”的关键设计,例如:为什么必须依赖本地盘?为什么这个状态必须全局一致?为什么 Hash Table 必须完整驻留内存?
但从头推导本身成本很高。成熟协议、文件格式和算法中包含大量已经被事故验证的知识。第一性原理不是忽略历史,而是把历史方案还原成约束,区分哪些约束仍然存在,哪些只是路径依赖。
7.5 决策类扩展模型
| 模型 | 有价值的提醒 | 使用边界 |
|---|---|---|
| Pareto Principle | 少数查询、路径或用户可能主导大部分成本 | 80/20 不是固定比例,要用数据找实际分布 |
| Dunning-Kruger Effect | 知识不足时也可能缺少校准能力 | 用于自省和引入校验,不能用于嘲笑异议者 |
| Hype Cycle / Amara’s Law | 人们可能短期高估、长期低估新技术 | 不能据此预测任何具体技术一定成功 |
| Lindy Effect | 经历长期使用的非易腐技术包含存活信号 | 老并不等于适合当前约束,新技术也可能解决新问题 |
| Cunningham’s Law | 公开的错误说法常能迅速引来纠正 | 属于网络文化观察,不应通过故意误导获取答案 |
7.6 把一句定律改写成可证伪的 ADR
如果一个“定律”不能改变验证动作,它在评审里通常只是修辞。更有效的做法,是把它写成一条轻量 Architecture Decision Record:
1Observation:
2 当前观测到什么,而不是我们相信什么?
3
4Law / Model:
5 哪条规律提示了哪一种风险?它属于数学结论、经验规律还是比喻?
6
7Hypothesis:
8 在当前系统约束下,具体会出现什么可观测后果?
9
10Falsifier:
11 哪个数据、实验或调用方检索结果会推翻假设?
12
13Decision:
14 选择什么方案,接受什么代价?
15
16Revisit Trigger:
17 Workload、团队、规模或故障模型发生什么变化时重新评估?
例如引用 Conway’s Law,不应直接得出“按团队拆服务”,而应形成可验证假设:跨团队接口的变更 Lead Time 与故障恢复时间是否显著更高?如果数据并不支持,就不应该为了组织图机械拆分系统。引用 Amdahl’s Law,也不应只计算一个理论上限,还要通过 Profile 找到真正的串行区间,并确认优化后瓶颈是否迁移。
这种写法保留了工程定律的启发价值,又强迫结论回到证据、约束和复查条件。
8. 面向数据库工程的十条常驻模型
如果只保留十条,我会选择下面这组。它们覆盖系统设计从需求、实现到上线和演化的主要风险。
| 模型 | 常驻问题 |
|---|---|
| Conway’s Law | 这个架构边界是否只是组织边界的偶然投影? |
| Leaky Abstractions | 底层差异会从哪里泄漏,如何诊断? |
| Hyrum’s Law | 哪些非正式行为已经被外部依赖? |
| Gall’s Law | 最小可工作闭环是什么? |
| Amdahl’s Law | Critical Path 上真正串行的部分在哪里? |
| CAP | 分区发生时,每类请求的确定语义是什么? |
| Distributed Computing Fallacies | 网络慢、断、抖和重复时会发生什么? |
| Lehman’s Laws | 演化复杂度和清理预算是否平衡? |
| Goodhart’s Law | 指标变成目标后会怎样被优化失真? |
| Confirmation Bias | 什么证据能够推翻当前判断? |
9. 把定律变成评审与排障清单
9.1 架构评审
- 系统边界和团队 Ownership 是否一致且可演进?
- 抽象隐藏了什么,哪些差异必须显式暴露?
- 用户可观察面是否有版本化和兼容策略?
- 分区、慢节点和局部失败时,各类请求的语义是什么?
- 必要复杂性被转移给哪一层,它是否有能力承担?
- 能否先交付一个可运行、可观测、可回滚的最小闭环?
9.2 性能优化
- 结论来自 Profile 和基准,还是来自对热点的想象?
- Critical Path、CPU 总量和资源峰值是否被混为一谈?
- 串行部分、全局 Barrier 和热点分区在哪里?
- 固定规模加速与同比扩容效率分别如何?
- 哪些 Query 或路径主导实际成本?
- 优化目标是否诱导 Benchmark 特化或牺牲 P99、正确性和公平性?
9.3 项目计划
- 主流程之外,是否单独估算测试、兼容、灰度、回滚和观测?
- 延期的真正瓶颈能否通过加人解决?
- 是否存在只有一个人掌握的关键上下文?
- Scope 是否有明确 Cut Line?
- 如果今天从零决策,是否还会继续当前方向?
9.4 故障排查
- 现象的时间、范围、版本和 Workload 是否已经固定?
- 当前有哪些竞争性假设?
- 什么证据能推翻最偏爱的假设?
- 是否先验证了配置、负载、缓存和依赖变化等简单原因?
- Retry、Timeout 或降级是否正在放大原始故障?
- 恢复服务之后,是否验证数据语义和积压任务已经收敛?
10. 结语:用定律打开讨论,而不是结束讨论
工程定律最大的价值,是把个人经验压缩成可以共享的提问方式。它们能提醒我们关注抽象泄漏、串行瓶颈、网络分区、兼容性、组织接口和认知偏差,却不能直接证明某个方案正确。
一次有价值的引用应该完成三步:
- 指出风险:这次变更可能触发 Hyrum’s Law;
- 转成问题:哪些可观察行为可能已被依赖?
- 要求证据和行动:检索调用方、运行兼容测试,并设计迁移期。
只说“这是 Conway’s Law”或“根据 CAP 做不到”,很容易变成名词压制。把定律翻译成可证伪的问题、可观测的指标和可执行的动作,它们才会从鸡汤卡片变成真正的工程工具。
参考资料
- Immanuel Kant,The Critique of Practical Reason
- Mel Conway,Conway’s Law
- Joel Spolsky,The Law of Leaky Abstractions
- Hyrum Wright,Hyrum’s Law
- Seth Gilbert、Nancy Lynch,Perspectives on the CAP Theorem
- Gene M. Amdahl,Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities
- John L. Gustafson,Reevaluating Amdahl’s Law
- Meir M. Lehman、Juan F. Ramil,Software evolution—Background, theory, practice
- Martin Fowler,Test Pyramid