工具手册这个看似再普通不过的东西,最近差点让我睡不着觉。我们一个自动化巡检项目需要根据内部工具手册判断某个能力的适用范围,手册是有的,还是在线文档,可里面的判断条件全写在表格备注里,写得再清楚人也得一个个读。脚本要读,读不了。于是组里同事花了两天,把那些自然语言一条条翻译成规则文件。翻译完第二天我就在想:如果手册本身就是机器可读的,这活根本不需要人干。这就是我做“工具手册的机器可读重构”的起因。整个项目的核心思路可以浓缩成八个字:人机双轨、六模块。这篇内容适合正在做知识资产化、运维自动化、文档工程改造的团队参考,尤其是那些被“手册更新跟不上系统建设”折磨过的人。
1. 工具手册的“可读”到底卡在哪里
1.1 我们以为的“可读”,机器完全读不懂
大多数团队现在的手册状态,我概括为三个字:有数据,没语义。文件是电子化的,Word、PDF、Wiki 页面都有,排版也精心调整过。可机器打开这些文件,看到的只是一行行字符串,它不知道“超时时间默认 3000”里那个 3000 是什么单位,不知道“仅支持 B 侧节点”是一个适用条件还是一个警告,更不知道“建议不要在高负载时执行”是一句可以转化成触发策略的规则。
当时我们做巡检脚本,想实现的其实是一件很简单的事:让调度系统根据手册判断某个工具在某个环境是否可用。可用性判断需要的字段无非是“工具标识、适用对象、支持协议、版本约束、状态”。这些字段在旧手册里都有,但各自藏在段落里、表格备注里、甚至图注里。机器没有能力完成这个“从人话里提炼字段”的动作,除非我们给它配一个自然语言理解模型,但那是后话,成本太高。
1.2 机器可读的三个层次:电子化、结构化、语义化
我习惯把“机器可读”拆成三层,这样团队沟通起来不容易鸡同鸭讲:
| 层次 | 典型表现 | 机器能做什么 |
|---|---|---|
| 电子化 | PDF、Word、在线文档、图片 | 能打开、能全文搜索,但内容仍需人理解 |
| 结构化 | 字段有名字、有类型、有层级 | 能解析、能校验、能按字段取值 |
| 语义化 | 枚举统一、术语受控、编码一致 | 能跨系统理解同一件事,支持自动决策 |
大多数团队停在第一层到第二层之间。有的导出了 Excel 就算“数据化”了,可 Excel 里很多列填的是“见备注”,备注里又是自然语言。有的写了 JSON,但没有字段规范,换个消费方就得重新对齐一遍,跟没有规范差不多。
这次重构,我的目标不是做出一个漂亮的在线知识库,而是把手册推进到第二层,并尽量触碰第三层:让所有关键信息都有明确的字段名、类型、取值范围和一致语义。人读时不在乎这些,但机器在乎。
1.3 为什么很多团队卡在结构化门口
我把原因总结成一个词:没人愿意为机器额外付出一遍劳动。写手册的人本身是在服务人类读者,他的表达习惯是“写清楚”。但机器消费要求的是“写规整”,比如“目标主机地址”必须写成target_host而不是“目标”“地址”“主机名”混用;“支持 TCP 和 UDP 而并不支持 ICMP”必须写成protocol: [tcp, udp]加一个exclude规则,而不是一句人话。
人写清楚了,机器读不动;机器读得动了,人又觉得太死板。人机双轨的思路就是来解决这个矛盾的:同一份源数据,同时保留人读的叙述性文本和机读的结构化字段,而不是逼着人在两套文档里各写一遍。
2. 人机双轨不是写两份文档,而是一条数据源两条输出轨道
2.1 最忌讳的做法:一份给人看,一份给机器用
重构之前我们内部有过一次讨论,有同事提议干脆做一个“双文档制”:文档系统里配一个 JSON 接口,机器用的时候读 JSON。这个提议被我拦下来了。原因很简单,一旦同一份知识存在两个地方,就一定会出现不同步。今天有人改了 Word 里的参数缺省值,忘了改 JSON,明天线上调度就可能在靠近边界条件的地方出错。这类问题发生后很难追溯,因为你不知道哪个文件是“真源”。
人机双轨不是双文档。双轨的意思是:同一份源文件,经过校验和构建,生成两种面向不同读者的产物。人在浏览器里打开的是排版良好的手册页面,机器加载的是registry.json、openapi.yaml、校验规则包。它们来自同一个提交,版本天然一致。
2.2 源文件长什么样:Markdown 加 YAML 前端数据
我们最终的源文件格式是Markdown + YAML frontmatter。整个文件看起来仍然是正常手册,人可以直接在 Git 上阅读、评论、审阅;但文件头部有一段被---包裹的 YAML,承载机器消费所需的全部结构化字段。
下面是我重构后的一个工具手册源文件示例:
--- id: net-health-check schema_version: "1.0" status: active owner: tooling-ops category: network-diagnostic applies_to: - scope: gateway versions: ["2.3", "2.4", "3.0"] capabilities: - type: connectivity-check protocols: [tcp, udp] targets: [host, ip] contracts: - id: run-check params: target_host: type: string required: true pattern: "^[a-z0-9.-]+$" protocol: type: string enum: [tcp, udp] default: tcp timeout_ms: type: integer min: 100 max: 30000 default: 3000 --- # 网络连通性检查工具 该工具用于诊断主机到网关的连通性,支持 TCP/UDP 探测。 ## 使用说明 在开始检测前,请确认目标地址可以被当前网络环境访问。检测结果 以退出码和耗时作为主要判据,退出码 0 表示成功,非 0 表示失败。你发现没有?人的阅读轨道只依赖正文里的叙述段落,机器的解析轨道只依赖 YAML 里的结构化字段,两边读的是同一份文件。人不会为机器补一段看不懂的配置,机器也不需要迁就人的自然语言。
2.3 构建阶段怎么分离两条轨道
源文件进入仓库之后,我们在 CI 里加了一条“手册构建与校验”的任务。它做的事情是:
- 解析所有源文件,先做 YAML 语法校验;
- 用 JSON Schema 对 frontmatter 做结构校验,缺字段、类型错、枚举越界都直接失败;
- 校验通过后,生成两个产物目录,打上同一版本号;
human/目录放渲染后的手册页;machine/目录放按模块拆分的机读产物;- 产物归档到对象存储,后续有人读站点、自动化任务各自拉取对应轨道的版本。
这套流程跑通之后,“手册变更”就变成了“代码变更”:有提交记录,有 diff 可以看,有 PR 可以 review。七十几份手册重建索引从原来的一个下午,变成流水线里的三分钟。
3. 六模块怎么拆:按机器执行链路拆,而不是按文档章节拆
3.1 为什么是六个模块
一开始有同事提议按旧手册的章节来分模块,“安装、参数、故障、FAQ”这种。我否掉了,因为那是人的阅读轴。机器消费一份工具手册,真正关心的不是“安装步骤在第五章还是第六章”,而是完成下面这六个问题:
- 这份手册现在能用吗?适用什么环境?(元信息)
- 这个工具到底能干什么?(能力描述)
- 每一项能力怎么调用?参数边界是什么?(接口契约)
- 多步骤操作怎么组织?失败怎么处理?(操作编排)
- 执行结果怎么判断对错?(校验规则)
- 版本演进会不会破坏我现在的用法?(变更追溯)
这六个问题连起来,恰好是一个工具从“被识别”到“被调用”、再到“被长期治理”的生命周期。所以六模块不是拍脑袋凑数,而是按机器执行工具时的推理链路反推出来的。
3.2 六个模块的职责和机器消费方
| 模块 | 一句话职责 | 典型机器消费场景 |
|---|---|---|
| 元信息 | 说明手册对象、版本、状态、维护方 | 工具注册中心加载,自动判断可用性 |
| 能力描述 | 声明工具能做什么、适用场景 | 任务编排器按能力标签路由 |
| 接口契约 | 定义调用参数、约束、返回值 | 参数校验、SDK 生成、Mock 服务 |
| 操作编排 | 定义多步骤流程和失败处理 | 工作流引擎、自动化执行器 |
| 校验规则 | 定义结果正确性的判定标准 | 自动化测试、巡检判定逻辑 |
| 变更追溯 | 记录版本差异与兼容性影响 | 发布比对、废弃提醒 |
最初我只设计了四个模块,落地后发现“怎么判断结果对不对”和“版本变了会不会影响别人”这两件事,无论如何都要单独立项。不单独立项,它们就被塞进接口契约和元信息里,最后谁都说不清楚边界在哪里。所以最终固定为六个,稳定运行到现在。
3.3 用一个小工具看六个模块的完整形态
我们内部用“网络连通性检查工具”作为第一个改造样例。它的六个模块在源文件里是这样划分的:
- 元信息模块:
id: net-health-check,status: active,applies_to列出网关 2.3/2.4/3.0; - 能力描述模块:在
capabilities字段声明自己是connectivity-check类型,支持 tcp/udp,目标可以是 host 或 ip; - 接口契约模块:
contracts里定义run-check这个操作,包含三个参数和各自约束; - 操作编排模块:后续扩展定义一个“检测后自动上报”的流程,包括
run-check、collect-result、push-metrics三步; - 校验规则模块:定义退出码为 0 视为成功,执行耗时超过
timeout_ms视为超时告警; - 变更追溯模块:记录从 1.0 到 1.2 的每次变更,包括一次“协议枚举新增 udp”的兼容性变更。
这套东西人读手册的时候基本不感知,但对机器来说,它拿到了一个完整的、可校验的“工具知识包”。后续无论是做自动巡检、容量评估还是横向对比,都直接消费这份知识包,不再需要人工翻译。
4. 接口契约与校验规则:把“经验描述”变成机器能执行的判定
4.1 契约字段的设计原则
契约模块是最容易返工的部分,因为参数边界动辄几十个字段。我坚持三个原则:
第一,所有参数必须有类型声明和约束,不允许出现“目标地址”这种没有 pattern 的字符串。比如target_host我们定义了正则^[a-z0-9.-]+$,不允许空、不允许带协议头,从源头拦截大部分低级调用错误。
第二,枚举优先于自然语言。协议字段写enum: [tcp, udp],而不是“支持常见协议”。版本字段写versions: ["2.3", "2.4", "3.0"],而不是“新版”。机器的世界里,没有“常见”这个词。
第三,必须有返回语义。以前的备注说“看退出码判断成功”,问题是非 0 到底对应什么错误?我们在契约里定义了一个退出口子,明确 0 成功、非 0 失败、超时单独用状态字段status: timeout标识,三类结果互不混淆。
类似地,如果这个工具是 HTTP 服务,契约模块就直接用 OpenAPI 描述;如果是命令行工具,我们用自己的 YAML 契约。统一思想是:不要自己发明一套复杂的描述语言,优先复用一个成熟规范,让生成、校验、SDK 都有现成生态。
4.2 校验规则从“注意事项”里长出来
校验规则模块最初的素材,来自旧手册里所有带“注意”“必须”“建议不要”字样的句子。比如:“注意:执行结果以退出码为准”“建议超时时间不要低于 100ms”“高负载时不要执行”。这些句子人读没问题,转化的时候却能逼出很多潜在分歧:
- “以退出码为准”——那退出码为 0 但耗时异常怎么算?我们补了一条超时状态规则;
- “不要低于 100ms”——规则是禁止还是告警?我们定为告警,因为某些网络环境下确实只能给 80ms;
- “高负载时不要执行”——怎么定义高负载?我们把判断条件放到编排模块的
pre_check里,用 CPU 阈值来表达。
转化之后,校验规则模块长这样:
validations: - rule_id: V001 when: exit_code == 0 result: success message: "命令执行成功" - rule_id: V002 when: exit_code != 0 result: failure message: "命令执行失败,参考 code 字段定位错误" - rule_id: V003 when: elapsed_ms > timeout_ms result: warning message: "执行耗时超过约定的超时时间" - rule_id: V004 when: status == timeout result: failure message: "连接建立超时,请检查网络策略"这套规则文件同时喂给两条轨道。人读轨道把它渲染成一个“判定对照表”,运维同事排障时可以直接看;机器轨道把它编译成一组测试断言,巡检框架加载后就是现成的判定逻辑。同一份规则,没有二次手写。
4.3 校验规则不能贪多
给校验规则模块提个醒:别想着把所有边界条件都固化成规则,规则越多,维护成本越高,最后没人愿意改。我们的经验是每个工具先锚定三类规则:成功判定、失败判定、超时判定。这三类覆盖了绝大多数自动化调用场景。更细的边界,比如“某个协议在某种路由模式下表现异常”,留给人读轨道的叙述性说明,让工程师在排查时阅读即可。
5. 操作编排与变更追溯:让手册参与自动化流程和版本决策
5.1 操作编排:人写步骤,机器跑流程
很多工具手册不只是描述单个命令,还会教人怎么做一整套操作,比如“先检查网络,再备份配置,然后升级,最后健康检查”。旧手册里这一部分是纯文本步骤,每一步之间是顺序关系,失败怎么办写得非常模糊。
操作编排模块把这部分变成结构化的流程定义:
orchestration: - flow_id: upgrade-gateway-2x entry: pre_check steps: - id: pre_check require_capability: connectivity-check on_failure: stop - id: backup requires: [pre_check] timeout_ms: 60000 on_failure: stop - id: upgrade requires: [backup] timeout_ms: 300000 on_failure: rollback - id: health_check requires: [upgrade] on_failure: notify rollback_plan: - restore-config - restart-service机器拿到这个定义之后,流程引擎才知道“备份成功之后才能升级”“升级失败要回滚”,这些以前只能靠人盯着的执行纪律,现在变成了流程引擎的硬约束。有人可能会说,这不就是运维编排工具干的事吗?对,但区别在于:我们把手册当作编排流程的唯一来源,而不是在手册里写一遍教学步骤,再在编排系统里另配一套流程。两条轨道还是同一个源。
5.2 变更追溯:手册也要谈兼容性
以前手册的“变更记录”通常写在末尾:v1.2 更新了什么、修复了什么,然后就没有然后了。机器完全无法判断“我现在用的版本还安不安全、要不要升级”。变更追溯模块补齐了这个短板。
它在 frontmatter 里增加了一个结构化变更日志:
changelog: - version: "1.2" date: 2024-11-05 change_type: compatible summary: 新增 udp 协议支持 - version: "1.1" date: 2024-08-19 change_type: breaking summary: 移除了 target_port 参数的自动推导能力 migration_note: 调用方需显式传入 target_port这个字段的价值,在自动化比对场景里体现得最明显。我们做了一个定期任务,扫描线上部署的工具版本,跟手册里的applies_to.versions、status、changelog.change_type做比对:工具已被废弃、版本不再支持、存在 breaking 变更且调用方未适配,这些都会自动生成告警。以前这种治理动作靠人肉追版本,根本追不过来。
5.3 机器消费是终点,但是要为“人回来查”留后门
把编排和追溯交给机器以后,有一个副作用:出了问题,工程师还是得回来对着手册排查。所以我们坚持在生成的人读站点里,把操作编排渲染成带流程图的步骤说明,把变更追溯渲染成变更历史页,每个环节都标注了对应的结构化字段名。这样机器排查时给一个flow_id: upgrade-gateway-2x,人就可以直接跳到对应页面看细节。机器是执行者,人始终是兜底者,双轨道的设计必须给人和机器留一个共同锚点。
6. 踩过的坑与最小落地路径:给也想重构的人一个参考
6.1 坑一:不要试图一次性迁移全部手册
项目一开始,我们把全量手册清单拉出来,五十多份文档排了个迁移计划,想着一个月全部完成。结果做到第三周就崩了:有的手册缺参数说明,有的工具已经废弃但没人敢删,有的负责人根本联系不上。全量迁移最大的问题不是写代码,而是没有前置的治理共识。
正确的做法是先挑三到五个高频使用、边界清晰、owner 愿意配合的工具做试点。我们最终选了“网络连通性检查工具”和另外两个常见工具,跑通模板和流水线之后,再让各负责人按模板往仓库里迁移。第一批跑通带来的示范效应,比任何推行命令都管用。
6.2 坑二:术语口径不统一,结构化之后反而更难维护
这件事特别讽刺:旧手册因为是自然语言,同一个概念换个说法,大家看多了能猜;结构化成字段之后,每个字段都要求一致语义,术语不统一的问题就暴露无遗。比如有的文档写target_host,有的写host_address,有的直接写ip,如果不在源文件里统一,机器读取时就会产生两套字段名,下游调用方无所适从。
我们的解法是建立了一个轻量级术语表,作为构建校验的字典:每个概念只有一个规范字段名,其余写法在迁移时强制替换,校验不通过直接阻止合入。这一步没捷径,靠的是迁移早期的严格 review。
6.3 坑三:机器可读之后,人读体验被牺牲
有一段时间我们只顾着把字段做得规整,结果渲染出来的人读页面变得特别碎,满屏字段名,工程师根本不想看。这个问题很快被反馈打回来。后来我们在源文件结构上定了一条规矩:每个模块的 YAML 字段可以有,但正文叙述必须完整保留背景、理由和经验。字段负责精确,正文负责体感。人读轨道永远面向“工程师在排障现场快速理解上下文”这个场景,而不是面向解析器。
6.4 最小落地路径:三步走
如果让我重新来一次,我会把实施路径压缩成三步:
- 第一步,先做元信息和接口契约两个模块,把最常用工具的“版本、状态、参数、返回值”变成结构化的,同时保留原有正文;
- 第二步,补校验规则模块,把旧手册里出现频率最高的“注意”“必须”“不要”类句子转化成规则断言,能覆盖成功、失败、超时三类判定即可;
- 第三步,再考虑操作编排和变更追溯,这两个模块依赖前两个模块的数据,晚点上不影响整体节奏,但前置数据没做好强行上,会让流程定义悬空,反而难维护。
个人最大的体会是:工具手册的机器可读重构,表面上是技术工程,实际上是一场团队协作方式的改造。文档从“写完即交付”变成了“持续维护的资产”,需要 review、需要版本管理、需要 owner 负责。如果你所在的团队有做自动化治理、智能问答、统一工具目录这类诉求,强烈建议从一份高频工具手册开始,先让机器“读懂”它,再考虑让它“用”起来。这条路不窄,只要你愿意把手册当成代码一样对待。