HackerTube English?
软件边界与变化 →

切开代码,就切开了依赖吗?

从一条要发邮件的规则开始。先把判断和执行分开,再检查分层、对象和抽象:改动一条业务规则时,哪些地方还是必须一起改?

本文的问题 · 17
  1. 把可以独立的职责真正拆开
  2. 把副作用集中起来,让其余行为更容易推理
  3. 不必更换语言,也能隔离副作用
  4. 值的语义,如何落实为模块边界?
  5. 形式分层不足以消除语义纠缠
  6. 小组件与值边界都在减少彼此耦合
  7. 提防错误抽象,也要让必要抽象足够深
  8. 都在写类,却对面向对象给出不同建议
  9. 从识别结构纠缠,退回到审视构思
  10. 拆成两个类以后,怎样判断真的拆开了?
  11. 谁选择给人看的候选项?
  12. 同一滴锡上的三次照射,能交换次序吗?
  13. 删掉一枚硬币,需要修改每张价格标签吗?
  14. 一套规则可以稳定产生什么结果?
  15. 隔离规则之后,哪些决定可以晚一点做?
  16. 拆出的对象和文件,分别替谁承担变化?
  17. 入口统一以后,谁少维护一项,谁多一项协调?

把可以独立的职责真正拆开

Simple Made Easy 建议辨认原本可独立却被纠缠的部分;Boundaries 将计算核心与命令式外壳分开,是可以用来检验这一原则的具体架构例子。

对照原文与时间戳 · 2
start seeing interconnections between things that could be independent
"Simple Made Easy" - Rich Hickey (2011) · ▶ 59:28–59:32
an imperative shell that surrounds the functional core.
Ruby Conf 12 - Boundaries by Gary Bernhardt · ▶ 14:48–14:54

这是跨资源分析,不是讲者宣称的继承关系;拆层的价值取决于真实依赖,而不是增加模块数量。

把副作用集中起来,让其余行为更容易推理

Overwatch 的 ECS 讲解指出主要行为副作用集中在一个调用侧;Bernhardt 则把决策留给函数式核心,并由命令式外壳处理数据库和邮件。合读可看到两种架构都通过明确副作用边界,降低理解和测试时需要追踪的交互范围。

对照原文与时间戳 · 2
all major behavioral side effects are localized to that call side
Overwatch Gameplay Architecture and Netcode · ▶ 18:20–18:26
an imperative shell that surrounds the functional core.
Ruby Conf 12 - Boundaries by Gary Bernhardt · ▶ 14:48–14:54

ECS 的系统调用组织与函数式核心/命令式外壳不是同一种模式;不能据此认为所有副作用都应放进单一函数或单一文件。

不必更换语言,也能隔离副作用

函数式是否主流的演讲把语言流行与无突变风格区分开;Boundaries 在 Ruby 中用值驱动的核心处理决策、命令外壳承接 I/O,提供无需先改语言的具体做法。

对照原文与时间戳 · 2
no language features are required to program this style
Why Isn't Functional Programming the Norm? – Richard Feldman · ▶ 36:07–36:18
the expired users class makes all the decisions and the sweeper class has all the dependencies.
Ruby Conf 12 - Boundaries by Gary Bernhardt · ▶ 15:28–15:38

Ruby 不会自动禁止突变;该设计靠边界纪律,且外壳仍保留必要副作用。

值的语义,如何落实为模块边界?

Hickey 强调不可变和语义透明;Bernhardt 展示以值作为交互边界,减少同步方法调用及测试替身的耦合。前者解释值的性质,后者给出组织代码的具体策略。

对照原文与时间戳 · 2
values are immutable right and values are semantically transparent
The Value of Values with Rich Hickey · ▶ 10:43–10:54
we now have values as boundaries.
Ruby Conf 12 - Boundaries by Gary Bernhardt · ▶ 10:17–10:24

值并不自动保证整个系统纯函数化;I/O 仍留在外壳。两位讲者没有在这些片段中直接互相回应。

形式分层不足以消除语义纠缠

Simple Made Easy警告分开类与层次并不自动带来简单;Maybe Not用schema示例指出把数据形状和调用场景的可选性绑定会造成复用困难,修正需要分离这两个语义维度。

对照原文与时间戳 · 2
it's a mistake to put optionality in aggregate definitions
Maybe Not - Rich Hickey · ▶ 30:36–31:48
don't associate simplicity with partitioning and stratification, right?
"Simple Made Easy" - Rich Hickey (2011) · ▶ 34:50–35:20

不主张任何模块拆分都无效;Maybe Not讨论特定信息约束建模。

小组件与值边界都在减少彼此耦合

Metz 建议把代码拆成更小的东西;Bernhardt 说明以值作为对象边界,可以替代组件之间的同步方法调用。合读可把“缩小组件规模”和“选择低耦合交互方式”看作两个不同但互补的边界设计手段。

对照原文与时间戳 · 2
make smaller things
RailsConf 2014 - All the Little Things by Sandi Metz · ▶ 0:54–0:58
we now have values as boundaries.
Ruby Conf 12 - Boundaries by Gary Bernhardt · ▶ 10:17–10:24

小类或小方法不自动形成良好边界;值边界的好处取决于交互是否适合通过数据传递表达。

提防错误抽象,也要让必要抽象足够深

Metz 提醒保留重复有时比维护错误抽象便宜;Ousterhout 则强调深模块以简单接口隐藏大量能力。把两者并读可帮助判断抽象质量:抽象不应只是消除重复,而应真正提供稳定、足够有价值的能力边界。

对照原文与时间戳 · 2
It is far cheaper to keep duplication than it is to have to mess with the wrong abstraction.
RailsConf 2014 - All the Little Things by Sandi Metz · ▶ 14:52–14:59
very simple interface with a very large amount of functionality underneath it
A Philosophy of Software Design | John Ousterhout | Talks at Google · ▶ 14:24–14:29

两位讲者没有互相回应。Metz 反对的是错误抽象,并未主张避免一切抽象;Ousterhout 也没有说每段重复代码都应被抽成模块。

都在写类,却对面向对象给出不同建议

Metz 建议把类、方法和相互依赖变小;另一位讲者主张转向过程式风格,却明确说问题并不在类本身。两个片段帮助区分语言构造与组织代码的方式。

对照原文与时间戳 · 2
make smaller things
RailsConf 2014 - All the Little Things by Sandi Metz · ▶ 0:54–0:58
the problem is really not classes per se
Object-Oriented Programming is Bad · ▶ 1:37–1:42

不能从标题推断两人对每种类设计都直接对立;这些片段不构成相互回应。

从识别结构纠缠,退回到审视构思

Simple Made Easy 让我们检查概念之间不必要的关联;Hammock 则提醒许多严重问题源于错误构思。两者把关注点从更快地写出代码移向先理解问题与结构。

对照原文与时间戳 · 2
start seeing interconnections between things that could be independent
"Simple Made Easy" - Rich Hickey (2011) · ▶ 59:28–59:32
most of the big problems we have with software are problems of misconception
Hammock Driven Development - Rich Hickey · ▶ 4:27–4:36

两段分别谈系统结构与问题求解,不可把所有构思错误归因为耦合,也不是效率提升的实证证明。

拆成两个类以后,怎样判断真的拆开了?

先用设计讲座里的做法写下障碍:每次换邮件服务都得改到期计算。熟悉的新库也许很好用,但《Simplicity Matters》提醒我们,熟悉程度不说明两个职责是否还纠缠。Sierra 把依赖显式交给系统装配,Hickey 的 transducer 则把逐项处理从承载它的集合或通道抽出。可用两次替换来检验:替换服务而不改判断,再把同一判断放在另一条输入流上;哪一次还得重写规则?

原文与时间戳 · 4
describing, diagnosing, delimiting the problem, choosing a direction at a high level
"Design in Practice" by Rich Hickey · ▶ 21:30–22:46 · 段落范围
It is really about the interleaving, not the cardinality.
Rails Conf 2012 Keynote: Simplicity Matters by Rich Hickey · ▶ 2:22–3:34 · 段落范围
I'm going to do is declare my dependencies and they'll get filled in later
Stuart Sierra - Components Just Enough Structure · ▶ 17:23–17:40
the exact same process bags I defined here, this concrete thing being reused in a completely different context
"Transducers" by Rich Hickey · ▶ 11:23–14:43 · 段落范围

谁选择给人看的候选项?

Urmson 的车遇到可能迈步的行人,可以先放慢速度再观察。这里动作的作用之一是给下一次判断买时间。Suarez 谈到另一种决策边界:如果传感器产生的数据多到人看不完,机器必须先选择给人看什么,人保留最后批准权也只能批准已进入队列的对象。读这两段时,可以把「收集新证据」「选择候选」「批准行动」画成三步,然后逐步检查是谁能改变下一步看到的东西。

原文与时间戳 · 2
Whereas a self-driving system can look at that pedestrian and say, I don't know what they're about to do, slow down, take a better look, and then react appropriately after that.
Chris Urmson: How a driverless car sees the road · ▶ 7:30–7:39
drones will tell humans what to look at, not the other way around.
Daniel Suarez: The kill decision shouldn't belong to a robot · ▶ 4:28–4:33

同一滴锡上的三次照射,能交换次序吗?

这台极紫外光源先改变锡滴的形状,再将整形后的靶转为等离子体。把每次脉冲都记成一个功率数字,会丢掉前一步为后一步准备了什么。画出每一步前后的锡滴形状,再问「把两步换序,下一束光遇到的还是同一个靶吗?」

原文与时间戳 · 2
It hits one tiny tin droplet three times in a row
The Closest Thing We Have to Alien Technology · ▶ 1:48–1:51
where you evaporate the pancake and turn it into a plasma.
The Closest Thing We Have to Alien Technology · ▶ 37:21–37:25

删掉一枚硬币,需要修改每张价格标签吗?

Grey 讨论的关键决定在结算时。先把购物篮内按分计价的商品相加,再对现金总额舍入,与先把每件商品舍入再相加会给出不同数值。用三件各自有尾数的商品算两遍,就能检验一条政策说明是否把舍入放在了正确步骤。

原文与时间戳 · 2
round to the nearest 5 cents for cash transactions
Death to Pennies · ▶ 3:05–3:08
automatically rounding to the nearest five cents.
Death to Pennies · ▶ 3:55–4:01

一套规则可以稳定产生什么结果?

Grey 用陪审团的例子区分程序规定的职责,以及不同规则一起运作后哪些结果可能在实践中维持。先把每条规定写成“谁能在何时改变谁的决定”,再沿无罪与有罪两条路径各走一次。这是对影片所述制度的分析;用来理解某地真实案件时,还需核对适用的具体程序。

原文与时间戳 · 2
juries can't be punished for a wrong decision
Watching This Video Disqualifies You From Jury Duty for Life · ▶ 0:59–1:02
their not-guilty decision can't be changed.
Watching This Video Disqualifies You From Jury Duty for Life · ▶ 1:21–1:24

隔离规则之后,哪些决定可以晚一点做?

Rich Hickey 把复杂性与原本可以独立的事物之间的纠缠联系起来。Robert C. Martin 回忆 FitNesse 团队曾用内存和文件保存数据,让应用行为可以先开发,等出现实际需求再选数据库。Rafal Dittwald 的纸牌例子把计算下一回合和打印战报分开,使计算结果可以脱离输出步骤检查。

把同一条规则放进两个问题里检查。换一种存储时,规则能否照常执行?改变要输出的内容时,规则与执行输出的代码各要改哪里?第一个问题检查实现选择能否推迟,第二个问题检查副作用是否仍然与规则绑在一起。

原文与时间戳 · 3
interconnections between things that could be independent
"Simple Made Easy" - Rich Hickey (2011) · ▶ 59:28–59:32
a good architecture allows major decisions to be deferred
Ruby Midwest 2011 - Keynote: Architecture the Lost Years by Robert Martin · ▶ 56:09–56:20
minimizing concentrating and deferring
Solving Problems the Clojure Way - Rafal Dittwald · ▶ 21:47–21:53

拆出的对象和文件,分别替谁承担变化?

Pete Hunt 用表格隔行着色说明,模板和控制器分处两地,改一种显示方式仍可能要修改它们共同约定的字段。Jack Diederich 从另一头检查边界。他举的问候对象只保存一个参数、调用一次方法,函数就能表达它要做的事;如果一组操作确实共同维护一份可变数据,他认为类就有作用。

选一项将要发生的修改,列出必须随它改变的数据和规则。这比数文件或类更容易看出,拆分之后哪些改动仍会一起发生。

原文与时间戳 · 3
templates separate technologies and not concerns.
Pete Hunt: React: Rethinking best practices -- JSConf EU · ▶ 6:20–6:28
you should have just written a function
Stop Writing Classes · ▶ 3:12–3:23
a bundle of mutable data and a bunch of related functions
Stop Writing Classes · ▶ 12:59–13:10

入口统一以后,谁少维护一项,谁多一项协调?

Rails World 2024 上,Hansson 介绍用同一种数据库系统支持后台任务、缓存和 WebSocket 通信。他的目标是减少应用团队在上线时要学习和运维的组件。Muratori 的提议落在另一层。他想为整套片上系统规定可直接编程的稳定接口,并在问答里指出,厂商就共同接口达成一致要花时间。两段论证各自点出一个承担方。评估统一方案时,可以同时问,使用者可以少维护哪些东西,以及以后要修改这个约定时,哪些提供者需要一起行动?

原文与时间戳 · 2
one thing to learn one thing to operate
Rails World 2024 Opening Keynote - David Heinemeier Hansson · ▶ 41:42–41:54
people have to agree and agreeing on things takes time
The Thirty Million Line Problem · ▶ 105:29–105:37

读完再想一步

画出一次规则变更会波及的地方,再看文件边界是否与它重合。