本篇要回答的问题:什么样的架构才算好?除了一些定性的基本准则,能不能给出可计算的定量公式来度量架构优劣?
上一讲我们谈了架构师的"心性修炼"——想进步先得知道什么是好。本讲承接这一点,从定性的基本准则讲到定量的经验公式:用"核心系统伤害值"度量核心纯洁度,用"耦合度公式"度量模块质量。
架构设计的基本准则
| 准则 | 全称/含义 | 关键主张 |
|---|---|---|
| KISS | Keep it Simple, Stupid | 简单比复杂好;强调易实施性、心智负担低、接口符合惯例避免惊异 |
| Modularity | 模块化 | 模块规格(接口)比实现机制更重要;着眼模块而非框架 |
| Testable | 可测试性 | 设计以可测试性为第一目标;可测试往往意味着低耦合 |
| Orthogonal Decomposition | 正交分解 | 架构就是不断对系统正交分解的过程 |
关键判断:框架是易变的、可复用性低;不让模块为框架买单,模块设计时应忽略框架的存在,把接口提高到通用普适场景去审视多余约束。
洞见:“优先组合而非继承”——用正交分解诠释就是鼓励做乘法(组合正交模块)而非做加法(继承叠加改造)。
核心系统的伤害值
正交分解第一件事:分出核心系统(业务最小功能集)与周边子系统。
| 系统类型 | 关注点 |
|---|---|
| 核心系统 | 变更要额外小心;若新功能后期被界定为核心,需评估对既有架构的破坏性 |
| 周边功能 | 如何降低新增功能对核心系统的影响?代码尽量内聚、独立成文件 |
终极目标是让新功能对核心系统的影响降到零(不改一行代码)——这需要核心系统提供插件机制(后续展开)。
核心系统受各周边系统的总伤害值公式:
总伤害 = 对每一处修改求和 ∑ log₂(修改行数 + 1)
| 公式要点 | 说明 |
|---|---|
| "一处修改"如何界定 | 同一周边功能相邻的代码行算一处;不同周边功能哪怕相邻也算多处 |
| 核心含义 | 修改处数越多,伤害越大;每处鼓励压到只改一行,更多代码下放到周边模块 |
| 适用性 | 既度量核心系统总伤害,也度量单个周边功能对核心的影响面 |
关键判断:核心系统越干净,增加新功能越容易。由于核心系统的特殊地位,伤害值是最重要的测量公式。
模块的耦合度测量
第二个关注点是每个模块自身的质量 = 接口质量 + 实现质量。接口质量是模块级最重要的东西,取决于:
| 接口质量因素 | 是否可计算 |
|---|---|
| 接口与业务的匹配性(越自然体现业务越好) | 不可计算,依赖主观判断(下一讲展开) |
| 接口的外部依赖(对环境的耦合度) | 可计算(见下) |
单个依赖的耦合度公式:
与所依赖模块 A 的耦合度 = 对每个依赖的符号求和 ∑ log₂(符号出现次数 + 1)
其中"符号(symbol)"指:被引用的类型(typedef/class/struct)、被引用的全局变量/全局函数/成员函数。
模块的总耦合度公式:
总耦合度 = 对每个依赖模块 A 求和 ∑(耦合度ₐ × 不成熟度系数ₐ)
| 系数 | 取值含义 |
|---|---|
| 不成熟度系数 = 0 | 依赖模块完全成熟、不再改变(理论上只有 int/string 等内置类型) |
| 不成熟度系数 = 1 | 模块剧烈变动,连规格都无法确定 |
洞见:公式鼓励依赖外部成熟模块、尽量减少外部依赖。注意:把接口参数类型改为
object/interface{}并不能降低耦合度——要看实际使用时它可能的各种具体类型,都计入依赖。
关键判断:这些都是经验公式,无严谨数学证明,计算值无物理含义,只能用于对比两个功能相同的方案;功能不同的 A、B 系统之间不可比较好坏。
总结
本讲先用四条基本准则(KISS / Modularity / Testable / 正交分解)指明方向,再用两类经验公式做定量评估:伤害值衡量核心系统纯洁度,耦合度衡量模块接口/实现质量。
局限:公式只考虑了静态依赖,未考虑动态依赖。例如网络模块 A 调 B:调用的接口数量越多(静态,已考虑)、调用的次数越多(动态,未考虑),依赖都越大。
先把"是什么"回答清楚
| 概念 | 一句话说明 |
|---|---|
| KISS | 简单即美,强调易实施性而非外观简洁 |
| 正交分解 | 用组合(乘法)而非继承(加法)构建系统 |
| 核心伤害值 | ∑log₂(修改行数+1),度量核心系统纯洁度 |
| 耦合度公式 | ∑log₂(符号次数+1)×不成熟度,度量模块依赖 |
| 不成熟度系数 | 0=完全成熟,1=规格未定剧烈变动 |
一句话速记
好架构 = 核心系统受到的"伤害值"尽量小(改动压到一行、下放到周边)+ 模块对成熟模块的"耦合度"尽量低;两个公式只能对比同功能方案。
几条值得记住的判断
- 接口比实现机制更重要,模块不为框架买单。
- 伤害值公式是最重要的——核心越干净,加功能越容易。
interface{}不能降低耦合度,要看实际类型。- 公式只覆盖静态依赖,动态依赖(调用次数)未纳入。
思考题
挑你系统里一个"核心系统",统计最近三个周边功能各自往核心里塞了几处修改、每处多少行,用 ∑log₂(行数+1) 算一下伤害值。哪个功能最该重构成"只改一行 + 插件下放"?
