加载中...

这一讲把前面 26~31 讲那些零散的判断,收成了一套方法论。
而且作者掏出了一个之前没给过的东西:一个完全不用 MVC、全部代码揉在一起的版本(v01)
让你亲眼对比"不这么做会怎样"。

这一讲的代码:

路径
v01:全部揉在一个文件里(470 行) 代码/qpaint源码/v01-全部揉在一起.htm
v26:同样功能,MVC 拆成 7 个文件 26 讲的 v26/

一 · 架构工作分两块

是什么 考验什么
基础架构 技术选型:选操作系统、编程语言、技术框架、第三方库 选择能力——技术前瞻性和判断力
业务架构 业务系统的分解能力——分解领域问题 对用户需求的理解

作者在这里说了一句挺重的话:

“大部分架构师往往更容易把关注点放到业务架构上,但实际上基础架构的影响面更广,选错产生的代价更高
架构师之间的差距,更大的是体现在其对待基础架构的态度和能力构建上。

阿里的"大中台、小前台",本质上就是在提倡基础平台建设。

而业务架构的第一步不是画图,是需求分析

没有需求分析,就没有业务架构。在业务架构过程中,需求分析至少应该花费三分之一以上的精力。

记住这个数字:三分之一。 第六节还会回到它。


二 · 分解得好不好,就看两条

① 功能的使用界面(接口),应尽可能符合业务需求对它的自然预期;
② 功能的实现要高内聚,功能与功能之间的耦合尽可能低。

而且这两条在每一层都一样用——子系统分解成模块、模块分解成类、类分解成函数,同一个套路

2.1 接口自然体现业务需求

这一讲最狠的一句话:

一个程序员的系统分解能力强不强,其实一眼就可以看出来。你都不需要看实现细节,
只需要看他定义的模块、类和函数的使用接口。如果存在大量说不清业务意图的函数,
或者存在大量职责不清的模块和类,就知道他基本上还处在搬砖阶段。

这条你在带读里见过它的小号版本:

出处 同一件事的说法
带读-01 · 2.6 addShape(矩形)业务动作_shapes.push(矩形)数据结构操作
门要开成业务动作的样子
带读-04 · 四 参数名划定了这个口子将来能长多大lineStyleshapeStyle
带读-06 · 3.2 一个设计良好的 URL,读一眼就能画出对方的 Model 树

2.2 高内聚:作者自己的两个习惯

“① 一个功能的代码尽可能单独一个文件,不要和其他功能混在一起;
② 一些小功能可能放在同一个文件中,但中间也会用 // ------------------ 这样的注释行
分割成很多逻辑上的"小文件"
。”

这个习惯你在源码里到处能看到——dom.jsview.jsmenu.js 里那些长长的 // --------- 分割线,
就是他在文件内部又划了一层"小文件"。

“代码高内聚的好处是,多大的团队协作都会很顺畅,代码提交基本上不怎么发生冲突。”

2.3 低耦合:两种依赖

依赖谁 关注点
业务无关的基础组件(开源库、公司基础平台) 稳定:① 成熟度(诞生多久、接口还调不调、缺陷多不多)② 持久性(谁在维护、社区信用、还活跃吗)
底层业务模块 ① 依赖要**“通用”尽量不要让底层专门为我定制接口
② 依赖的
接口个数少、调用频次低**

“不要让底层业务模块专门为我定制接口” 这条很实在——
一旦底层为你开了个专用口子,它就跟你绑死了,别人再来用会发现这个模块长得很怪。


三 · 各种「使用界面」其实是同一件事

组织单元 它的使用界面是
函数 函数原型(函数名 + 输入参数 + 输出结果)
公开属性 + 公开方法
包 / 动态库 导出的符号。包对开发者友好,动态库跨语言但只能取语言间的共性部分
网络服务 网络协议(29 讲那张七条的路由表)
命令行程序 命令行参数 + stdin + stdout
桌面程序 用户的操作方式——而且"最重要的不是界面外观,是交互范式"
子系统 一个逻辑概念,物理上不存在。通常 = 根模块的接口

它们的共性:都在定义"怎么完成业务需求",只是需求满足方式的层次不一样。
类和函数靠语言级调用,网络服务靠 RPC 请求,桌面程序靠用户交互

理解了这点,"接口应尽可能符合业务需求的自然预期"这句话就不只是在讲函数命名了——
它同样在讲你的 URL 怎么设计、你的命令行开关怎么起名、你的界面交互怎么组织。

顺带一句关于桌面程序的

“桌面程序的界面外观当然是重要的,但不是最重要的。最重要的是交互范式
即用户如何完成功能的业务流程的定义。为什么我们需要专门引入产品经理这样的角色来定义产品,
正是因为使用界面的重要性。


四 · ★ v01 vs v26:不用 MVC 会怎样

作者掏出了一个之前没给过的东西:把画图程序所有代码(HTML + JavaScript)揉在一个文件里的版本

4.1 真实行数(我数的)

v01(全揉一起)        470 行   1 个文件
v26(MVC 拆开):
    dom.js         Model         109 行
    view.js        View          112 行
    accel/menu.js  Controller     86 行
    creator/path.js       Controller  90 行
    creator/freepath.js   Controller  71 行
    creator/rect.js       Controller 108 行
    index.htm      总控            18 行
    ──────────────────────────────────
    合计                          594 行   7 个文件

差额:+124 行

(原文估的是 +110,因为他把 dom.js 记成了 100 行。结论不变。

4.2 所以 MVC 换来了什么

这说明 MVC 架构的价值并不是给我们降低总代码行数。
实际上,它关注的重点是如何让我们团队协同作战,让工作并行。

“v26 版本我们把功能分拆为 6 个文件,可以交给 6 个团队成员来做,平均每个人写 100 行左右的代码。”

多写 124 行,换的是"6 个人可以同时动手,而且基本不冲突"。

作者自己也承认这个规模有点小题大做:

对于总体代码量 500 行不到的一个程序来说,这多多少少显得有点小题大做。
但我们在此之后演进迭代了多个版本,功能越来越复杂,分工的必要性也就越来越大。”

而带读-08 那组数字正是这句话的验证:五轮迭代后前端涨到 1513 行,
而三个原有 Creator 从 269 变成 256——各自的作者从头到尾没被别人打扰过。

4.3 作者建议的三个对比维度

拿 v01 和 v26 对着读,看这三点:

# 看什么
1 功能的高内聚——某个功能的代码被分散在多少地方
2 功能间的低耦合——v01 全揉在一起,从"如何做系统分解"的视角推演 v26 用 MVC 的意义
3 怎么减少全局变量,为控件化做好准备(这条正好接 31 讲,见 带读-09 · 六

五 · 为什么会形成 MVC:稳定点 vs 变化点

这一节把 26 讲那个架子的来历讲清楚了。

“我们第一章探讨需求分析时反复强调一点:要分清需求的稳定点和变化点。
稳定点是系统的核心能力,而变化点则需要做好开放性设计。

对应什么 为什么
Model 稳定点:业务的核心逻辑 “除非出现新的技术革命导致产品的内在逻辑发生质变”
View 变化点一:屏幕尺寸 更小的屏幕意味着信息要被更高效地组织
Controller 变化点二:交互方式 鼠标交互和多点触摸是完全不同的

这也解释了三层的"数量"为什么不一样(带读-03 · 3.1 那张依赖图):

Model 层是一个整体……View 层也是一个整体,但在不同屏幕尺寸和平台可能有不同实现,
但数量不会太多……Controller 层并不是一个整体,它是以插件化的形式存在,不同 Controller 非常独立。”

“比如创建矩形,在 PC 鼠标+键盘下有一个 RectCreator
在触摸屏的交互方式可以是一个全新的 RectCreator。在不同平台下,
我们可以初始化不同的 Controller 实例来适应该平台的交互方式。”

而 Model 自己也有变化点:存储和网络。

“不过无论是存储还是网络,从架构视角来说变化都是可预期的。存储介质会变,网络技术会变,
但是变的只是实现,它们的使用接口并没变化。这意味着 Model 层不只是核心逻辑稳定,
IO 和网络子系统也都很稳定。当然这也是把它们归于 Model 层的原因。
如果它们是易变的,可能就被从 Model 层独立出去了。

这句回答了带读-05 那个疑问——为什么 28 讲把 localStorage 存取、29/30 讲把服务端通讯
全都塞进 Model因为它们的使用接口稳定。
"什么该放进 Model"的判据,在这里给全了:稳定的放进去,易变的独立出去。


六 · ★ AI coding 之后,这些边界怎么变了

⚠️ 本节是我们自己的引申,不是原文内容。 32 讲写于 AI coding 普及之前。

32 讲的方法论有几条建立在一个前提上:写代码是有成本的,而且是由多个人分工写的。
这两条现在都动了。但不是全面模糊——有的松了,有的反而更硬了。

6.1 松掉的三条

① "让团队并行"这个理由被削弱了

32 讲最直白的那句:多写 124 行,换"6 个人各写 100 行"。
而现在写代码的可能是 1 个人 + AI——AI 不需要并行,它一次能读完 594 行。

② "该不该分"的经济账变了

原文说"500 行不到的程序分层显得小题大做",是因为多写 124 行有成本
现在这个成本几乎塌了。于是常见的现象是:

让 AI 生成一堆分层,分了,但没想清楚为什么分——形式上有 MVC,实质是伪分层。

AI 特别擅长生成"符合模式"的代码。 你说"用 MVC",它就给你三个文件夹。
但它不知道你的业务哪里是稳定点、哪里是变化点——而第五节刚说了,
MVC 之所以长成那样,正是因为"业务核心稳定、用户交互多变"。

AI 能复制形式,复制不了那个判断。

③ 那"三分之一的精力"最容易被跳过

“没有需求分析,就没有业务架构。在业务架构过程中,需求分析至少应该花费三分之一以上的精力。

AI coding 最容易跳过的就是这一步——你说个需求它直接开写。三分之一变成了零。

6.2 反而更硬的三条

① 「代码即文档」成了字面真理

代码即文档。代码是理解一致性更强的文档。

因为 AI 真的只读代码。 你写在文档系统里的设计说明它看不到;你写在接口里的意图它读得到。

"接口自然体现业务需求"从一条好习惯,变成了"AI 能不能帮上忙"的前提条件。
一个叫 handleData2 的函数,AI 跟人一样懵。

② 高内聚从"协作顺畅"变成了"AI 改得对不对"

原文给的理由是"团队协作顺畅、提交不冲突"。现在多了一条更直接的:

AI 的上下文是有限的。功能散落各处 = 改一处要先读一堆无关文件。

32 讲那个"一个功能尽可能单独一个文件"的习惯,
现在直接换算成"AI 能不能只读一个文件就改对"。

③ 低耦合里那条"不要让底层为我定制接口"更要紧了

因为 AI 会很乐意帮你在底层开专用口子——它不知道那个模块还有别的使用者。
这类"局部最优、全局变形"的改动,AI 生成起来毫无阻力。

6.3 新冒出来的一条边界:什么自己想,什么给 AI 写

这是 32 讲那个年代没有的问题。 而答案恰好就在这一讲最狠的那句话里:

你都不需要看实现细节,只需要看他定义的模块、类和函数的使用接口。

分解 = 定接口          ← 【必须自己想】
实现 = 填接口          ← 【可以给 AI】
谁做 为什么
需求分析、划稳定点/变化点 AI 不知道你的业务哪里会变
定模块边界、定接口 这就是"系统分解"本身,是架构工作的全部
填实现 AI 32 讲说的"不需要看实现细节"
写测试、写 mock AI 见下

6.4 反过来:有一件事 AI 让它更可行了

32 讲说:

为了降低风险,系统的概要设计阶段也应该有代码产出。
其一,系统的初始框架代码;其二,原型性的代码来验证。一些核心子系统在这个阶段提供了 mock 的系统。

这正是 29 讲那个"先做 Mock 版服务端"的决策(带读-06 · 四)。
当年那个决策要权衡"值不值得多花几天",现在写 mock 的成本几乎为零——权衡基本没有了。

所以 AI coding 不是让 32 讲过时,是让它的天平换了个方向:
"想清楚"的相对成本上升了,"写出来"的相对成本下降了。
于是"先做个 mock 验证一下"这种以前嫌贵的做法,现在应该成为默认选项。

6.5 一句话收口

32 讲的方法论没有失效,失效的是它的"成本假设"。

  • "分不分"的账变了——分层不再贵,所以更容易分错(分了但没想清楚)
  • "想清楚"的账没变——稳定点在哪、接口该长什么样,AI 替不了
  • 而"代码即文档"从建议变成了硬约束——因为读你代码的现在还有 AI

七 · 验收

# 问题 答案在
1 判断"分解得好不好",最朴素的两条标准是什么?
2 函数、类、网络服务、命令行、桌面程序,它们的"使用界面"分别是什么?共性是什么?
3 v01(470 行,一个文件)vs v26(594 行,七个文件)——MVC 换来了什么?
4 为什么 Model 是"一个整体",而 Controller 是"复数、插件式"的?
5 为什么把 localStorage 存取、服务端通讯都塞进 Model

答案

1.功能的使用界面(接口)应尽可能符合业务需求对它的自然预期
功能实现要高内聚,功能之间耦合尽可能低。而且每一层的分解都遵循同一套路

2. 函数→函数原型;类→公开属性和方法;包/动态库→导出的符号;
网络服务→网络协议;命令行→参数 + stdin/stdout;桌面程序→用户的交互范式
共性:都在定义"怎么完成业务需求",只是需求满足方式的层次不同——
语言级调用 / RPC 请求 / 用户交互。

3. 不是降低代码量(反而多了 124 行),换的是**“6 个文件可以交给 6 个人并行做,而且基本不冲突”
作者自己也承认 500 行的规模"多少显得小题大做",但
后面五轮迭代验证了它**:
三个 Creator 从 269 变成 256,各自的作者从头到尾没被打扰。

4. 因为它们对应的东西不同:Model 是稳定点(业务核心逻辑),一个业务只有一套核心逻辑;
Controller 是变化点(交互方式),而交互方式随平台而异——
PC 的 RectCreator 和触摸屏的 RectCreator 可以是两个完全不同的实现,
所以它必须是插件式的复数,按平台装不同的。

5. 因为它们的使用接口是稳定的。原文:“存储介质会变,网络技术会变,
但变的只是实现,它们的使用接口并没变化……如果它们是易变的,可能就被从 Model 层独立出去了。”

判据:稳定的放进 Model,易变的独立出去。


下一步

33 讲:桌面开发篇的回顾与总结——整章收尾。

再往后 41~44 讲会把 29 讲那个 Mock 服务端做成正式版(数据库、多租户、高可靠)。

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