
做中文工具链这些年我越来越确信一件事中文编辑器能不能被用户信任几乎完全取决于它背后的“完整纠错规则库”做得够不够扎实。CNSH中文编辑器这次发布 v2.0核心工作就是把整套规则库从头拆开重写了一遍。标题里“完整”两个字听起来像宣传话术实际上是被第一版用户的真实反馈逼出来的漏报太多让人心累误报更是直接让人想卸载工具。规则库调不好编辑器的纠错功能就是个摆设再好看的界面都救不回来。这篇文章不打算给你列功能清单我想讲的是 CNSH 中文编辑器的纠错规则库 v2.0 内部到底是怎么组织的规则按什么维度分类、每条规则如何触发和评级、规则文件怎么配置和导入导出、我在调优过程中踩过的坑以及最终怎么把一套规则库变成整个团队可以共同维护的资产。这些东西对三类人最有用正在做中文写作或校对工具的产品同学、维护编辑器或插件规则体系的开发者、以及单纯想搞懂“纠错到底是怎么工作的”的普通用户。1. 为什么中文写作比英文更需要“完整规则库”1.1 中英文纠错的本质差异英文拼写检查的难度主要集中在词典匹配。一个单词拼错了算法只需要在词表里做最近邻查找再辅助一点词性判断就能给出像样的建议。中文则完全不同句子没有天然的分词错误类型高度依赖上下文。“截止”和“截至”两个词都完全合法“做”和“作”、“连”和“联”也只有在具体句子里才能判断哪个正确。英文纠错的核心单元是“词”中文纠错的核心单元是“语境”。这一条差别直接决定了规则库的设计起点想靠一张静态词表覆盖所有中文错误必然同时掉进误报和漏报两个极端。我见过不少从英文拼写检查思路转过来的方案第一版几乎都会做一张“易错字表”然后按字形相似度去匹配。结果就是批量误报用户很快会把整个纠错功能关掉。CNSH v1 也有同样的毛病这也是 v2.0 把“上下文过滤器”设为所有规则的标准配置、而不是可选项的根本原因。中文纠错规则库必须内置上下文的概念否则根本没有资格叫“完整”。1.2 从 v1 到 v2用户反馈逼出来的升级方向v1 的规则库其实只做了三件事维护一张错别字表做标点全半角检查检测连续重复字词。上线之后的用户反馈高度集中在几个点上。第一错别字表经常误报。比如“密钥”这种正常词因为字表里只做了字形相似度匹配会被提示成“密玥”这种提示不但没用还让用户对编辑器的整体判断力失去信任。第二标点规则太死。英文引号、斜杠、反引号在技术文档和代码上下文里是合法用法却会被一律标红。第三完全没有成语、术语、惯用搭配层面的纠错用户费劲写的专业文本里出现的错误工具一点办法也没有。最要命的是性能扫描一万字左右的文档规则库跑完之后编辑框几乎进入假死状态输入一个字符要等半天才能看光标。v2.0 的目标就是从这一堆反馈里提炼出来的。我当时在项目文档里写了三条底线规则要分类规则要分级规则要可解释。如果一条规则说不清楚“为什么认为这里是错的”那这条规则就不允许上线。于是重写时把规则库拆成六大维度每条规则都标注错误级别和置信度并且默认挂接上下文过滤器。这些处理也许不像某个新功能那么显眼但直接决定了纠错工具到底是真的能用还是只能存在于演示视频里。1.3 “完整”到底指什么我在标题里说的“完整”不是指规则数量堆到了一个好看的整数。按我的定义一支合格的纠错规则库至少要满足三个条件。第一个条件是覆盖面。从单字错别字、标点规范到成分残缺、句式冗余再到语义层面的搭配异常不同层级的错误都该有对应的规则而不是只会抓错别字。第二个条件是分级响应。不是一发现就直接标红而是按错误级别给不同样式的提示确定错误、疑似问题、仅供参考三者必须分得开。如果所有问题都用同一种红色波浪线用户根本无法判断优先级要么全部忽略要么焦虑到逐条点击。第三个条件是可定制。不同用户、不同项目的标准完全不一样论文要求中英文标点严格区分产品文案则经常故意用口语化短句小说和剧本里更充满了“看似不规范但其实是风格”的写法。规则库必须支持用户级和项目级的增删改否则再好的内置规则也适应不了真实写作场景。这三点构成了 v2.0 规则库的骨架。接下来我会从规则体系的分类方式、底层判断逻辑、配置与性能、踩坑记录、团队协作五个角度展开把这次版本迭代里真正重要的东西一次讲透。2. v2.0 规则库的顶层拆解六维规则体系与错误分级2.1 六维规则体系整个 v2.0 把纠错规则分成六个维度。这样设计不是为了一张漂亮的架构图而是为了让每一条规则都有明确的职责边界一条规则如果不能被归入某一个维度说明它的触发条件还没想清楚不应该进入规则库。维度标识维度名称处理问题典型示例TYPO错别字与用词单字/多字错写、同音字、形近字“变本加利”建议“变本加厉”PUNCT标点与符号全半角、中英文标点混用、括号配对中文语境下英文逗号提示改为“”GRAMMAR语法与句式成分残缺、搭配不当、关联词逻辑“不但……而且……”搭配缺失STYLE风格与表达口语化、冗余、被动语态、长句连续多句以“了”结尾时提醒FORMAT格式与规范数字单位、日期格式、编号层级“2024年1月1号”提示补全为“日”SEMANTIC语义与一致性前后矛盾、术语不一致、指代不明同一文档中“登录”“登陆”混用TYPO 维度是最基础也最容易量化的它主要靠词表和字形、读音相似度组合判断适合处理明显的字词错误。PUNCT 维度在中文写作里非常重要全角逗号、句号、中文引号的使用规范在 v2.0 里被拆成了“语境敏感”规则技术文档中的英文标点不提示纯中文语境下的英文标点才提示这样就避免了误伤。GRAMMAR 维度难度最高它依赖模式模板而不是简单匹配比如检测“通过……使得……”这类句式时要避免把“通过努力我们取得了成绩”这种正确句子误判成成分残缺。STYLE 和 FORMAT 维度更像“编辑规范”而非“错误”很多个人用户会主动关闭但团队写作场景特别需要。SEMANTIC 维度在 v2.0 里主要做术语一致性和前后矛盾检测这也是规则库从“校对”走向“审校”的一种尝试。2.2 错误级别错误、警告与建议六个维度解决“管什么”错误级别解决“管多严”。v2.0 里每条规则必须声明一个基础错误级别目前分三级级别标记方式适用场景误报容忍度error红色下划线 一键修正按钮确定错误如错别字、括号不配对极低出现误报用户会立刻不信任warning黄色下划线疑似问题如“的地得”混用、量词不当中等允许用户自行确认suggestion蓝色浅提示优化建议如句子过长、被动语态多高提示太多用户会直接关闭三种提示在界面上默认带不同的操作error 可以直接一键替换warning 点击后显示原因和候选词suggestion 只提供可忽略的说明。这套分级最直观的作用是减少了“全篇都是红线”的压迫感。我见过很多工具在 v1 阶段把所有规则都设为 error结果用户每天面对一片红色很快就麻木了反而真正严重的错别字被淹没在大量“疑似问题”里这绝对不是设计者想要的效果。2.3 规则权重与置信度为什么不能只靠一个级别错误级别解决的是“展示方式”但规则触发还需要一个定量判断置信度。级别只告诉用户严重性置信度告诉规则引擎到底该不该触发。每条规则里都有一个 confidence 字段取值为 0 到 1。当模式匹配命中之后上下文过滤器会计算一个实际置信度只有它高于该规则预设的 threshold 时才会真正上报给用户。权重则用于排序和批量修正场景。举例来说错别字规则的 baseWeight 是 80风格规则只有 20。当一篇文章同时出现一个错别字和一段冗余表达时批量修正会先处理权重更高的错别字避免优先改动那些“可改可不改”的内容。为什么这样设计因为中文文本的歧义性很高单条规则很难百分之百确定错误。分级加阈值本质上是把一部分判断责任分摊给用户确定的事自动改不确定的事请用户拍板。这样误报对信任的伤害就能降到最低规则库也会显得“有分寸”。3. 核心纠错原理与规则写法详解3.1 三层判断引擎v2.0 的规则执行不是一条正则走天下而是分三层。最底层是词典与实体库包含现代汉语常用词、成语、人名地名、领域术语和一部分网络用语。中间层是模式匹配层负责执行正则和模板规则把文本变成候选命中。顶层是上下文决策层结合句子边界、前后段落、文档类型进行最终判断。三层之间有明确的数据流模式匹配层先跑命中后把候选交给上下文决策层上下文决策层如果认为当前环境不满足条件就直接丢弃候选。这种设计避免了“正则一命中就上报”的大量误报。比如“登陆”这个词如果出现在“登陆页面”里并且文档语境是访问网站那可以被提示为疑似“登录”但句子换成“台风登陆沿海城市”就不该提示哪怕字形上两者一样这里取的是地理语义。这个区别靠词表判断不出来必须靠上下文决策层。3.2 常见中文错误的规则表达我在 CNSH 里把规则统一存储在 JSON 格式的文件中每条规则有一个全局唯一 ID。下面是一条错别字规则的示例{ id: TYPO-1042, dimension: TYPO, name: 变本加利-变本加厉, pattern: 变本加利, suggest: 变本加厉, level: error, confidence: 0.98, threshold: 0.85, context: { notAfter: [是, 为], sentenceIndependent: true } }这段 JSON 表达的意思是当文本中出现“变本加利”时建议改成“变本加厉”错误级别为 error置信度达到 0.98只要上下文过滤器的阈值不低于 0.85 就会触发。notAfter指定了“是”“为”等字符之后不算命中这是为了防误报。比如“这种做法更为变本加利”这种口语化表达虽然不规范但强行替换成“变本加厉”也不对最好留给 warning 级别去处理。再举一个更容易混的例子“截止”和“截至”。规则不能简单地提示“截止 - 截至”或者反过来因为两个词的语义完全不同。“报名截止日期”是正确用法“报名截至日期”才是错的而“截至昨天共收到 100 份申请”又是正确用法。v2.0 的做法是把规则写成上下文模板{ id: GRAMMAR-0217, dimension: GRAMMAR, name: 截至误用检查, match: [截至{NUM}{DATEUNIT}, 截至{DAY}], check: 前置时间段如昨天/本月底/今天下午时建议使用截至, level: warning, confidence: 0.9, threshold: 0.75 }如果用正则表达大概是截至(?(昨天|今(?:日|天)|本周末|月底|...))但模板写法对维护者更友好也更容易被非程序员理解。这里的关键是规则要能描述“什么时候不能用”而不只是简单说“哪个词错了”。3.3 自定义规则的语法与示例v2.0 支持两种自定义方式JSON 规则和内置 DSL 脚本。对于大多数用户JSON 足够把 pattern、suggest、level 三件套填好规则就能跑。需要写 DSL 的场景往往是组合条件比如“当某个词在标题中出现且正文中出现另一种写法时提示统一术语”。DSL 脚本示例如下rule UNIFY_TERM: category SEMANTIC level warning when: doc.contains(监控器) and doc.contains(监测器) and doc.section 2 suggest: 同一章节内监控器/监测器混用请统一术语这里的 doc 对象是上下文决策层提供的接口包含全文、句子边界、段落编号等基础信息。自定义规则会被编译器转换成与内置规则相同的中间表示再进入同样的三层引擎执行。这保证了用户规则不会因为“走的是另一套逻辑”而在性能和稳定性上落后内置规则。自定义规则还有一个很实用的能力项目级词表扩展。比如你正在写一本文案集需要把“手记”作为一种特定文体保留不希望它被 STYLE 维度的“口语化”规则提示。这时只需要在项目规则文件里加一条抑制规则{ id: CUSTOM-0003, dimension: STYLE, name: 排除手记文体提示, action: suppress, targetRule: [STYLE-0811], condition: text.startsWith(手记) || text.endsWith(手记) }这条规则本身不是纠错规则而是一条元规则指定在什么条件下暂时停用某条内置规则。可定制不代表规则库混乱靠的就是这种“规则之上还有元规则”的管理方式。4. 落地配置项目级规则库、导入导出与性能调优4.1 规则文件格式与项目结构规则库 v2.0 在磁盘上是一整套目录推荐的结构是这样rules/ ├── builtin/ │ ├── typo/ │ │ ├── basic.json │ │ ├── similar.json │ │ └── idioms.json │ ├── punct/ │ │ ├── halfwidth.json │ │ └── quote.json │ ├── grammar/ │ ├── style/ │ ├── format/ │ └── semantic/ ├── user/ │ └── my_rules.json └── project/ └── .cnshrules.json内置规则按维度拆分文件方便维护和裁剪。用户规则放在 user 目录项目规则放在项目根目录的.cnshrules.json里。编辑器在打开项目时会按“内置规则 - 用户规则 - 项目规则”的顺序加载后加载的规则在冲突时取得优先权。为什么不让项目规则单独生效因为大多数人想要的是“在内置规则基础上做补充”而不是推倒重来。内置规则维护了大量通用场景项目规则只需要覆盖团队特化需求两者叠加才是完整状态。4.2 规则优先级与合并策略多来源规则的合并策略在 v2.0 里用一张优先级表就能说清楚冲突类型处理策略示例同一 ID 的规则定义冲突项目规则覆盖用户规则用户规则覆盖内置规则项目把某条内置规则的 level 从 error 降为 warning同维度同文本的多条规则取置信度最高的规则错别字规则与风格规则同时命中错别字优先抑制规则suppress永远优先执行项目规则禁止某条风格规则在当前文档中触发自定义新规则与内置规则文本相同双方都保留在结果层去重避免同一位置出现两条相同提示这套策略保证了规则库既可以深度定制又不会在合并之后产生行为爆炸。导入导出方面v2.0 支持两条命令cnsh rules export和cnsh rules import。导出文件是带校验和的 JSON 包导入时编辑器会做三件事校验 JSON 语法、检查规则 ID 是否冲突、跑一遍自检样例集验证新规则没有制造明显的批量误报。这个“导入即验证”的步骤很重要也是我在 v1 里没做而后悔很久的事情。没有校验的导入等于允许用户把一颗没测试过的炸弹塞进编辑器核心。4.3 性能实测与优化建议规则库大了之后最现实的问题就是性能。我做过一组实测数据测试机是 M 系列芯片的笔记本文本是一万字左右的中文技术文档场景规则数量首次全量扫描耗时输入时增量检查单帧关闭规则库0约 8ms每帧低于 1msv1 全部启用约 3000 条约 780ms编辑框卡顿明显偶发超过 200msv2 内置规则6000 条约 210ms平均 12msv2 内置 300 条自定义6300 条约 235ms平均 15ms从 780ms 降到 210ms主要不是硬件差异而是重写时做了三件事。第一是正则预编译v1 里每条规则都在运行时即时编译正则v2 在加载阶段统一编译并缓存省掉了重复编译的开销。第二是分组扫描把规则按触发词拆分文本先通过一个很轻的 Aho-Corasick 自动机筛出候选只有候选命中的文本片段才进入昂贵的正则和上下文判断绝大多数无关文本在前置筛选中就被跳过了。第三是限制回溯写正则时统一要求控制最大回溯深度并且避免嵌套量词防止个别畸形文本把引擎拖入几十毫秒的循环。增量检查也做了优化。编辑器在输入时只对改动行做模式匹配上下文决策层才访问附近段落。这个思路其实是模拟人类审稿习惯先判断一句话本身再看它和左右邻居的关系。自定义规则如果写得不好性能开销可能比内置规则大得多所以我给用户文档里写了三条建议尽量用固定字符串当触发词不要用全正则做扫描不要在 pattern 里写过多贪心匹配每条自定义规则都要配一个最小测试样例。5. 误报与漏报我在调优过程中踩过的坑5.1 误报规则不知道“这是故意的”我踩过最典型的误报发生在 v2 早期测试“的地得”规则的时候。当时内置规则会把“高兴的跳了起来”提示成“地”这在大部分场景下是对的。但用户反馈说剧本、小说对话里的“高兴的跳了起来”是作者故意用的口语风格规则库完全不知道“这是故意的”。后来我们在上下文决策层里加了一个 protect 区段概念。用户在文档中把某些区域标记为“剧本”“小说”“歌词”STYLE 和部分 GRAMMAR 规则在这些区域就会默认降低置信度。更通用的一种做法是凡是出现在引号、书名号、代码块里的内容规则默认不触发除非规则本身设置了allowInQuote: true。这样既保留了错别字检查又不会错误建议修改别人的对话原文。另一类高频误报来自术语和品牌名。比如“果壳”在科普文章里是正常词但规则表里如果有一条“果壳 - 果核”的形近字规则就会误报。解决方式是引入一个用户术语白名单白名单中的词在任何规则里都豁免这比在每条规则下加例外条件要省事得多维护成本也低一个量级。5.2 漏报规则粒度与语境盲区漏报比误报更隐蔽因为用户不会主动告诉你“这里应该提示而你没提示”。我在调优中发现三个典型的漏报场景。第一个是规则粒度太粗。比如“做/作”辨析单独匹配“做”几乎不现实因为没有上下文根本无法判断。但如果规则定义成只在“做/作 名词”的组合中触发覆盖又会漏掉很多动宾结构。最终方案是同时维护一张高频动宾搭配表把“做贡献”“作贡献”、“作报告”“做报告”这种高频组合单独列成规则一对对去校。第二个是语境盲区。文档里第一次出现某个术语用“登录”后面二十次全用“登陆”v1 完全不会管。v2 把这类问题交给 SEMANTIC 维度处理检测同一文档内近义词的分布如果两种写法同时出现且频率都超过阈值就提示统一术语。这种规则不是为单个错误服务的而是为“一致性”服务的。第三个是长距离依赖。典型例子是“虽然……但是……”的搭配前半句在第 3 段后半句在第 4 段单段正则匹配根本发现不了。我们的临时方案是允许规则声明一个 window 参数把候选句子周边 500 字视为一个滑动窗口在窗口内做关联词检查。代价是性能开销增大所以这类规则默认关闭只在用户手动开启“深度审校”时启用。5.3 处理误报的机制忽略列表、规则抑制与一键反馈无论规则写得多么小心误报都会存在。v2.0 的做法不是追求零误报而是把误报处理流程做得足够短。编辑器在每条提示的下拉菜单里放了三项操作忽略此条、添加白名单、反馈误报。忽略此条只对当前错误标记生效添加白名单会把触发文本加入用户术语表反馈误报会连同文档片段和规则 ID 一起生成一份匿名样本后续版本据此修正。这个闭环是整个规则库能够持续改进的根本原因而不是靠维护者拍脑袋想当然。我自己的习惯是每周导出一份误报反馈记录按规则 ID 分组统计。哪条规则的误报率超过 10%就要回炉重调。看数据比凭感觉调规则靠谱得多因为主观感受太容易受到最近几条反馈影响只有汇总数据能还原真实分布。6. 让规则库成为团队资产团队共享与版本管理6.1 规则评审与灰度切换规则库做到 v2.0我发现真正的难点已经不是“怎么写规则”而是“怎么让团队持续维护规则”。在个人场景里规则错了只影响自己在团队场景里一条有问题的规则上线可能让所有人对工具的信任瞬间归零。团队里每个人都可能提一条自定义规则但规则质量参差不齐。我们采用的做法是所有自定义规则先进入独立的“建议区”不直接生效。规则写完后至少要有两个人评审通过才能提升为正式团队规则。评审的核心不是看正则写得对不对而是看三点这条规则有没有明确的场景误报风险有多大有没有对应的测试样例这里还有一个实用技巧灰度切换。新增一条规则时先在团队内部分 10% 的用户灰度观察误报反馈。如果两周内误报率低于 5%再全量开放。我见过一条规则在灰度期误报率高达 40%如果直接全量上线整个团队的信任度就崩了。灰度机制是规则库作为团队资产必须具备的能力不是可选加分项。6.2 规则变更日志与自动化测试规则库既然是项目的一等公民就应该像代码一样有版本管理和测试。v2.0 引入了 ruleset 版本的概念。项目中的.cnshrules.json可以声明自己基于哪个内置规则集版本当内置规则升级时项目管理员会看到一份“变更影响分析”$ cnsh rules diff --base builtin-v2.0 --target builtin-v2.1 TYPO-1042 level: error - warning (影响 12 个项目) STYLE-0811 deprecated: 请使用 STYLE-0812 替代 新增规则: GRAMMAR-0220这份报告会把可能影响项目的行为逐条列出避免“工具更新完项目行为悄悄变了”的情况。这种清晰度非常重要因为编辑器更新往往并不可见用户只觉得“好像提示变多了”但说不清为什么。测试部分我们为规则库维护了两套测试集。一套是正样例集包含所有规则应当命中的文本一套是负样例集包含所有规则不应误报的文本。每次规则变更都要完整跑测试集回归测试不过关的规则不允许发布。最初我觉得这种做法有点过度工程直到一次误报事故影响了对外发布文章之后我才意识到规则库的质量门槛必须和代码同等级。一个 bug 影响的是一个功能一条错误规则影响的是所有文档的观感。6.3 规则共享与协作让规则库长在项目上最后聊聊团队协作里最实际的规则共享。v2.0 在团队版里做了一个规则集的概念有点像代码仓库里的包管理机制。团队成员可以把经过评审的规则打成包其他项目通过一条命令引入cnsh rules install team-style-guide --version 1.3.0引入的规则包默认是只读的如果要修改必须 fork 成自己的包。这个设计避免了“规则包被某个成员无意修改后牵连其他项目”的隐患。每个项目可以同时引入多份规则包比如一份来自公司内容团队一份来自具体项目组再用项目级配置做最终覆盖。我把这个思路总结成一句话规则库不是配置文件它是团队知识资产的一部分。它记录了团队对“什么是对的、什么是错的”的一致判断。因此给它配上版本管理、评审流程和自动化测试是完全值得的投入。这也是 v2.0 对我而言最有价值的改变规则库从一个工具的内置功能变成了可以持续演进的独立体系。关于这次 v2.0 规则库的重写我最后想分享一个很朴素的体会。做了这么多年中文编辑工具我越来越觉得所谓“完整纠错规则库”真正重要的不是规则数量而是规则的分级、可解释和可治理。我在实际维护中养成了两个习惯第一每一条规则上线前先在真实文档上跑一遍眼见为实只看样例集永远不如拿一篇文章实测来得直接第二误报反馈一定按规则 ID 归因别让“整体体验不好”这种模糊结论掩盖掉具体问题。如果你也在做中文编辑、校对或者内容审校相关的工具建议先从小而精的规则集开始把一条规则吃透再扩大覆盖面这条路比一开始就堆几千条规则稳得多。希望这些踩坑经验能让你少走几步弯路。