别先从类和函数开始,把静态加载器当成编译器来设计
设计这类库时,先固定 use case,再固定 pipeline,再定义阶段产物,最后才讨论 node 变体、代码结构和测试。
- Design
- Architecture
- Compiler
- Documentation
很多人设计“读取目录、静态解析、补全关系、做质检”的库时,第一反应是先看类、函数和当前代码文件怎么拆。
这通常就是复杂度开始失控的地方。
如果这个库的本质是在做:
- 读取某个目录结构
- 归一化用户输入
- 建立引用与绑定关系
- 构建静态结果
- 推导补全信息
- 输出最终 closure 或 bundle 结果
那更适合它的设计心智,其实不是“业务工具箱”,而是“静态加载器 / 编译器”。
先用两条轴固定系统
设计这类库时,最稳的方法不是一开始同时讨论分层、类图、异常路径和特殊 case,而是先固定两条轴。
第一条轴:场景轴
先回答,系统对外到底解决哪几个主要问题。
你现在最稳定的两个主场景其实已经很明确:
- 读取一个 operator 目录,得到 bundle 级静态结果,并在过程中做质检。
- 读取整个 repo 目录,得到 project closure 下的静态结果,并在过程中做质检。
这一步的目标不是展开细节,而是把主入口钉死。每个场景先只回答四件事:
- 输入是什么
- 最终输出是什么
- 哪些阶段会 fail-fast
- 哪些内容明确不在这个场景负责范围内
如果这一层都还没收口,就不要过早下钻到 task、container、cross-bundle ref 或 runtime 边界。
第二条轴:阶段轴
主场景固定后,再按类似编译器的方式拆主流程。
对这类库,最稳的一条阶段链通常是:
readnormalizebindbuildderive/checkfinalize
这样人的理解顺序会很自然:
- 先知道系统在解决什么问题
- 再知道主流程如何推进
- 再知道每一步产出什么中间结果
- 再知道不同 node 类型会在哪些步骤分叉
- 最后才回到代码结构、类和函数
推荐的设计顺序
每一轮只增加一个维度,不要一开始就同时讨论场景、分层、数据模型、特殊分支和代码实现。
第一步:固定主场景
这一步只谈 use case,不谈实现。
例如:
load_operator(...)load_repo(...)
每个入口都先只回答四个问题:
- 输入
- 最终输出
- fail-fast 边界
- 明确不负责什么
这会把“系统边界”先锁住。
第二步:拆主流程
等主场景固定后,再把每个入口拆成阶段表。
每一步都强制写清:
- 目标
- 输入
- 输出
- 失败边界
- 不负责什么
这里的关键,不是把步骤写多,而是让每一步只有一个清楚职责。只要有一个函数同时承担 read / normalize / bind / finalize,后面的人就一定越来越难理解系统。
第三步:定义阶段产物
不要先问“需要哪些类”,而是先问:
这一步完成之后,后续阶段到底要拿什么继续往下走?
这才是数据模型最自然的来源。
比如:
read之后保留什么原始读取结果normalize之后哪些 authoring 结构已经被归一化bind之后引用和静态资产是否已经解析完build之后 bundle 级对象是否已经可消费derive/check之后哪些补全和质检结果已经稳定finalize之后最终对外返回什么结果
数据模型应该跟着阶段产物走,而不是跟着当前文件树或类层级走。
第四步:再谈分支设计
主流程稳定后,再单独讨论变体和特殊分支,比如:
task / commandgraph / loop / loop_block- container 的 IO spec 推导
- 只有 project closure 才能完成的补全
这些都应该挂在稳定主流程之后,而不是在 overview 阶段就直接冲进去。
第五步:最后才落到代码和测试
这里也有顺序:
- 已存在真实代码:用 code-source block 关联
- 还没实现的部分:先用 Markdown 代码块写设计草图
- 行为边界稳定后:再补 acceptance block
代码和测试不是不重要,而是不应该提前吞掉系统设计。
对这类库,最自然的文档树长什么样
如果要把文档持续写清楚,最稳的组织方式通常是:
- 先用 overview 固定主心智
- 再用数据模型文档固定阶段产物
- 然后只围绕两个主入口收口
- 主流程稳定后,再把下钻专题拆出去
也就是说,先有:
load-and-validate.mdload-and-validate-data-models.md
然后继续围绕:
load_operator(...)load_repo(...)
把阶段表写实。
在这之后,再单独开下钻文档,例如:
normalize_bundle_authoringbind_bundle_static_assetscheck_task_bundle_static_readinesscomplete_container_project_contracts
这个顺序的关键在于:特殊情况要挂在主流程下面,而不是反过来让主流程被特殊情况牵着走。
一个很实用的判断标准
行业里这类 library 最稳的做法,本质上就是:
- use-case first
- pipeline second
- artifacts third
- variants fourth
- code last
- tests after boundaries are stable
反过来看,最容易让人越来越糊涂的做法通常是:
- 先按当前代码文件组织来理解系统
- 太早讨论特殊情况
- 一个函数同时承担多个阶段职责
- 把数据模型校验、语义校验和 project closure 校验混在一起
如果团队已经开始觉得“系统明明没那么大,但为什么越讲越糊”,通常不是因为功能太多,而是因为讲解顺序错了。
一个可以立刻执行的动作
如果现在就要推进文档,不需要先开十篇专题。
最有效的下一步通常只有一个:
把 load_operator(...) 和 load_repo(...) 各自整理成一张阶段表,每一步只保留五列:
- 目标
- 输入
- 输出
- fail-fast
- 当前代码锚点
这个动作的价值很高,因为它会强迫团队把“主场景”和“阶段边界”真正写实。一旦这两张表稳了,后面的数据模型、下钻文档、代码分层和测试边界都会清楚很多。
结语
设计静态加载器这类库时,最大的误区不是不会建模,而是太早把注意力放进实现细节。
更稳的方式是:
- 先定义系统到底对外解决什么问题
- 再定义主流程怎么走
- 再定义每一步留下什么阶段产物
- 再讨论分支和特殊情况
- 最后才让代码和测试收口
这不是“写文档优先”,而是在避免系统心智被实现细节反向绑架。