题记与说明
- 日期:2026-11-05
- 条目前缀:knowledge-d2f37j93-0
- 主题:以 Rich Hickey《Simple Made Easy》(Strange Loop 2011) 为主文本,与 John Ousterhout《A Philosophy of Software Design》对照,提炼"简单 vs 容易"的可操作工程判据。
0. 来源与精读方式(本笔记的证据基础)
| # | 来源 | 精读程度 | 链接 |
|---|---|---|---|
| 1 | Rich Hickey, *Simple Made Easy*, Strange Loop 2011 —— matthiasn/talk-transcripts 逐字稿(473 行,含时间戳) | 全文精读(本次已完整读毕) | https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md (raw: https://raw.githubusercontent.com/matthiasn/talk-transcripts/master/Hickey_Rich/SimpleMadeEasy.md );视频 https://www.youtube.com/watch?v=SxdOUGdseq4 |
| 2 | dev.to:A Philosophy of Software Design 逐章摘要 | 要点精读(引述经转述) | https://dev.to/markadel/a-philosophy-of-software-design-summary-pk9 |
| 3 | Jan Meppe:Book Notes, A Philosophy of Software Design | 要点精读(多条逐字引语) | https://www.janmeppe.com/blog/a-philosophy-of-software-design-john-ousterhout/ |
| 4 | Pragmatic Engineer:John Ousterhout 访谈页 | 部分精读(页面为摘要/时间轴,非逐字稿,仅取其转述要点) | https://newsletter.pragmaticengineer.com/p/the-philosophy-of-software-design |
| 5 | typesanitizer:*A meta-analysis of three different notions of software complexity*(对 Hickey / Ousterhout / Tellman 三种复杂性定义的对照批评) | 要点精读(第三方对照文献) | https://typesanitizer.com/blog/complexity-definitions.html |
未验证/受限点(诚实声明):
- InfoQ 官方摘要页(https://www.infoq.com/presentations/Simple-Made-Easy/ )返回 HTTP 405,未能读取;talk 内容以 GitHub 逐字稿为准。
- Ousterhout 原书本次未直接阅读,其"逐字引语"均来自来源 2/3/5 的转引(其中复杂性的定义句在 Jan Meppe 笔记与 typesanitizer 两处以逐字形式一致出现,可信度较高);其余 Ousterhout 观点标注为"据转述"。
- Pragmatic Engineer 访谈无逐字稿,仅取页面转述(如 "tactical tornado" 的比喻、Tk "design it twice" 案例)。
1. 演讲精读:《Simple Made Easy》(逐字稿引述均标注行号/时间戳)
1.1 词源定义:simple ≠ easy
- 开篇借 Dijkstra 立论:"Simplicity is a prerequisite for reliability."(L21, 00:01:00)
- simple 词根 sim-plex = *one fold/braid*:"one braid or twist";complex = "braided together or folded together. Being able to think about our software in terms of whether or not it's folded together is sort of the central point of this talk."(L27–29)
- easy 词源溯至 *adjacent* = "to lie near"(近便);反义词 hard 与"远"无关,而是"strong, tortuous"(L31)。
- simple 的操作性定义:一个 role / 一个 task / 一个 objective / 一个 concept / 一个维度;但关键不是数量:"What matters for simplicity is that there is no interleaving, not that there's only one thing."(L39, 00:03:19–05:16)——区分 cardinality(数一数)与 interleaving(缠没缠)。
1.2 客观 vs 相对:本演讲的第一根钉子
- "if something is interleaved or not, that's sort of an objective thing. You can probably go and look and see... simple is actually an objective notion."(L41)
- easy 三义:near at hand(装了就能跑)、familiar(接近已会的东西)、near to our capabilities(在能力圈内)。"easy is relative... easy for whom, or hard for whom?" 而 simple 可以客观查验(L45–57, 00:05:16)。
- 熟悉癖的代价:"if you want everything to be familiar, you will never learn anything new."(L51)
1.3 construct vs artifact:评估对象错了
- "We don't ship source code... the running of it, the performance of it, the ability to change it all is an attribute of the artifact, not the original construct."(L65, 00:09:06)
- 雇主喜欢"容易"(熟悉工具、可替换的程序员),但用户只面对工件。判据应建立在工件属性上:能不能靠、能不能修、能不能改(L73)。
1.4 理解的极限与交织的组合负担
- "when things are intertwined together, we lose the ability to take them in isolation... every intertwining is adding this burden, and the burden is kind of combinatorial."(L89, 00:12:14)
- 杂耍类比:"The average juggler can do three balls. The most amazing juggler in the world can do, like, 9 balls"——相对于能造出的复杂度,"We're all statistically at the same point in our ability to understand it, which is not very good."(L188, 00:22:35)→ "We have to make things near by simplifying them"(变聪明不行,只能把东西变简单)。
1.5 护栏编程与速度幻觉
- 每个线上 bug 的共性:"It passed the type checker. ... It passed all the tests."(L118–122, 00:14:07–15:32)
- "guardrail programming":"Who drives their car around banging against the guardrail saying, 'Whoa! I'm glad I've got these guardrails...'"(L126)护栏不指路——推理能力(informal reasoning,非证明)不可替代(L107, L130)。
- 速度:"if you ignore complexity, you will slow down. You will invariably slow down over the long haul."(L148, 00:17:14);"the complexity will eventually kill you. It will kill you in a way that will make every sprint accomplish less"(L154)——每百码重新鸣枪的"冲刺"是自欺。
1.6 incidental complexity:附带复杂度
- 工具自带的复杂度不是问题要求的部分:"We chose a tool. It had some inherent complexity in it. It's incidental to the problem. 'Incidental' is Latin for 'your fault'."(L168, 00:19:36,玩笑式定义)
- 织物城堡 vs 积木城堡:"Is having a test suite and refactoring tools going to make changing the knitted castle faster than changing the Lego castle? No way."(L178, 00:21:30)
- Perlis 名言移用:"we're looking all for benefits, and we're not looking carefully enough at the byproducts."(L220–222, 00:27:33)
1.7 复杂性工具箱 vs 简单性工具箱(L226–238, 00:28:50;L304–328)
| 复杂(complect 什么) | 简单替代 |
|---|---|
| state:complect value 与 time(L286) | values(不可变值) |
| objects:complect state, identity, value(L306) | 函数 + 数据 |
| methods:complect function 与 state(+命名空间)(L308) | functions |
| inheritance:complect types,"It's definitional."(L312) | polymorphism à la carte(protocol/type class) |
| switch / pattern matching:complect 多对"谁→做什么",且封闭(L314) | 开放式多态 |
| vars/variables:不可分离 value 与 time(L316–318) | managed references(能"compose values and time",L302) |
| syntax:complect meaning 与 order(L310) | data("It's inferior to data in every way") |
| imperative loops / fold:complect 做什么与怎么做;fold 隐含顺序(L320) | set functions |
| actors:complect what 与 who(L322) | queues |
| ORM:complect 逻辑与表示 | declarative data manipulation(SQL/LINQ/Datalog) |
| conditionals:规则散落各处、与程序结构 complect(L328) | rules(声明式规则系统) |
| inconsistency:"consistent means to stand together",不一致=把分开站的东西强行一起想(L238) | transactions + values |
1.8 complect vs compose;模块化 ≠ 简单
- "complect actually means to braid together. And compose means to place together."(L262, 00:33:07)复杂度的来源就是 complecting:"This is where complexity comes from: complecting."(L250)
- 模块化不是免罪牌:"You can write modular software with all kinds of interconnections between them. They may not call each other, but they're completely complected."(L268);"don't associate simplicity with partitioning and stratification. They don't imply it. They are enabled by it."(L278);"be particularly careful not to be fooled by code organization."(L280, 00:33:35–35:38)
- 状态的泄漏:"That complexity just leaked right out of there... It's like dropping some dark liquid into a vase."(L288–290)唯一出路是外面呈现真函数接口(same input, same output)。且明确声明:"This is not about concurrency... It's about your ability to understand your program."(L292)
1.9 为简单而抽象:who / what / when / where / why / how
- abstraction = "to draw something away",不是"just hiding stuff. That is not what abstraction is, and that's not going to help you"(L382–384, 00:49:20)——与 Ousterhout 的信息隐藏形成耐人寻味的对照(见 §3.2)。
- what(操作规格)应"really small, much smaller than what we typically see"(L394);"Strictly separating what from how is the key to making how somebody else's problem."(L400, 00:50:39)
- who(数据/实体):direct injection 风格组装;"You should have probably many more subcomponents than you have"且接口要更小(L406, 00:52:41)。
- when/where:直连调用即 complect——"If thing A calls thing B, you just complected it... Stick a queue in there."(L416–418, 00:54:40)
- why(策略/规则)应外置为声明式规则系统,而不是散落全程序的 conditionals(L424–426)。
- 信息即简单:"information: it is simple. The only thing you can possibly do with information is ruin it."(L430);"represent data is data. Please start using maps and sets directly."(L438, 00:56:10)
1.10 环境复杂度:诚实的边界
- "Inherent is Latin for not your fault."(L368, 00:47:21)GC、CPU/内存争用是固有复杂度;但真正无解的是"the policies around this stuff don't compose"——线程池大小这类资源策略每人自定时,叠加即碎(L370–374)。Hickey 承认"not one for which I have a solution at the moment"。
1.11 收束:简单是选择
- "The bottom line is simplicity is a choice. It's your fault if you don't have a simple system."(L449, 00:58:14)
- 需要养成的是"entanglement radar":看代码时找"interconnections between things that could be independent"(L455)。
- 测试/类型等可靠性工具"They're safety nets, but they're nothing more than that."(L457)
- "Simplicity is not about counting. I'd rather have more things hanging nice, straight down, not twisted together, than just a couple of things tied in a knot."(L463, 00:59:53)——回应"拆出更多小件=更复杂"的直觉。
2. 《A Philosophy of Software Design》要点(据来源 2/3/5 转引,原书未直接读)
2.1 复杂性定义与症状
- 定义(两处二手来源逐字一致):"Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system."(Ousterhout, ch.2;转引自 typesanitizer 与 Jan Meppe 笔记)
- 三个症状(dev.to / typesanitizer 引):
- Change amplification:"a seemingly simple change requires code modifications in many different places."
- Cognitive load:"how much a developer needs to know in order to complete a task."
- Unknown unknowns:"it is not obvious which pieces of code must be modified to complete a task."
- 两个成因:dependencies(代码不能孤立理解/修改)与 obscurity(重要信息不明显:含糊命名、隐式依赖、文档缺失、不一致)。
2.2 深模块与黄金法则
- "Aim to write deep modules, modules with powerful functionality but a simple interface."(Jan Meppe 引)
- 经典例证:Unix I/O "five simple system calls (open, read, write, lseek, close) that hide hundreds of thousands of lines of complex implementation code"(dev.to 转述)。
- 反面:shallow modules 与 Classitis——"classes are designed to be excessively small, based on the mistaken view that 'more classes are better'"(dev.to 转述)。
- 黄金法则:"Pull complexity downwards.";"It is more important for a module to have a simple interface than a simple implementation."(Jan Meppe 引)——这是与 Hickey 分歧最大的一句(见 §4.2)。
2.3 工作代码不够、战略编程、设计两遍
- Ch.3 "Working Code Isn't Enough":tactical programming(尽快跑起来)vs strategic programming("The primary goal is a great design that also works"),建议"Spending about 10-20% of total development time on design investments"(dev.to 转述)。
- Design it twice(Ch.11):大设计决策至少做两个方案再比选;Pragmatic Engineer 访谈页转述 Tk API 的例子——第二版显著更好。
- 注释(Ch.12–15):自文档化代码"是个神话","Comments should describe information that cannot be easily deduced or is not immediately obvious from the code itself";先写注释,"a complicated comment is a red flag"(dev.to 转述)。
- 对 TDD 的批评(Jan Meppe 逐字引):"The problem with test-driven development is that it focuses on getting specific features working, rather than finding the best design. There is no feedback loop in TDD that forces you to look at and improve your design."——与 Hickey 的 guardrail programming 高度同构。
3. 对照:两套语言下的同一战场
3.1 术语映射
| Hickey | Ousterhout | 关系 |
|---|---|---|
| complect(缠绕) | dependencies + obscurity | Hickey 只点名"纠缠";Ousterhout 把"看不出来"(obscurity)单列 |
| incidental complexity | complexity 的主要打击对象 | 同物:问题没要求、工具/设计自找的 |
| artifact 属性(长期运行/可改) | "hard to understand and modify" | 最终法庭一致:都是未来改代码的成本 |
| guardrail programming | tactical programming;TDD 无设计反馈环 | 都把"测试/类型/快速迭代"降级为安全网 |
| entanglement radar | "the ability to see complexity"(据 dev.to 转述的功课) | 都要求训练一种查看复杂度的感官 |
3.2 一致点(收敛的工程共识)
- 以"未来的理解与修改成本"为唯一法庭(Hickey L65/L73;Ousterhout 定义句)。
- 接口要小:Hickey "much smaller than what we typically see"(L394)+更多 subcomponents(L406);Ousterhout 深模块"简单接口、强大功能"。
- 组织结构不等于简单:Hickey "don't be fooled by code organization"(L280)↔ Ousterhout shallow modules / Classitis。
- 熟悉与速度是幻觉源:Hickey 的 sprint 曲线(L148–154)↔ Ousterhout 的 tactical tornado(Pragmatic Engineer 页转述,且用于批评 AI 编码工具)。
- 增量投资:Hickey "spend a little time upfront simplifying things before we get started"(L463 附近, 00:59:53)↔ Ousterhout strategic programming 的 10–20%。
- 测试/类型检查不是设计(Hickey L118–130, L457 ↔ Ousterhout TDD 批评)。
3.3 分歧点(判据要在这里做出选择)
- 复杂度藏进哪里:Ousterhout 黄金法则明确"接口简单比实现简单更重要"(藏进去);Hickey 主张构造物本身不得缠绕,且状态复杂度会像"深色液体"渗漏(L288–290)——他允许的"藏"仅限于呈现真函数接口的边界。一个治标于接口,一个治本于构件。
- 客观性主张:Hickey:simple 客观可查、easy 相对(L41/L57);typesanitizer 指出 Ousterhout 的 obscurity 是读者相对的("If someone reading your code says it's not obvious, then it's not obvious"),且"成因(obscurity)用症状(不明显)定义"有循环之嫌——Ousterhout 框架在这点上不如 Hickey 干脆。
- complect 是否无条件坏:Hickey 是;typesanitizer 反驳其不能泛化(如数据库外键正是有意的耦合),Ousterhout 则只要求依赖"少而明显"。→ 工程折中:耦合不可避免,晦涩不可原谅。
- 调试视角:Hickey 用"bug 都过了测试与类型检查"论证简单性是调试的前提;typesanitizer 指出 Ousterhout 全书"doesn't talk about debugging much at all"——简单 vs 容易的判据里,调试可达性主要来自 Hickey 侧。
- 打击对象:Hickey 点名语言构造(ORM、继承、actor、syntax);Ousterhout 不谈语言构造,只谈模块划分与信息隐藏——前者是构造选型判据,后者是结构设计判据,作用在不同粒度。
4. 提炼:简单 vs 容易的工程判据(本笔记综合,非原文)
综合判据为本次对照的产出,来源标注在每条后。判定句式尽量做成"可当场回答的问题"。
总判据(一句话版):先问"这是简单的还是只是容易的"——即"它没有缠绕,还是仅仅在我手边/我熟悉"(Hickey L41/L57);再问"它把复杂度推向了用户还是拉向了内部"(Ousterhout 黄金法则)。两个问题都过关才叫好设计。
| # | 判据 | 当场要问的问题 | 通过标准 | 出处 |
|---|---|---|---|---|
| 1 | 交织判据(客观) | 实现这个变更要碰几个本不相关的文件/概念? | 理想为 1;每多一处即一个 complect 信号 | Hickey L89/L250;Ousterhout change amplification |
| 2 | 工件判据 | 我们在评估"跑起来的软件",还是"敲代码的体验"? | 一切评审以工件属性(可靠/可修/可改)为准 | Hickey L65–L73 |
| 3 | 认知负荷判据 | 完成此任务需同时记住几件事?新成员能从代码里推出来吗? | 推不出来的信息=unknown unknowns=obscurity,必须消除 | Ousterhout 症状三;Hickey L188 杂耍类比 |
| 4 | 接口深浅判据 | 模块是否"小接口、大实现"?有没有 pass-through 浅模块/Classitis? | 接口≤其实现复杂度;每类只有 1–2 个正交抽象 | Ousterhout ch.4;Hickey L394/L406 |
| 5 | 泄漏判据(分歧区) | 内部藏的复杂度是否以隐式依赖、非明显状态漏到外面?状态是否 complect 了 value 与 time? | 藏可以,漏不行;对外的必须是 same input→same output 的值语义 | Ousterhout 黄金法则 vs Hickey L286–L290 |
| 6 | 熟悉度判据 | 我说"简单"时,是否其实在说"我熟悉"?(parens 测试:不熟悉但无交织→学习责任在我) | 区分"你的错"(不熟)与"工具的错"(真缠绕) | Hickey L51/L198–L214;L168 "incidental is Latin for your fault" |
| 7 | 速度判据 | 现在的快是靠省掉思考吗?这条曲线 6 个月后还在吗? | 允许前期爬坡(simplifying upfront / 战略编程 10–20%) | Hickey L148–L154;Ousterhout tactical/strategic |
| 8 | 守护网判据 | 测试/类型/重构工具是否被当成了设计替代品? | 它们是 safety net;bug 已证明能通过它们 | Hickey L118–L130, L457;Ousterhout TDD 批评 |
| 9 | 决策位置判据 | 策略(why)是否散落全程序?资源策略(线程池等)是否各自为政、不可组合? | 策略外置为声明式/规则化,环境策略集中决策 | Hickey L328–L336, L370–L374 |
| 10 | 数据判据 | 信息是否被包成对象/ORM,逻辑是否绑死表示? | data is data:直接用 map/set,操纵数据的通用工具才能只写一次 | Hickey L430–L438 |
使用顺序建议:1/2 先行(客观、便宜),3/5/9 是深层病灶,4/10 是结构性处方,6/7/8 用于拆穿日常争论中的语义偷换("我说 simple 其实是 easy")。
5. 七维提取
| 维度 | 内容 |
|---|---|
| 核心问题 | 为什么我们明知复杂度有害,仍在持续生产复杂系统?Hickey 的答案:混淆了 simple(客观、无缠绕)与 easy(主观、近便、熟悉),并围绕"容易"组织了整个工具文化与评审文化;Ousterhout 的答案:复杂性是增量累积的,缺乏一套"看见并持续偿还"它的设计纪律。 |
| 主要主张 | Hickey:简单是客观属性(无 interleaving),容易是相对属性(near/familiar/capable);复杂度来自 complecting;应选简单构造(值/函数/数据/队列/声明式)、做小接口大拆分,简单是一种需要 constant vigilance 的选择。Ousterhout:复杂性=任何使系统难理解难修改的结构特征,症状为变更放大/认知负荷/未知未知,成因是依赖与晦涩;处方是深模块、信息隐藏、"把复杂度往下拉"、战略编程与设计两遍。 |
| 方法 | Hickey:词源辨析 + 构造→工件的因果分析 + 逐一点名常见构造的 complect 方式(复述式论证,无实证数据);给出 who/what/when/where/why/how 的抽象拆分操作法。Ousterhout(据转述):症状—成因—处方的定义驱动框架 + 大量正反例(Unix I/O、Classitis、Tk)。 |
| 数据与证据 | 两者都是经验性论证而非实证研究:Hickey 的"科学图表"自嘲无坐标轴(L150),证据形态是行业共同经验与构造分析;Ousterhout 的证据是教学/工业案例(Unix I/O 五个系统调用、Tk 两版设计、Tolkien 式 Classitis 反例)。第三方检验(typesanitizer)指出:Hickey 定义客观但 complect 泛化受限;Ousterhout 框架在 obscurity 上有循环、全书几乎不谈调试。 |
| 结论 | 二者在终点收敛——一切以"未来理解与修改的成本"为法庭,接口要小,组织结构不等于简单,测试/类型是安全网而非设计;分歧在复杂度允许藏进哪里(Ousterhout:藏进深模块内部;Hickey:构造本身不得缠绕,状态复杂度必然渗漏)与简单性的客观性主张(Hickey:可查验;Ousterhout:经 obscurity 间接读者相对)。 |
| 局限 | Hickey:无度量、无实证;对"complect 无条件坏"的绝对化被 typesanitizer 举反例(外键类有意耦合);对环境复杂度自认无解(策略不可组合);隐含 Clojure 立场。Ousterhout:obscurity 定义循环、读者相对;调试/可观测性缺位;深模块的"藏"与 Hickey 的"渗漏"论证未交锋。本次精读自身局限:Ousterhout 原书未读,引语经二手转引;InfoQ 官方页 405 未读。 |
| 对我的启发 | (1)日常评审里把"简单/容易"当两个词用:说"简单"必须能指出哪里没缠绕(判据 1、5),否则改口"容易"。(2)判据 4 与判据 5 联用可仲裁最深的设计争吵:允许复杂实现,禁止隐式泄漏——这正是深模块与值语义的交集。(3)判据 6 用来拆穿选型争论:" parens 不熟"与"ORM 缠绕"是两类问题,前者学习解决,后者换工具解决。(4)把 typesanitizer 的批评记为检查项:任何耦合都先问"它是被解释在一起的必然,还是偶然缠绕"。 |
6. 参考来源
- Rich Hickey, *Simple Made Easy*, Strange Loop 2011. 逐字稿:https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md (本次全文精读,raw 文件本地暂存于工作区 sources-d2f37j93/ 目录);视频:https://www.youtube.com/watch?v=SxdOUGdseq4
- *A Philosophy of Software Design Summary* (dev.to, 2025-07):https://dev.to/markadel/a-philosophy-of-software-design-summary-pk9
- Jan Meppe, *Book Notes: A Philosophy of Software Design*:https://www.janmeppe.com/blog/a-philosophy-of-software-design-john-ousterhout/
- Pragmatic Engineer, *A conversation with John Ousterhout about The Philosophy of Software Design*(2025-04,摘要页非逐字稿):https://newsletter.pragmaticengineer.com/p/the-philosophy-of-software-design
- typesanitizer, *A meta-analysis of three different notions of software complexity*(Hickey / Ousterhout / Tellman 对照):https://typesanitizer.com/blog/complexity-definitions.html
- 未能读取:InfoQ 官方页 https://www.infoq.com/presentations/Simple-Made-Easy/ (HTTP 405)。