加载中...

本篇要回答的问题:把"架构思维篇"反复讲的核心系统 / 周边系统正交分解伤害值两个工具,真刀真枪地套到第二章实战的"画图程序"(v44)上,看业务到底是怎么被分解的,以及如何用数据评判方案优劣。

本讲是一篇加餐式实战复盘,不引入新概念,而是验证前几讲(57 心性、58 如何判断架构优劣、59 少谈框架多谈业务、60 架构分解之边界)的想法。基准是画图程序的最后一次迭代 v44(github.com/qiniu/qpaint/tree/v44)。核心信念:“架构的本质是业务的正交分解,分解后每个模块业务上仍然自洽”,而关注每个模块的业务属性是架构的最高准则。

文件级整体结构:把内核压到最小

除入口 index.htm 外,所有文件按业务属性分四类,用颜色标注:

类别 颜色 含义
核心系统 棕色 画图业务的核心,不可或缺
周边系统 黄色 业务的可选组件(各 Controller)
通用控件 绿色 与画图业务无关的通用界面元素,被周边系统引用
基础框架 紫色 与业务无关的第三方代码或底层框架

画图程序文件级系统组织结构

关键洞见:整个画图程序的"内核"极小——只有 index.htmview.jsdom.js 三个文件。去掉所有周边系统及其依赖,程序仍能工作,只不过退化成一个只读查看器 QPaintViewer所有 Controller 都被做成彼此完全正交的可选组件

伤害值:用数据评判周边对核心的"伤害"

最关心的不是核心,而是周边系统(Controller)对核心系统的伤害有多大。方法是逐处统计周边模块对 View 层 / Model 层接口的引用。

周边模块对核心系统的引用关系表

creator/rect.js 为例:对 View 层引用 10 处、对 Model 层引用 6 处,裸伤害值 = 16。但关键在于这些接口大多不是为它一家提供的:

关键判断:伤害值是工程测量值。若某接口被 N 个周边模块引用,则每个周边模块只分担 1/N 的伤害。极端地,被无穷多模块引用的接口,对其中任一模块的伤害趋近 0——因为它已被实证为足够通用、足够稳定。

这解释了一个反直觉现象:新增一个互不相关的周边模块,反而会拉低既有模块的伤害值——因为它再次证实了某些接口确实通用。当前只被 rect.js 引用的接口(new QLinenew QRectnew QEllipseshape.onpaint,表中标红)一眼看上去就很通用,只是因程序规模还小、暂无人分担。

按"被超过 1 个模块引用即记引用 5 次"的近似估算,5 个周边模块的伤害值:

周边模块 近似伤害值
creator/rect.js 12/5 + 4 = 6.4
creator/path.js 12/5 + 1 = 3.4
creator/freepath.js 13/5 = 2.6
accel/select.js 10/5 + 6 = 8
accel/menu.js 5/5 + 6 = 7
合计(估算) 27.4

周边系统整体与核心系统的关系

把周边看作整体,实际引用是 31 处(实际伤害值 31)。估算值 27.4 偏小,差异来自"统一用 5 而非逐一统计引用次数"的近似。

Model 层:再分解一次

代码量最大的是 Model 层 dom.js(约 850 行),于是对它再做一次"核心 + 周边"分解——分形地复用同一套方法论。

Model 层的核心与周边分解

Model 层内部同样分四类:

类别 标色 含义
核心系统(接口级) 棕色 会被周边引用的"接口级"模块
核心系统(内部) 白色 只被核心内部引用,可不画出
OS 相关辅助函数 绿色 与业务无关但与平台相关
纯算法辅助函数 紫色 与业务、与 OS 都无关

因 JavaScript 是弱类型,Shape 接口在代码里不显式存在,作者用 Go 语法把它写清楚:style / onpaint / hitTest / bound / setProp / move / toJSON 等方法构成图形的统一契约。

Model 层周边对核心的引用关系表

关键洞见:Model 层需求的开放性主要体现在图形(Shape)种类(未来是否支持图片、艺术字……变数大)。不同图形对核心的需求完全一模一样,整个周边伤害值 = 4,平均每种图形伤害值 = 1。这正是好分解的标志——周边对核心的依赖整齐划一。

通用控件库:同一套方法论的第三次复用

控件种类无穷,出于开放性同样拆成核心(控件框架)+ 周边(各控件)。

通用控件库的核心与周边分解

控件库周边对核心的引用关系表

控件实现本身与控件框架几乎无关,只是把自己注册到框架中。所有控件对核心需求一致,整个周边伤害值 = 1,平均每种控件伤害值 = 1/3

总结

本讲把"核心系统 / 周边系统正交分解 + 伤害值度量"在画图程序上跑通三遍(整体文件层、Model 层、控件库),证明这套方法论是分形可复用的:任何一个复杂业务,向内看都能再分解出自己的核心与周边。好架构的特征是内核最小、周边正交、周边对核心的依赖整齐且伤害值低。

先把"是什么"回答清楚

概念 一句话说明
核心系统 业务不可或缺的部分,应被压到最小(画图内核仅 3 文件)
周边系统 彼此正交的可选组件,去掉后程序仍能退化运行
伤害值 周边对核心接口的引用次数(工程测量值),用来量化耦合、评判方案优劣
伤害值分担(1/N) 接口被越多周边引用,单个周边分担的伤害越低,因其被实证为通用
分形分解 把任一子系统(Model 层、控件库)当作完整业务,再做一次核心/周边拆分
正交 周边间互不引用,即使关联也只通过核心间接发生

一句话速记

把内核压到最小、让周边彼此正交,再用"伤害值"量化周边对核心的依赖——伤害越低、越整齐,分解越好;同一把尺子可以一层层递归地量下去。

几条值得记住的判断

  • 内核最小化是复杂系统设计的第一要务:理清核心与周边的边界。
  • 抽象共性接口 优于给单个周边开绿灯;接口的通用性需要"每多一个引用方就被实证加强一次"。
  • 伤害值是可用于横向比较的数据,能客观评判不同架构方案的好坏。

思考题

如果你也写过一个"画图程序"或类似的可扩展系统,试着算一算你的周边模块对核心的伤害值。有没有某个接口被你主观判定为"通用",却至今只有一个调用方?它的通用性,是否还缺少实证?

公告栏
这是我的个人知识库。
记录技术,也记录生活 —— 读过的、试过的、想明白的,都堆在这儿。
最新文章
网站资讯
文章数目 :
5
已运行时间 :
本站总字数 :
15.7k
本站访客数 :
本站总访问量 :
最后更新时间 :
全局知识图谱
当前页面 已访问 文章 标签
ESC 关闭 · 滚轮缩放 · 拖拽移动 · Ctrl+G 开关