--- name: think-model-first description: 思维模型:建模先于代码。写碰到状态与操作的代码之前,先把每个操作写成偏函数,写明它在哪些前置状态下有定义;再全化,定义域之外的每一格给一个确定的答复,不变式排除的格写不可达;最后翻成领域模型与对象模型再写代码,每个类型能指回模型。设计或实现会话、任务、连接、资源这类有生灭的对象,加一个操作或一个状态,或实现里出现「不该发生」的分支时加载;人说「先建模」「全化一下」「状态表呢」,或点名 /think-model-first 时也加载。不碰状态的纯计算与纯文档改动不加载。 --- # 建模先于代码 回答的问题:代码该长成什么形状。偏函数是「不是对所有输入都有定义的函数」,例如「关闭会话」只在会话打开时有定义。全化是把剩下的情况也定下来:会话已关闭时再关一次,答「已关闭」,不报错也不崩。模型定义的是实现的形状,所以它排在代码之前。 ## 三句动作 1. **把状态和操作写成偏函数:每个操作在哪些前置状态下有定义。** 状态名与操作名取自统一语言。 2. **全化:定义域之外的每一格给一个确定的答复。** 答复是拒绝、幂等返回或显式错误。不变式排除的格写「不可达」,不处理。全化之后实现里不再有「不该发生」的分支。 3. **翻成领域模型和对象模型再写代码。** 概念与边界按领域建模,对象怎样协作按 SOLID,名字取自统一语言。代码里的每个类型能指回模型。 全化表说的是什么必须成立,不是代码该长成什么样。照着它直接写,得到一个按状态分支的大函数,对但难维护;翻成领域模型与对象模型,对的东西才写得简洁、能维护。 ## 各类工作里怎么用 | 这类工作 | 偏函数与全化 | 翻成什么 | 展开 | | --- | --- | --- | --- | | 新增一个有生灭的对象 | 全部状态乘全部操作,逐格填 | 聚合、按状态分开的类型、结果类型 | [偏函数与全化表](partial-and-total.md) | | 给已有状态机加一个操作或状态 | 新的一行或一列,连同它与旧格的交叉 | 已有类型上的新变体或新方法 | [偏函数与全化表](partial-and-total.md) | | 实现一个切片 | 本片碰到的状态与操作 | 开工包的领域模型与对象模型 | [翻成代码的形状](translate.md) | | 修一个「不该发生」的缺陷 | 补出漏掉的那一格的答复 | 结果类型的一个具名变体 | [偏函数与全化表](partial-and-total.md) | | 并发、生命周期或跨域性质靠测试守不住 | 全化表照做,再看要不要模型检查与证明 | 精化对照表 | [什么时候再往深做](how-deep.md) | | 不碰状态的纯计算 | 不适用 | 数据加一张表,或一个归约函数 | [翻成代码的形状](translate.md) | ## 写在哪 不新开文档。全化表写在细化设计清单的「全化表」一节,或切片开工包的「全化表」一行;不碰状态写「不涉及状态」。领域模型与对象模型写在细化设计的领域模型与关系模型、开工包的领域模型与对象模型。全化表碰到状态与操作就做,不需要人确认。 ## 什么时候必须重做 - 实现里出现「不该发生」「理论上不会」的分支或注释。 - 加了一个状态或一个操作。 - 一个函数按状态写了一长串分支。 - 一个类型说不出它指回模型的哪一部分。 - 同一个状态判断在几处各写了一遍。 ## 相邻的模型 `think-ought-vs-is`:这件事的工作到底是什么。`think-orthogonal`:这个问题的空间怎么拆。`think-right-then-well`:按什么顺序建,怎样算好。`think-evidence`:怎么知道它是真的。`think-closed-loop`:这件事该由谁做,一次做多大,多久看一次。