返回文章列表
/更新于

别先从类和函数开始,把静态加载器当成编译器来设计

设计这类库时,先固定 use case,再固定 pipeline,再定义阶段产物,最后才讨论 node 变体、代码结构和测试。

  • Design
  • Architecture
  • Compiler
  • Documentation

很多人设计“读取目录、静态解析、补全关系、做质检”的库时,第一反应是先看类、函数和当前代码文件怎么拆。

这通常就是复杂度开始失控的地方。

如果这个库的本质是在做:

  • 读取某个目录结构
  • 归一化用户输入
  • 建立引用与绑定关系
  • 构建静态结果
  • 推导补全信息
  • 输出最终 closure 或 bundle 结果

那更适合它的设计心智,其实不是“业务工具箱”,而是“静态加载器 / 编译器”。

先用两条轴固定系统

设计这类库时,最稳的方法不是一开始同时讨论分层、类图、异常路径和特殊 case,而是先固定两条轴。

第一条轴:场景轴

先回答,系统对外到底解决哪几个主要问题。

你现在最稳定的两个主场景其实已经很明确:

  1. 读取一个 operator 目录,得到 bundle 级静态结果,并在过程中做质检。
  2. 读取整个 repo 目录,得到 project closure 下的静态结果,并在过程中做质检。

这一步的目标不是展开细节,而是把主入口钉死。每个场景先只回答四件事:

  • 输入是什么
  • 最终输出是什么
  • 哪些阶段会 fail-fast
  • 哪些内容明确不在这个场景负责范围内

如果这一层都还没收口,就不要过早下钻到 task、container、cross-bundle ref 或 runtime 边界。

第二条轴:阶段轴

主场景固定后,再按类似编译器的方式拆主流程。

对这类库,最稳的一条阶段链通常是:

  1. read
  2. normalize
  3. bind
  4. build
  5. derive/check
  6. finalize

这样人的理解顺序会很自然:

  • 先知道系统在解决什么问题
  • 再知道主流程如何推进
  • 再知道每一步产出什么中间结果
  • 再知道不同 node 类型会在哪些步骤分叉
  • 最后才回到代码结构、类和函数

推荐的设计顺序

每一轮只增加一个维度,不要一开始就同时讨论场景、分层、数据模型、特殊分支和代码实现。

第一步:固定主场景

这一步只谈 use case,不谈实现。

例如:

  • load_operator(...)
  • load_repo(...)

每个入口都先只回答四个问题:

  • 输入
  • 最终输出
  • fail-fast 边界
  • 明确不负责什么

这会把“系统边界”先锁住。

第二步:拆主流程

等主场景固定后,再把每个入口拆成阶段表。

每一步都强制写清:

  • 目标
  • 输入
  • 输出
  • 失败边界
  • 不负责什么

这里的关键,不是把步骤写多,而是让每一步只有一个清楚职责。只要有一个函数同时承担 read / normalize / bind / finalize,后面的人就一定越来越难理解系统。

第三步:定义阶段产物

不要先问“需要哪些类”,而是先问:

这一步完成之后,后续阶段到底要拿什么继续往下走?

这才是数据模型最自然的来源。

比如:

  • read 之后保留什么原始读取结果
  • normalize 之后哪些 authoring 结构已经被归一化
  • bind 之后引用和静态资产是否已经解析完
  • build 之后 bundle 级对象是否已经可消费
  • derive/check 之后哪些补全和质检结果已经稳定
  • finalize 之后最终对外返回什么结果

数据模型应该跟着阶段产物走,而不是跟着当前文件树或类层级走。

第四步:再谈分支设计

主流程稳定后,再单独讨论变体和特殊分支,比如:

  • task / command
  • graph / loop / loop_block
  • container 的 IO spec 推导
  • 只有 project closure 才能完成的补全

这些都应该挂在稳定主流程之后,而不是在 overview 阶段就直接冲进去。

第五步:最后才落到代码和测试

这里也有顺序:

  • 已存在真实代码:用 code-source block 关联
  • 还没实现的部分:先用 Markdown 代码块写设计草图
  • 行为边界稳定后:再补 acceptance block

代码和测试不是不重要,而是不应该提前吞掉系统设计。

对这类库,最自然的文档树长什么样

如果要把文档持续写清楚,最稳的组织方式通常是:

  1. 先用 overview 固定主心智
  2. 再用数据模型文档固定阶段产物
  3. 然后只围绕两个主入口收口
  4. 主流程稳定后,再把下钻专题拆出去

也就是说,先有:

  • load-and-validate.md
  • load-and-validate-data-models.md

然后继续围绕:

  • load_operator(...)
  • load_repo(...)

把阶段表写实。

在这之后,再单独开下钻文档,例如:

  • normalize_bundle_authoring
  • bind_bundle_static_assets
  • check_task_bundle_static_readiness
  • complete_container_project_contracts

这个顺序的关键在于:特殊情况要挂在主流程下面,而不是反过来让主流程被特殊情况牵着走。

一个很实用的判断标准

行业里这类 library 最稳的做法,本质上就是:

  1. use-case first
  2. pipeline second
  3. artifacts third
  4. variants fourth
  5. code last
  6. tests after boundaries are stable

反过来看,最容易让人越来越糊涂的做法通常是:

  • 先按当前代码文件组织来理解系统
  • 太早讨论特殊情况
  • 一个函数同时承担多个阶段职责
  • 把数据模型校验、语义校验和 project closure 校验混在一起

如果团队已经开始觉得“系统明明没那么大,但为什么越讲越糊”,通常不是因为功能太多,而是因为讲解顺序错了。

一个可以立刻执行的动作

如果现在就要推进文档,不需要先开十篇专题。

最有效的下一步通常只有一个:

load_operator(...)load_repo(...) 各自整理成一张阶段表,每一步只保留五列:

  • 目标
  • 输入
  • 输出
  • fail-fast
  • 当前代码锚点

这个动作的价值很高,因为它会强迫团队把“主场景”和“阶段边界”真正写实。一旦这两张表稳了,后面的数据模型、下钻文档、代码分层和测试边界都会清楚很多。

结语

设计静态加载器这类库时,最大的误区不是不会建模,而是太早把注意力放进实现细节。

更稳的方式是:

  • 先定义系统到底对外解决什么问题
  • 再定义主流程怎么走
  • 再定义每一步留下什么阶段产物
  • 再讨论分支和特殊情况
  • 最后才让代码和测试收口

这不是“写文档优先”,而是在避免系统心智被实现细节反向绑架。