简单、组合、复杂性:用什么问题阅读一个造物
本轮的研究假设是:好的产品让少数稳定概念覆盖很多真实任务;好的实现让常见变化只影响少数责任边界。 这两件事可以互相帮助,也可能互相冲突。界面只有一个输入框,后台仍可能是一套庞大的执行系统。
这是一套针对本轮案例的分析框架,不是复杂度定理、产品评分或选型公式。相关:调查入口 · Raycast / Warp · Zed / Grok Build · Yuuki 的变化实验。
1. 先说明:谁觉得简单,做什么时简单
Section titled “1. 先说明:谁觉得简单,做什么时简单”Rich Hickey 在 Simple Made Easy 中区分了结构上的简单与使用者感到容易:前者关心责任是否纠缠,后者受熟悉程度和可获得性影响。这里借用这个区别,不把某种语言、范式或所有可变状态一概判为复杂。演讲原始资料,2011
本轮把“简单”拆成四个观察面:
| 观察面 | 具体问题 | 容易混淆的现象 |
|---|---|---|
| 用户操作 | 找到目标、判断状态、完成动作,要记住几套规则? | 按钮少,却必须自己搬运上下文、猜进度 |
| 局部实现 | 修改一项行为,必须同时理解哪些状态与责任? | 文件短,却依赖大量隐含调用顺序 |
| 组合使用 | 一个能力接入另一个入口,需要重复多少业务规则? | 都能调用,却无法传递结果、等待完成、取消 |
| 维护与运行 | 出错后能否定位原因、解释副作用、恢复工作? | 演示流畅,异常后只剩下重启和猜测 |
这四面没有天然同一的最优点。Raycast 的扩展作者容易开发,可能来自宿主承担运行时与桥接工作;Grok Web 用户容易发布,可能来自平台接管部署与授权。它们的价值是真实的,但应把成本的承担者写出来。Raycast 案例 · Grok 产品边界
少代码、少语言、少进程、少依赖都有具体收益,但都只是线索。 一个进程减少通信故障,也共享崩溃与资源边界;两个进程增加生命周期工作,也可能复用成熟生态。上轮 PI 调查已经展示了 Electron、Node Agent 与 Rust host 的这类交换;仅数 crate 无法回答整体是否更好理解。PI 的责任与协议
2. 组合成立,需要共同语义
Section titled “2. 组合成立,需要共同语义”Parnas 的模块划分讨论强调:模块是责任分配,可以围绕困难或容易变化的设计决定来划分,让其他模块不必知道这些决定。他用不同分解方式比较改动传播,而非仅按处理流程逐步分文件。本轮将“变化会扩散到哪里”作为主要检验方法。Parnas 原文,1972,大学转载
下面是本轮自拟的组合检查表。它不要求每个纯函数都具备持久化协议;涉及异步、有状态或跨进程能力时,再补齐相应项目。
| 接缝 | 必须讲清的事 | 研究中的反例 |
|---|---|---|
| 对象与身份 | 两端操作的是同一个什么?ID 的寿命有多长? | 屏幕第 3 行不能充当跨更新的任务身份 |
| 输入与结果 | 参数怎样获得?结果是值、句柄还是通知? | 同为 JSON,不代表字段语义一致 |
| 状态权威 | 谁能决定运行、取消、失败和完成? | UI 和 Runner 分别猜一份最终状态 |
| 时间与顺序 | 接受、开始、输出、完成分别如何表示? | Raycast 启动确认不能代替业务完成 |
| 生命周期 | 关闭入口、丢弃句柄、断开连接会发生什么? | Whisper 的 stop 与 cancel 不能合并 |
| 失败与副作用 | 超时后结果未知还是明确未发生?能否重试? | 写入已发生,取消并不使它消失 |
| 资源与观察 | 谁限流、截断、清理?如何查有效配置? | 丢弃积压快照仍可能付出完整序列化成本 |
反例的源码和 API 依据分别见命令与 Block、Grok 的扩展与会话、本地三条变化链。
还需要分开四种常被混称的组合:
- 呈现组合:把列表、详情、按钮放进同一界面,解决布局与交互。
- 能力组合:不同入口调用同一业务操作,复用参数校验、执行和结果。
- 执行组合:让任务可靠衔接,处理等待、取消、失败与已发生的副作用。
- 部署组合:通过进程或网络协议分工,处理版本、连接、身份与资源寿命。
Warp 的丰富内容共用滚动区域,首先证明呈现组合;Raycast Form 与 AI tool 共用函数是能力组合的设计方式;PI 的 turn 与持久化涉及执行组合;Grok ACP 则提供进程协议入口。某一层成立,不能自动证明下一层成立。
3. 复杂性既可以搬动,也可以实际减少
Section titled “3. 复杂性既可以搬动,也可以实际减少”Brooks 在 No Silver Bullet 中区分问题本身的概念难度与表达、工具带来的附加成本,并讨论外部约束带来的复杂性。这里用它提醒自己区分成本来源;不把历史论述变成对今天 AI 或工具进步的绝对预测。原始技术报告
本轮采用以下工作分类,分类可以随需求变化,不宣称某个问题永远不可简化:
| 来源 | 例子 | 可以怎样处理 |
|---|---|---|
| 已接受需求带来的状态 | 流式输出、审批、取消与文件写入竞态 | 明确状态和责任;也可缩小需求减少组合 |
| 平台与兼容约束 | 输入法、PTY、Shell hooks、音频格式、子进程退出 | 集中到适配层,给调用方保留必要失败语义 |
| 自己引入的纠缠 | 多份权威状态、重复校验、不必要中转、共享全局开关 | 合并责任或删除机制,真正减少总工作 |
| 尚未需要的通用性 | 任意插件、全套云编排、通用原生控件协议 | 延后到具体复用压力出现 |
因此,不采用“复杂性总量守恒”的说法。删除需求会减少状态空间;移除重复权威会减少同步关系;限制一次只运行一个任务会避免一批并发交互。另一方面,若只是把部署交给平台,复杂性主要改变了承担者,仍会以费用、可观察性、迁移和故障处理的形式影响使用者。
“隐藏”也有好坏之分。调用方不必知道 Whisper 如何转换音频,但需要知道停止录音后还在转写;调用方不必知道数据库布局,但需要知道收到的是接受确认还是持久化完成。好的边界隐藏实现决定,保留影响下一步行动的事实。 这是本轮从案例中归纳的判断,并非引用某产品的设计宣言。
4. 为什么限制有时增加表达力
Section titled “4. 为什么限制有时增加表达力”自由度越高,不意味着用户越容易表达意图。如果每个扩展都重新定义选择、确认、返回和快捷键,用户就要为每个能力重新学习一套操作。Raycast 固定条目和动作的交互语法,把变化留给业务内容;Zed 让单文件也经过 MultiBuffer,使普通编辑和片段编辑共享后续能力。动作契约 · MultiBuffer 证据
这里的启发是:优先寻找能被多项功能共同使用、语义又足够明确的单位。这个单位不一定是最小函数,也不一定要抽成独立包。它可能是一次执行、一个可编辑片段、一项业务操作或一个会话。
也要留意反例:为了复用,把所有对象压成一个任意 JSON 结构,会把检查推迟到运行时;为了统一,把停止录音和放弃转写变成同一按钮,会损失必要意图;为了通用,把不同生命周期强塞进同一抽象,会使每个调用方都增加例外分支。
5. 用变化实验代替复杂度总分
Section titled “5. 用变化实验代替复杂度总分”先固定一个真实变化,再记录以下内容,得到可复查的比较:
- 为了理解它,实际读了哪些模块、协议和状态;不要只统计最终修改文件数。
- 真正修改了哪些责任边界;是否重复修改同一条规则。
- 新增了哪些状态、跨边界消息和顺序假设。
- 错误、取消、重复请求、旧结果晚到时,用户会看到什么。
- 运行行为是否验证;哪些只是静态阅读推断。
这些是观察项,不能相加成一个客观“简单分”。同样也不能把 Zed、Raycast、Warp、Grok 的规模和产品任务当作等价负载比较。对 Yuuki 更有价值的,是在五条 UI 路线上对同一个业务变化留下证据;具体探针见 04 · Yuuki。
本轮对“璀璨造物”的解释由此落地:它们值得欣赏的地方,是把人的意图、可操作对象和反馈组织得连贯。研究实现时,则继续追问这种连贯依赖哪些契约、付出了什么成本、在哪里需要承认边界。