代码写完后过一遍 lint,这是工程团队的肌肉记忆。可轮到文档、README、API 说明,很多团队又退回人肉检查。Vale 是一个开源的自然语言 Linter,它把写作规范变成可执行的检查流程。我第一次用 Vale 跑自己的博客文章,看到一排告警后有点愣住:原来我反复混用“点击”和“单击”、“例如”和“比如”,自己完全没有察觉。
后来我意识到,Vale 真正解决的从来不是“帮你把文章改好”——它没这个能力,也不会假装有这个能力。它解决的是另一件事:把写作规范变成像代码规范一样可维护、可复用、可自动执行的工程资产。这一点,比它检查出来的每一处问题都重要。
1. 代码有 Lint,文档为什么没有?
1.1 文档不规范,通常不是“人不认真”
技术团队里最常见的文档问题是这一类:README 里一会儿写 “Spring Boot”,一会儿写 “spring boot”;API 说明里同一个参数名三种大小写;发布公告里“点击”和“单击”随机出现。这些不是低级错误,而是长期没有统一检查手段的自然结果。
人肉 review 的局限很明显。每个 reviewer 只能靠记忆去核对规则,记不全,标准也会漂移。新同学不知道团队历史上约定过什么,写出来的东西自然会和旧文档不一致。更麻烦的是,文档问题不像编译错误那样当场报错,它会积累成一种“延迟债务”:用户因为术语不一致产生困惑,客户因为产品名拼法不统一觉得不专业,支持团队反复解释同一个问题。
所以一个团队文档规范没落地,往往不是态度问题,而是没有一个机制让规范真正被执行。
1.2 Vale 想补的不是“编辑”,而是“检查流程”
Vale 定位很独特。它不是 Grammarly 那种写作助手,不会给你整句改写建议;它更像一个编译器前端的检查器,或者代码世界里的 eslint、golangci-lint。你给它一份 Markdown、AsciiDoc、reStructuredText 或 HTML 文档,它按配置好的规则输出结构化告警,告诉你哪一行、哪一列、什么级别、什么消息。
这个定位决定了它的使用方式。它不是某个编辑的个人审美,而是一个团队共同认可的“检查基线”。一旦规则文件被提交到仓库里,它就变成了一种自动化的流程约束,不需要有人在群里喊“注意大小写”,不需要新人靠猜来学习风格规范。
2. 拆开 Vale 的设计骨架:它不是正则扫描器
2.1 解析器先行:先理解文档,再检查文字
Vale 经常会被称为 “syntax-aware linter”,这个“语法感知”很关键。它不是简单地把文档当成纯文本正则扫一遍,而是会先解析 Markdown、AsciiDoc、reStructuredText、HTML 这些标记语言的结构,再针对正文内容执行规则。
这个设计带来的实际差别非常明显。代码块里的英文变量名、URL 链接、行内代码、命令行示例,都不应该被当成普通正文来检查。如果工具不理解文档结构,它就分不清“这一行是正文里的英文句子”和“这一行是代码块里的函数名”。Vale 把结构解析放在前面,这让它在技术文档场景里能长期稳定使用,而不是只在纯文本博客上好看。
2.2 规则以“样式包”形态沉淀
Vale 的规则不是散落在配置文件里的一条条临时正则。它要求你把规则组织成“样式包”。在配置里指定一个StylesPath目录,里面每个子目录就是一个样式包,每个样式包由若干 YAML 文件组成。每个 YAML 文件定义一条或一组检查规则。
你可以把这套结构理解成依赖管理。团队可以基于社区维护的公共样式包起步,再叠加自己私有的规则包。规则越积越多,它就不再只是几份配置文件,而是团队写作知识的格式化表达。比如“不要用‘点击’,统一用‘选择’”,这句话如果只写在规范文档里,大概率会被遗忘;如果写成一条 substitution 规则,它就会在每次检查中生效。
2.3 配置、告警级别与输出方式
Vale 的核心配置是一个.vale.ini文件,语法接近 INI 格式。里面指定StylesPath、MinAlertLevel,以及针对不同文件后缀的规则组合。MinAlertLevel用来控制告警门槛,取值为 suggestion、warning、error 之一。CI 里想只拦截严重问题,可以把门槛提到 error;日常本地检查想看完整列表,就保留 suggestion。
输出方面,默认是逐条告警,包含文件路径、行号、列号和消息。给 CI 解析时也可以让 Vale 输出 JSON 格式。这些细节组合起来,它很像一个真正的编译工具链:输入文档,输出结构化告警。
3. 从零跑通一次 Vale 检查
3.1 安装:能跑起来就行
Vale 是 Go 写的,发布的是单个二进制文件,没有运行时依赖。macOS 上常用 Homebrew 安装,Windows 上可以从 GitHub Releases 下载二进制,或者用对应的包管理器。安装完先跑一下:
vale --version能正常输出版本号,说明二进制没问题。这里不需要等团队统一安装,先在自己机器上用一个文件试起来。
3.2 最小目录和第一个规则
一个最小的 Vale 项目只需要两个部分:.vale.ini和一个样式目录。结构大致如下:
my-docs/ ├── .vale.ini └── styles └── TeamDemo └── Terms.yml.vale.ini里面先写最基础的内容:
StylesPath = styles MinAlertLevel = suggestion [*.md] BasedOnStyles = TeamDemo然后在styles/TeamDemo/Terms.yml里写一条最简单的替换规则:
extends: substitution message: "倾向使用 '%s' 而不是 '%s'" level: warning ignorecase: true swap: 点击: 选择 例如: 比如这条规则的含义是:在 Markdown 文档的正文里,遇到“点击”就提示换成“选择”,遇到“例如”就提示换成“比如”。准备一份测试文档:
# 安装指南 1. 点击开始安装。 2. 例如,你可以先查看日志。然后执行:
vale README.md你会在输出里看到带行列号的告警。这里建议把级别先设为 warning 而不是 error,目的是先让问题可见,再由团队决定哪些级别要阻止合并,不要一上来就用最严格的配置。
3.3 先跑单文件,再看批量
这里有一个重要建议:先用一条样例文件把输入、输出、日志都确认正常,再考虑扩大到整个目录。不要一上来就扫全部历史文档。存量文档往往积压了大量问题,一次全量运行只会让告警列表变成噪音,也会让团队产生“这个工具不靠谱”的错觉。
注意:不要一上来就把整个历史文档目录全部扫一遍。先用一个文件把输入、输出和日志都跑通,再考虑扩大到批量目录。
4. 规则体系:从用词到整篇可读性
4.1 常用规则类型
Vale 的规则体系不是只有“替换词”这一种。按常见使用频率,可以分成下面这几类:
| 规则类型 | 它检查什么 | 典型场景 |
|---|---|---|
| existence | 某类词是否出现 | 禁止“众所周知”“显而易见”等套话 |
| substitution | 用 A 替换 B | 统一术语、规范动作动词 |
| capitalization | 大小写规则 | “Spring Boot”不被写成“spring boot” |
| spelling | 拼写异常 | 配合团队词汇表识别专有名词 |
| sequence | 多个词出现的顺序 | 步骤里“首先 → 然后 → 最后” |
| conditional | 出现 A 就必须出现 B | 提到“安装”就应给出“配置” |
| readability | 句子复杂度 | 长难句给出调整信号 |
existence 规则写起来很简单:
extends: existence message: "避免使用 '%s'" level: suggestion ignorecase: true tokens: - 众所周知 - 显而易见conditional 规则适合约束文档结构。比如,你希望一篇安装文档里只要出现“安装”,就必须出现“配置”,因为缺了配置步骤,用户装完也不知道下一步做什么:
extends: conditional message: "'%s' 后面应包含 '%s'" level: warning first: 安装 second: 配置这些规则类型的意义在于:它们把“写作约定”从人脑记忆变成了机器可读的形式。团队里最有争议的往往不是“这个词对不对”,而是“我们到底要统一成哪个词”。一旦把这个决定写进规则文件,讨论就结束了。
4.2 Vocab 词汇表:沉淀组织专有名词
拼写类规则刚上手时容易误报,尤其是产品名、内部项目名、品牌专有名词。Vale 提供了词汇表机制来解决这个问题。你在styles/Vocab/下建一个目录,名称在.vale.ini里通过Vocab配置指定。目录里的accept.txt放“必须接受”的词,reject.txt放“必须拒绝”的词。
这个机制相当于给团队做了一本“官方拼法字典”。产品名、API 名、专业术语放进去之后,拼写规则就不会再对它们报错。团队新同事写文档时,也不需要去翻规范文档确认某个内部项目名到底怎么写,工具会直接告诉他。
4.3 样式继承与团队私有包
.vale.ini里的BasedOnStyles可以写多个样式包,既可以引社区维护的公共样式包,也可以引团队私有规则包。公共样式包帮你建立通用基线,私有规则包表达团队自己的约定,两者叠加使用。
不过这里要提醒一点:样式包叠加得越多,告警重合和优先级问题越容易出现。实际落地的经验是先少而精,公共样式包只选最贴合团队风格的,私有规则包从三五条最关键的开始,跑一段时间后再逐步增加。规则多而混乱,最后只会逼着大家关掉整个工具。
5. 把 Vale 放进真实工作流
5.1 本地写作:编辑器里的即时反馈
命令行检查是最小的使用方式,但真正的体验提升来自编辑器集成。VS Code 等编辑器有 Vale 相关扩展,会在你写作过程中直接把告警标注在文档里,类似编辑器对代码的实时纠错。这样你不需要等 CI 跑完才知道问题,写完一个段落就能看到。
使用编辑器集成时要注意一点:扩展会读取当前工作区的.vale.ini,如果你打开的是单个文件而不是项目目录,它可能找不到配置。把.vale.ini和styles/放在工作区根目录下,体验会稳定很多。
5.2 CI:文档检查成为合并卡点
把 Vale 放进 CI,是让它从“个人工具”变成“团队流程”的关键一步。常见做法是写一个 CI 任务,对文档目录执行vale docs/,然后根据返回状态决定是否阻止合并。实际工程里建议先用--minAlertLevel=error,只让 error 级别问题阻塞流程;等规则运行稳定了,再把 warning 也纳入拦截范围。
另一个常见实践是只检查本次变更的文件,而不是全量检查所有历史文档。代码 review 是看 diff,文档 review 也可以看 diff。如果某次 PR 只改了一个段落,把整本手册全部重新扫一遍,意义不大,还容易把历史问题混进本次变更。
5.3 存量文档:分级收口而不是一次拉满
针对一个已经积累了几百篇文档的仓库,我把落地路径总结成三步收口法:
- 只对新增或修改的文件开启检查,让新内容先符合规范。
- 规则级别先以 suggestion、warning 为主,观察命中情况,别急着上 error。
- 稳定运行两到四周后,选择核心目录开启 error 门槛,再逐步扩展到其他目录。
这样做的原因是:规范的落地本质上是一次行为改变,不是一次技术部署。全量扫一遍很容易,但让团队在一次次 PR 里接受告警、调整写作方式,才是真正难的部分。分级收口能降低反弹,也能让积累的规则逐渐变成团队共识。
6. 落地时最容易踩的坑
6.1 误报不是 bug,是配置边界问题
初用 Vale 时最常见的抱怨是“它把我的代码块也检查了”。这通常不是工具坏了,而是配置没有告诉它哪些内容要跳过。代码块、行内代码、URL 和命令示例是最常见的误报源。
Vale 提供了一些配置项来处理这类边界,比如BlockIgnores和TokenIgnores,可以用正则把命中的内容排除在检查范围之外。常见写法类似:
[*.md] BlockIgnores = (?s) *```.*?``` * TokenIgnores = https?://[^\s]+这段配置的意思是:让 Markdown 代码块不被整体检查,让 URL 不被当作普通词检查。实际落地时,正则需要根据你文档里的具体写法调整。
另外要记住,Vale 的很多内置规则面向英文写作。中文没有“大小写”概念,针对英文的 capitalization 规则在中文技术文档里要控制权重,不然会产生大量没有实际意义的告警。
6.2 排查链路:按层定位问题
当 Vale 表现异常时,我一般按这个顺序排查,而不是直接改规则:
- 看现象:是误报、漏报,还是命令本身没有执行成功。
- 看输入:文件扩展名是否匹配
.vale.ini里的 section;文件编码、BOM、换行是否异常。 - 看配置:
StylesPath路径是否正确,BasedOnStyles名称是否拼错,Vocab是否开启。 - 看规则:YAML 缩进是否正确,
extends类型名是否合法,level是否在允许范围内。 - 看版本:不同版本的 Vale 对个别规则字段的支持有差异,升级前先看变更说明。
- 看输出:用
--output=JSON拿到结构化信息,排查会比看纯文本方便很多。
这个顺序的核心思路是:先确定问题出在哪一层,再决定改哪里。很多人一遇到误报就删规则,结果删掉的是真正有价值的检查,问题反而没解决。
6.3 别把告警数量当 KPI
告警数量多不代表文档质量差,告警数量少也不代表规则有效。如果一条规则长期没有命中,先想想它是否真的适用;如果一条规则命中极多,反而要警惕它可能产生了大量“不想改”的噪音。
我建议定期看规则命中分布。把命中最高的几条规则拿出来人工复核,判断这些告警是真正推动了规范,还是只是在刷存在感。规则少而准,永远好过规则多而吵。
宁可规则少而准,不要规则多而吵。
7. 适用边界与长期价值
7.1 Vale 适合谁,不适合谁
Vale 不是万能工具。适合和不适合的场景都很清楚:
| 适合 | 不适合 |
|---|---|
| 维护 README、API 文档、发布说明的技术团队 | 需要深层语义理解或改写建议的写作场景 |
| 有内容规范但靠人记不住的团队 | 没有风格基线、自由创作高于一致性的个人博客 |
| 已经有 CI 流程、想加入文档检查的工程团队 | 期望工具自动“理解”一句话好坏的中文语义场景 |
| 需要统一产品术语、品牌名词的团队 | 认为编辑判断可以被规则完全替代的团队 |
特别是中文场景,Vale 更适合做“词级、术语级、结构级”的检查,而不是“这句话写得好不好”的语义判断。不要期待它像一个中文编辑那样理解你的行文逻辑,它真正擅长的是做一致性和规范性检查。
7.2 长期来看,它改变的是写作规范的“版本化”
如果把视角拉长,Vale 最值得关注的不是某条规则,而是它让写作规范第一次有了“版本化”的能力。规范一旦写成规则文件,就能像代码一样被 review、迭代、审计。团队讨论某个文档问题时,讨论焦点也会发生变化:从“我觉得你不该用这个词”变成“我们是不是该新增一条规则”。
这个转变比省几分钟人工 review 重要得多。新同事加入时不靠背规范,工具会提醒;对外发布时不靠某个编辑把关,流程会兜底。文档写作从个人经验变成团队资产的路上,Vale 是很扎实的一步。
所以,如果你问我 Vale 到底值不值得引入,我的回答是:值得,但别把它当成改稿工具。它更像一面镜子,让你第一次清楚看到团队的写作规范到底有没有被遵守。真正想用好它,第一步不是下载安装,而是先问自己:我们团队最重要的三条写作规则是什么?把这三条写成规则文件,再用 Vale 跑一个文件,你会立刻看到它和普通文档检查的差别。