oss

Rich Hickey 最简单的架构检验:你把哪些东西编结在了一起?

8 条来源 6 条一手来源 已翻译 2026年7月24号

正文
2009 年的一场软件活动上,Rich Hickey 站在讲台旁,身边的投影幻灯片标题为 Chunked Sequences。

Clojure 创始人 Rich Hickey 于 2009 年 6 月演讲,比本文注解的 Strange Loop 演讲早两年。摄影:Howard Lewis Ship。[8]

视频模式

本文包含 1 个可跳转的视频片段。

  1. 1 Rich Hickey 讲解关注点分离如何让软件更易理解和修改 YouTube 视频

软件团队常把一套系统称作“简单”,依据往往是安装配置很快、语法很熟悉,或者演示内容一屏就能放下。这些特征各有价值,却很少透露系统面对变更时会发生什么。即使工具只需一条命令即可启动,它仍会把存储、策略、时序和故障处理紧紧绑在一起,令每次修订都演变成协同迁移。

Rich Hickey 在 2011 年 Strange Loop 大会上的演讲 Simple Made Easy 给出了一套更精确的词汇。“容易”(easy)讲的是距离,也就是一个人已经掌握或很快就能取得什么;“简单”(simple)讲的是结构,也就是各项关注点保持分离的程度,以及它们被编结在一起的程度。大会官网把这场演讲概括为一项主张:以可组合的部件写出健壮而灵活的软件;InfoQ 同期发布的笔记则保留了演讲顺序与时间点。[1][2][3]

这场演讲值得重看,最有价值的是一套早在团队选择语言或框架之前就能使用的审查方法。观看时可留意三个动作:Hickey 把一个含混的形容词拆成两个坐标轴,点出常见的纠缠形式,继而用做什么、谁、如何、何时、何处和为何这一组问题,让抽象概念落到设计审查中。最终留下的是审视变更耦合的方法,重点从给编程范式排座次移到了具体的联动关系。

图片背景:主图摄于 2009 年 6 月,画面中的 Hickey 正在演讲,比 2011 年的 Strange Loop 演讲早两年。[2][8]

2:30:两个坐标轴取代一个含混的形容词

Hickey 开场所作的区分,作为一份可操作的术语表最有用;若围着字典定义争高下,反而会错过它的用途。容易属于工具与人之间的关系,包括安装、熟悉程度、文档和本地可得性。简单属于制品本身,衡量其中穿插了多少彼此独立的工作。[1][3] 于是,一项东西可以容易上手却结构复杂,也可以陌生却结构简单。

技术审查因这一区分而更准确,各种收益可以各归其位。“所有人都熟悉这套框架”是合理的人力配置与交付依据。至于它能否划出可独立变化的各个部分,仍要另行查验。反过来,一套精简的代数体系或不可变数据模型会带来早期学习成本,同时减少推理后续行为时需要掌握的事实数量。团队应分别计算两方面的成本并明列取舍;若只用“简单”一词,取舍便会藏起来。

这里有一项具体检验。先列出系统预期会遇到的变更:调整验证规则、更换数据库、重放事件、把工作移入队列、增加第二个客户端,或修订重试策略。随后逐项追问,每一处变更还会牵动哪些决定。一项策略修订若迫使传输、持久化、调度和呈现同时改动,问题已经超出代码体量:这些关注点已被编结在一起。审查追求的是 被迫共同变化 的事项最少,组件数量本身居于其次。

约 28:50:这份清单旨在挑起追问,记分卡会把它读窄

演讲的中心表格把状态、对象、方法、继承、命令式循环、actor 和对象关系映射放在复杂一侧,把值、函数、命名空间、数据、协议、队列和声明式系统放在较简单一侧。[1][3] 按字面照读,它很像一条普遍禁用主流构造的命令。把它当作探照灯时,作用反而更强:每一项都在追问,某种构造通常会把哪些方面接合起来。

对象可以把身份、当前值和行为合在一起。方法可以把函数、隐藏状态和命名空间合在一起。命令式循环可以把待计算的结果与计算所用的确切顺序合在一起。这些组合本身不能判定程序优劣,却会扩大需要一并理解和修改的单元。审查要辨明的是,这种耦合究竟来自问题本身,还是来自所选做法。

Hickey 的语言设计把这项偏好写得很具体。Clojure 是运行于 JVM 的宿主式 Lisp,采用不可变的持久化数据结构,也提供显式手段来管理变更。[4] 它的状态模型把身份与当前关联的值分开:值不会原地变化,引用则管理一个值向另一个值的转换。[5] 协议同样把一组具名操作与具体实现分开,并可在类层级之外扩展。[6]

复杂性仍然存在。持久化数据会消耗资源;引用类型带有并发语义;协议会被草率扩展;JVM 与外部服务各有自己的时钟和故障模式。它们对架构的贡献范围更窄,也更经得起检验:一些接合处因此清晰可见。值可以独立流转并接受检查,可变身份留在原处。讨论抽象时,具体做法可以留到以后。变更依然存在,却有了明确地址。

35:40:状态显露出隐藏的时钟

这场演讲经久耐用的一项诊断是,可变状态把值与时间接在了一起。读取一个字段时,“这是什么?”只覆盖一部分;还要追问“它是在何时被观察到的?”“谁能修改它?”以及“在那一刻,还有哪些东西必须保持一致?”[1][3] 只要调用方仍然共享同一个变化位置的访问权,模块边界也会把这些问题保留下来。

它的影响远远超出函数式语言之争。标签可被覆盖的部署清单、带有隐含到期时间的缓存条目、原地更新的数据库行,以及在不同时刻读取的功能开关,都会把信息与一只时钟拴在一起。团队经常确实需要状态,绝大多数有用的系统皆如此。设计要决定的是,历史与协调留在隐式副作用里,还是明确进入模型。

Moseley 与 Marks 在 Out of the Tar Pit 中独立提出了一项彼此呼应的论证:状态和显式控制是偶然复杂性的主要来源,因此,应把必要状态同逻辑以及面向性能的控制分开。[7] 实践范围可以比“把每次变化都送上事件总线”克制得多:尽量保留稳定的值,只隔离真正发生变化的一小组身份,并为每次转换赋予具名操作和可观察的失败行为。

对于小型服务,这可以是一份不可变请求值:它先经过无副作用的验证与计算,再由一次存储库调用修改持久状态。对于更大的平台,则可以采用版本化配置、只追加的事实、幂等键,以及用队列将调度同领域逻辑隔开。引入的设施越多,对运维成熟度的要求也越高。团队若缺少延迟监控、安全重放和 schema 治理能力,以“简单”之名引入分布式日志,编结点便只是从代码挪到了运维中。

从 49:17 起:抽象化为六个审查问题

在结尾部分,Hickey 用做什么、谁、如何、何时、何处和为何来组织设计。[1][3] 这个次序很实用,因为架构讨论常常先从“如何”开始——先谈框架、数据库或部署目标——此时其他几个方面还混在一起。

先写清 what(做什么):表达能力与不变量,暂时搁下具体做法。再辨认 who(谁):有哪些值或实体参与,让它们各自只携带所需行为。把 how(如何) 留到后面,让具名能力背后的方案可以替换。让 when(何时)where(何处) 各自对应调度和部署位置,业务函数只保留业务本身。策略,也就是 why(为何),则保持集中可见;散落的条件分支会冲淡它的来由。

这六个问题首先要求语义上的分离,物理拆分随后再决定,服务和接口文件的数量另行计算。单个进程可以守住这种分离;微服务集群也会因共享 schema、同步调用链和协同发布而把六项重新缠在一起。检验标准是,读者能否单独解释其中一项,以及团队能否单独修改它,省去全系统重写的协调。

即使其中提到的工具已显岁月,Simple Made Easy 仍然保有价值。它用一套词汇把审美偏好变成可证伪的问题:哪些决定被迫一起移动?熟悉程度可以借学习与工具改善;必要复杂性可以记录并收束。一旦互不相关的关注点被编进同一制品,依赖便会照旧存在,较短的语法和较快的脚手架只触及表面。最简单且有用的一步,是在添上另一股线之前先找到编结处。

来源

  1. Strange Loop 大会,“Simple Made Easy — Rich Hickey”(2011)——大会演讲嵌入视频。
  2. Strange Loop,“Simple Made Easy”——2011 年官方场次页面与演讲者资料来源。
  3. InfoQ,“Simple Made Easy”——独立的演讲摘要、要点与带时间戳的节目笔记。
  4. Clojure,“Rationale”——关于该语言的 JVM 宿主模型、不可变持久化数据结构和并发处理方式的官方说明。
  5. Clojure,“Values and Change: Clojure's approach to Identity and State”——关于不可变值、身份、引用和受约束转换的官方说明。
  6. Clojure,“Protocols”——关于如何将具名抽象同实现分开,以及如何在继承之外扩展类型的官方参考。
  7. Ben Moseley 与 Peter Marks,“Out of the Tar Pit”(2006)——关于状态、控制和偶然复杂性的独立分析。
  8. Howard Lewis Ship,“Rich Hickey”(2009 年 6 月 3 日),Wikimedia Commons——主图来源页面。
Previous GitLab 2017 年数据库丢失事故:一份用于预发布环境的快照成了恢复方案

Recommended In oss

Matched by subject and format