“impeccable”这个词我用了很久,最早不是拿来当项目名,而是被客户当场教育出来的。前几年我负责给一个长期合作的内容平台做交付,团队每次输出前都要走三遍人工校对,结果还是被对方的质控部门挑出一个“第四节表格列宽错位”的问题。对方没有骂人,只回了一句话:你们交付的东西,连最基本的格式都做不到 impeccable。
说真的,这句话把我堵得没话说。格式错位、术语不统一、标题层级混乱、链接过期……这些问题单看都不致命,但堆在一起就是灾难。更要命的是,传统人工校对既慢又靠不住,同一个错误换个人检查可能就漏过去了。所以我后来花了不少时间,把“质量”从人的责任心问题,变成一套可以自动化、可重复、可量化的校验流程,代号就叫 impeccable。
这篇我不打算讲什么玄乎的大道理,就把这套东西怎么设计、怎么配置、怎么接入日常工作流、以及我踩过的坑一次说清楚。它适合所有每天要交付文档、配置、代码、甚至活动文案的人——只要你的产出是“白纸黑字”,这套思路就能用得上。
1. 为什么我非要做一套叫 impeccable 的质量校验
1.1 一个低级格式错误引发的连锁反应
那次客户投诉之后,我回团队复盘,发现事情远没有“改个表格”那么简单。当时的交付流程大致是:作者写完,编辑改一遍,校对员再改一遍,最后我终审。听起来三重把关,实际上每个人都在默认“别人已经检查过了”,于是真正认真看的只有最后一关的我。
更要命的是,每个人的检查标准还不一样。有人只管错别字,有人只管排版,没人对“标题是否跳级”“中英文之间有没有空格”“专有名词大小写是否统一”这些东西负责。结果就是:所有小问题都攒到最后,靠某个人肉眼硬扛。
这件事让我意识到,质量问题的根源不是“人不负责”,而是“标准不可见、不可测、不可自动化”。你没法要求人在疲惫的时候记住四十条细则,但你可以让机器在几秒钟内检查完所有细则。
1.2 impeccable 不是“检查器”,而是一套交付心法
很多人一听“质量校验”,第一反应是找个工具跑一遍。但我做 impeccable 的初衷,不是简单地做检查器,而是想设计一套“交付前必须过一遍的关卡”。关卡本身要足够快、足够明确、足够少误报,这样团队才会愿意用,而不是当成又一个形式主义流程。
我给自己定了三条硬性要求。
第一,所有规则必须是可配置的,而不是写死在代码里。因为团队的项目不同,有的写技术文档,有的写营销文案,有的写开源项目 README,标准天然不一样。
第二,规则默认保守,宁可少报,不可误报。误报一次,团队就会开始忽略所有报告;漏过一次,至少不会让工具失去公信力。
第三,必须能在命令行跑,也要能在 CI 里跑。因为只有把校验嵌到“提交代码”或“发布上线”的必经之路上,它才能真正拦住坏交付。
基于这三条设计原则,我把 impeccable 拆成了两个部分:一个是解析规则配置的小引擎,一个是基于规则输出检查结果的命令行工具。
1.3 解决了谁的什么问题
用下来之后,我最直观的感受是:它把“质量焦虑”从人身上转移到了流程上。
对普通写作者来说,impeccable 相当于一个不知疲倦的校对助理,它会在你提交之前把“标题层级错乱”“列表符号不统一”“超链接没有标题属性”这类问题全部抛出来;对技术负责人来说,它把“终审”这个环节从人工抽检变成了全量检测;对新加入团队的成员来说,它本身就是一份活着的团队规范文档,比读十遍 wiki 都管用。
我也拿它处理过不少非技术内容。比如广告物料里的合规词过滤,合同模板里的敏感信息占位符检查,甚至公众号排版中的中英文混排检查。只要输出内容是文本,impeccable 的规则体系就都能覆盖。
2. 整体设计:规则为先,还是约束为先
2.1 设计理念:把“人靠不靠谱”变成“流程靠不靠谱”
我先回答一个很多人会问的问题:为啥不直接用现成的 lint 工具?其实我早期也试过拆一堆现成工具,比如用于 Markdown 的 markdownlint、用于拼写的 codespell、用于链接检查的 lychee。它们都很优秀,但问题是:一套项目要同时接三四个工具,配置语法还不一样,CI 里还要串行跑好几遍,输出格式更是五花八门。
这样的方案维护成本太高,团队成员接受度也低。所以我干脆做了一个统一入口:impeccable 内部可以调用规则引擎,但对外只暴露一种配置格式、一种 CLI 输出、一种退出码逻辑。这样不管是本地跑还是 CI 集成,心智负担都很小。
从流程角度看,我把它设计成三段式流水线:读取文件、抽取文本、执行规则。你可能会觉得这没什么技术含量,但这个分层恰恰是关键。因为只有把“读取”和“执行”分离,才能做到一套配置同时处理 Markdown、纯文本甚至代码注释。
2.2 技术选型:为什么用 Python 加纯文本约定
技术选型上我考虑过 Node.js、Go,最后选了 Python。原因很简单:团队里最不缺的就是 Python 背景的人,后续想改规则、加插件、做内部的 Web 小面板都方便。而且 Python 的正则生态和文本处理能力非常成熟,搞这类校验工具属于舒适区。
不过真正核心的设计决策不是语言,而是“纯文本约定”。什么意思?就是 impeccable 的配置本身就是一个 YAML 文件,里面只描述“哪种文本应该满足什么条件”。这样做的好处是:规则和代码完全解耦,新人上手时打开配置文件就能看到所有校验项,不需要读一行源码。
配置文件长这样:
rules: structure: heading_level_jump: error typography: auto_space_between_cjk_and_latin: warning terminology: - rule: consistency terms: - impeccable - Impeccable links: validate_url: true safety: banned_words: level: error这条规则声明读起来非常直白:结构类的“标题层级跳级”是错误;排版类的“中日韩字符与拉丁字符之间自动空格”是警告;术语类的“impeccable”必须统一大小写;链接要验证可达性;违禁词默认禁止。
我故意避开了复杂的 DSL(领域特定语言),因为校验规则的绝大多数场景用“级别加参数”就能描述清楚,没必要引入学习成本。
2.3 配置引擎的价值:为什么不能硬编码
有人可能会说,规则直接在代码里写死更快,何必搞配置化?我拿一个实际场景回答你。团队 A 要求中英文之间必须加空格,团队 B 因为在做日文文档,反而要求不加空格,这种矛盾如果写死在代码里,你就只能改源码、发版本;但写成配置,团队各自维护自己的 YAML 就行。
同理,术语表是长期演进的产物。你不可能在工具发布的时候就把所有禁用词、统一词穷举完,更合理的做法是工具提供一个“词表挂载点”,让使用者按需补充。配置化带来的灵活性,短期看是额外设计成本,长期看是维护成本的大幅降低。
我还在引擎里支持了配置继承,也就是说团队可以维护一份基础配置,不同项目再覆盖部分规则。这个设计让我不用在每个项目里复制粘贴完整配置,基础规则升级时也只需要动全局那一份。
3. 核心细节解析与实操要点
3.1 七类规则逐条拆解
impeccable 的规则体系,我按内容类型分成了七类,每一类的侧重点完全不同。
结构类规则处理文档骨架。比如“标题层级只能逐级递减”“文档必须包含 H1”“目录锚点必须存在”。这类规则最适合放在最开始跑,因为骨架错了,后面的很多细节检查都没有意义。
排版类规则处理视觉呈现。包括中英文之间空格、全角半角混用、行尾多余空格、列表项符号统一等。这类问题肉眼能看见,但高频且烦人,恰好是机器最擅长的。
语言类规则处理语法与措辞。我内置了一个精简的语法检查接口,可以外接 LanguageTool 或自定义词性规则。同时会标记过长的句子、连续出现两次以上的弱化词,比如“非常”“其实”“基本上”。这类规则默认是 warning 级别,因为语言风格的主观性太强。
术语类规则处理专有名词与命名一致性。多数情况是维护一个术语对照表,比如“Markdown 不能写成 MarkDown”“API 不能写全小写 api”。这是我从客户投诉最多的点中总结出来的高价值规则。
链接类规则处理外部依赖的可靠性。只要文档里出现链接,就走 HTTP HEAD 请求验证可达性,同时检查图片是否有 alt 文本、链接是否有可读的锚文本。这条规则帮我抓出过大量失效的文档外链。
合规类规则处理交付红线。比如过滤广告法违禁词、脏话、不合规的绝对化用语、内部占位符等。这里没有固定词表,而是依赖每家公司自己的业务配置。
清单类规则处理交付必需元素。比如文档必须包含版本号、作者、更新日期、变更记录。这类规则的本质是把“团队约定”变成“机器强制”,防止大家在忙乱中漏掉关键信息。
3.2 规则参数与匹配逻辑
规则不是简单的“开或关”,很多需要参数化。我举几个真实的配置片段。
中英文空格的规则,核心就是一条 Unicode 边界正则,但它需要两个参数:允许的语言区间和忽略场景。如果不忽略代码块、链接地址和数学公式,工具就会在正常的 Markdown 代码段里疯狂误报。
typography: auto_space_between_cjk_and_latin: level: warning ignore_in: - code_block - link_url - inline_code cjk_ranges: ["4E00-9FFF", "3400-4DBF", "F900-FAFF"] latin_source: "A-Za-z0-9"标题层级跳级的规则,则要构建一个“本级标题深度”的栈。每遇到一层新标题,就检查它的深度是否大于等于当前最小深度,如果出现 H1 直接跳 H3,就报告错误。但这规则有一个坑:Markdown 在某些平台里会把 H1 当成文档标题,正文通常会从 H2 开始,所以配置里必须有一个allow_start_at: 2的参数,否则所有正常文档都会被误报。
链接校验规则也值得展开。它的参数包括超时时间、是否走 HEAD 请求、是否忽略特定域名、是否允许内网地址。我通常把超时设为3秒,并发数设在10,否则文档里 200 个链接会跑得非常慢。因为外部站点对 HEAD 请求的支持不一致,我在引擎里做了一个降级策略:HEAD 失败后自动重试 GET,只取状态码,不下载完整内容。
3.3 误报率控制:阈值、白名单、上下文判断
实际使用中,误报是最好的”劝退工具”。没有误报,用户才会认真看检查报告。所以我花了很多精力在误报率控制上,主要用了三个手段。
第一个是阈值。比如句长规则,不是“超过 60 个字符就报错”,而是“超过 100 个字符才报 warning,超过 160 个字符报 error”。正常中文长句在 80-120 字之间波动,阈值定得太低会让全篇都是黄色 warning,最后没人看。
第二个是白名单。任何规则都配套一个全局白名单,里面填写允许忽略的具体文本或正则。比如术语规则要求“API”全大写,但代码注释里出现api_client是合理的,那就在白名单里加上/\bapi_client\b/。白名单应该支持精确匹配和正则匹配两种模式,精确匹配用于单点豁免,正则匹配用于同一类场景。
第三个是上下文判断。例如“列表项末尾句号是否统一”这条规则,如果只看句号本身,会拿中英文句号的标准去卡,很容易误报。我的引擎会把列表项分成“短语型”和“完整句子型”,短语型不检查末尾标点,完整句子型才要求结尾有句号或感叹号。这个逻辑用不到 AI,纯词法和句法启发式就够了,却能大幅降低误报。
4. 实操过程与核心环节实现
4.1 从零初始化一个校验项目
下面按实际操作顺序走一遍,你可以直接照着做。
第一步,安装。impeccable 是一个 Python 包,我建议用虚拟环境或者全局隔离工具安装,避免依赖冲突。
pip install impeccable-cli impeccable --version第二步,初始化配置。在项目根目录执行:
impeccable init --template standard它会生成一份.impeccable.yml和.impeccable-ignore文件。前者是规则配置,后者是文件豁免清单。初始化完成后,我建议先跑一次基线扫描:
impeccable scan .此时你会看到大量检查项提示。这一步不要急着全改成 error,正确的做法是先让团队讨论一下:哪些规则是团队的硬性红线,哪些只是建议。把规则级别定成三档:error 代表阻断交付,warning 代表需要人工确认,info 代表仅记录。
第三步,逐步收紧。不要第一天就启用五十条规则,那样报告会爆炸。我推荐第一周只开结构类、排版类和术语类中最明确的十五条,等团队习惯后,再逐步开放链接类和合规类。让工具从“渐进增强”而不是“突然施压”的姿态进入流程。
4.2 命令行输出与 CI 集成
配置完成后,本地跑一次全量检查的输出大概长这样:
$ impeccable scan docs/ --format table CHECK FILE RULE LEVEL MESSAGE heading_level_jump docs/guide.md 结构规则 error H1 后直接出现 H3,缺少 H2 link_checker docs/guide.md 链接规则 warning https://example.com/page 返回 404 term_consistency docs/api.md 术语规则 error 出现 api,应为 API trailing_spaces docs/quickstart.md 排版规则 info 第 18 行末尾有 3 个空格 5 files scanned, 2 errors, 3 warnings, 4 info退出码逻辑很关键:有 error 时退出码为 1,有 warning 但不影响上线时退出码为 0。为什么 warning 不阻断?因为在代码仓库里,warning 阻断很容易导致无害的“红色管线”,把 CI 变成形式主义;error 阻断则可以守住底线。
所以我在 CI 里的集成方式通常是:
impeccable scan docs/ --format junit > impeccable-report.xml if [ $? -ge 1 ]; then ./notify_teams.sh "文档质量检查未通过" fiJUnit 格式是为了在 GitLab CI、Jenkins 这些系统里直接看测试报告。它比纯文本的体验好太多,失败列表会以测试失败的形式直接展示在合并请求页面上。
4.3 规则库扩展与自定义规则写法
内置规则总归不够用,impeccable 的架构里自定义规则是接入 Python 插件的界面。我给你们写一个简单示例:检查文档里是否包含 “TODO” 关键词,这是很多交付场景里的硬性要求。
# custom_rules.py import re from impeccable.rules import RuleContext, BaseRule class NoTodoRule(BaseRule): name = "no_todo_allowed" scope = "line" def check(self, context: RuleContext): for line_no, line in enumerate(context.lines, 1): if re.search(r"TODO|FIXME", line): self.report(context.path, line_no, "禁止遗留 TODO/FIXME", level="error")然后在配置里注册这个插件:
plugins: - path: ./custom_rules.py rules: no_todo_allowed: enabled: true level: error从这里能看出来,自定义规则不是“必须改核心代码”的重型操作,而是继承一个基类、实现一个方法的事。我把插件接口设计得足够薄,就是为了避免团队因为扩展门槛太高而被迫接受不完整的校验。
5. 常见问题与排查技巧实录
5.1 高频问题的排查速查表
我把实操中收集到的高频问题整理成了一张表,按出现频率排序,供你遇到同款情况时直接对照。
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 扫出来的错误比预期多非常多 | 配置了多条重复交叉的规则,或模板未按项目调整 | 先跑impeccacle rules --only-enabled列出实际启用的规则,删掉冗余项 |
| 同一份文件上次扫没问题这次全是错 | 依赖的在线服务不可用,比如链接校验超时 | 检查网络代理或把 URL 校验临时切换为offline模式 |
| 中文句子被拆成多个语块误报 | 文件编码不是 UTF-8,Python 打开后出现解码错位 | 转成 UTF-8 编码,关闭 BOM 头 |
| 配置文件改了但报告没变化 | 缓存了旧配置 | 使用impeccable scan . --no-cache强制刷新 |
| 自定义规则没有生效 | 插件路径写的是相对路径但当前目录不对 | 在配置里改用绝对路径,或从项目根目录启动 |
| 链接校验特别慢 | 文档包含大量外链且未开启并发 | 调整link_checker.max_concurrency参数 |
| 忽略文件不生效 | 忽略了目录但没递归,或者.impeccable-ignore里的规则格式错误 | 空行彻底去掉,检查正则是否以docs/而不是docs开头 |
这张表的每一行都是从真实使用场景里挖出来的,哪怕你暂时用不到,也可以先存着。
5.2 三个我踩过的比较深的坑
第一个坑是关于自动修复的。一开始我在工具里加入了--fix参数,想着自动修空格、修行尾空格、修标题层级,很贴心。结果发现它把 Markdown 里特殊语义的空格也“修”了,比如数学公式里的空格、缩进代码块里的空格,导致文档渲染直接崩坏。后来我果断把自动修复拆成“safe-fix 只处理明确的排版问题”和“全量修复默认关闭”两类。你的工具里如果也有自动修复功能,我强烈建议把所有修复操作都放进 dry-run 模式。
第二个坑是术语规则里的词形变化。很多专有名词在不同上下文里会发生变形,比如“color”和“colour”、“analyze”和“analyse”。如果直译成“两个词不能混用”,英语文档里的合理混页都会被误报。最后我引入了一个“忽略带有前缀或后缀的词”的参数,让它只精确匹配独立单词,不匹配通配变形。
第三个坑是 CI 环境里的禁用外网。有些 CI 流水线运行在隔离网络里,链接校验规则就会把“因为网络不可达”误报成“链接已失效”。从那以后,我让引擎同时检测网络可达性,如果连 DNS 解析都失败,就进入“跳过所有外链检查”的模式,并在输出中明确提示“链路不可达,已跳过外链校验”。
5.3 一点心得:质量工具的终极目标是“消失”
用了半年之后,我发现一个好质量工具应该让人感受不到它的存在。不是因为它没报错,而是因为报错越来越少。团队磨合到后面,提交前顺手跑一下 impeccable 已经成为肌肉记忆,产出文档能一次通过校验的比例从最初的不到一半涨到九成以上。这时工具不再是皮鞭,而变成了安全网。
我会建议每个准备引入质量校验机制的团队都从最小可行规则开始,不要追求一步到位。先把最痛的三五条规则跑通,形成正向反馈,再把缺失的规则一点点补回来。工具存在的意义不是证明你错,而是让你可以放心地把注意力放到真正需要人判断的事情上去。
最后还是分享一个小技巧:把你的规则配置文件当成代码来评审。每周抽半小时过一遍配置变更记录,看看哪些规则被频繁豁免、哪些规则从 error 被降成 warning。这本身就是对团队“质量标准是否合理”的持续校准。质量校验不是一道静态的关卡,它应该随着业务需求一起流动,而 impeccable 只是那个替你忠实执行标准的守门员。