基于规则引擎的代码与文档质量校验工具设计与实现 1. 一个词撑起一个项目为什么“impeccable”值得单独拿出来做第一次看到“impeccable”这个词被当成项目标题我脑子里冒出来的不是词典释义而是一个很具体的场景你交出去的东西对方挑不出毛病但又说不出哪里特别惊艳——就是那种“无懈可击”的状态。这个词在英文里表示“无可挑剔的、完美的、毫无瑕疵的”它不像“awesome”那么张扬也不像“perfect”那么绝对它带着一种克制的高级感不是炫技而是每一个细节都经得起推敲。我之所以对这个标题感兴趣是因为它天然指向了一类非常实用的项目方向——质量校验与细节打磨工具。你可以把它理解成一个“挑刺引擎”输入一段文本、一份代码、一张设计稿、一份文档它帮你找出那些不致命但很掉价的问题。比如中英文混排时多了一个空格、代码里变量命名风格不统一、文档里标题层级跳级、设计稿里两个元素间距差了2像素。这些问题单看都不大但堆在一起整体质感就垮了。这个项目适合谁来参考三类人。第一类是独立开发者一个人要兼顾代码、文档、界面没有团队帮你review需要一个自动化的“第二双眼睛”。第二类是内容创作者和运营每天产出大量文案和物料格式规范全靠人肉检查效率低还容易漏。第三类是对交付质量有执念的技术人不满足于“能跑就行”想让自己的产出在细节层面站得住。接下来我会把这个项目拆成几个层面来讲整体设计思路是怎么定的、核心校验规则怎么设计、具体怎么落地实现、以及实际跑起来会遇到哪些坑。所有内容都围绕“impeccable”这个核心概念展开把它从一个形容词变成一套可执行的质量标准。2. 整体设计思路把“无可挑剔”翻译成可执行的规则2.1 从模糊感受到可量化标准“无可挑剔”是个主观判断但项目要落地就必须把它拆成客观规则。我的做法是建立一个三层校验模型第一层是硬性规则违反就是错误比如代码语法错误、Markdown标题跳级、链接失效第二层是风格规则违反算警告比如命名风格不统一、中英文之间缺空格、列表标点不一致第三层是建议规则属于优化提示比如某个函数太长、某段文案可以更简洁。为什么这么分因为如果把所有问题都当成错误用户会被淹没在告警里最后干脆全部忽略。分层之后用户可以按需开启赶时间的时候只看硬性错误做最终交付前把风格规则也打开追求极致的时候连建议一起看。这个设计参考了主流代码检查工具的成熟实践但把适用范围从代码扩展到了文本和文档。三层模型的另一个好处是可配置。不同项目对“无可挑剔”的定义不一样技术文档可能要求中英文之间必须加空格但营销文案可能更在意语气一致性。规则分层之后每一层都可以单独配置阈值和开关不会一刀切。2.2 为什么选择规则引擎而不是机器学习有人可能会问现在大模型这么强为什么不直接让模型来判断“这段内容是否无可挑剔”我实际试过结论是大模型适合做建议不适合做校验。原因有三个。第一一致性差。同一段文本你今天问和明天问模型可能给出不同判断。但质量校验需要的是确定性同一个问题每次都必须被检出否则用户没法信任结果。第二无法解释。模型说“这里有问题”但说不清具体违反了哪条规则用户改起来没有方向。第三成本高。每次校验都调用模型对于需要频繁检查的场景来说不划算。规则引擎的优势正好相反确定、可解释、零成本。每条规则都有明确的触发条件和修复建议用户看到告警就知道该怎么改。当然规则引擎的短板是覆盖不了语义层面的问题所以我的方案是规则引擎做主力模型做补充规则负责格式、风格、结构这类可量化的问题模型负责语气、逻辑、表达这类需要理解的问题。两者各司其职不互相替代。2.3 核心架构解析、规则、报告三段式整个项目的架构我拆成三段。第一段是解析层负责把输入内容转成结构化数据。如果是Markdown就解析成标题、段落、列表、代码块等节点如果是代码就解析成AST如果是纯文本就按段落和句子切分。解析层的质量直接决定后续校验的准确度所以这部分要优先做好。第二段是规则层每条规则是一个独立模块接收解析后的结构输出问题列表。规则之间互不依赖可以单独启用或禁用。这种设计让扩展变得很简单想加一条新规则只需要写一个新模块注册进去就行不用改动其他代码。第三段是报告层把问题列表按严重程度、文件位置、规则类型等维度聚合输出成人类可读的报告。报告支持多种格式终端彩色输出、Markdown表格、JSON结构化数据。终端输出适合开发时快速查看Markdown适合贴到文档里JSON适合接入CI流程做自动化判断。注意解析层和规则层之间要约定好数据结构否则每加一条规则都要改解析逻辑维护成本会失控。我的做法是定义一个通用的节点结构包含类型、位置、原始文本、子节点等字段所有规则都基于这个结构工作。3. 核心校验规则设计哪些细节最影响“无可挑剔”3.1 文本层面的规则清单文本类内容是最常见的校验对象我整理了以下几类规则按优先级从高到低排列。中英文混排空格是最容易踩的坑。中文和英文、中文和数字之间应该加一个空格比如“使用 Python 编写”而不是“使用Python编写”。这条规则看起来简单但实际检测时要处理很多边界情况标点符号前后不加空格、行首行尾不加空格、代码块内不处理、URL不处理。我一开始没考虑这些结果误报一大堆后来加了白名单和上下文判断才稳定下来。标点符号统一也很关键。中文内容里混用英文逗号和中文逗号或者句号一会儿用“。”一会儿用“.”读起来就很别扭。规则的做法是检测每个标点符号的上下文语言环境如果前后主要是中文就要求用中文标点如果主要是英文或代码就要求用英文标点。列表格式一致性经常被忽略。同一个列表里有的项以句号结尾有的没有有的项首字母大写有的小写。这些不一致单看无所谓但整体扫一眼就能感觉到“不整齐”。规则会检查同一列表内所有项的结尾标点和首字母大小写是否统一。标题层级连续性是Markdown文档的高频问题。从一级标题直接跳到三级标题或者同级标题编号不连续都会让文档结构显得混乱。规则会遍历所有标题节点检查层级是否逐级递增编号是否连续。规则名称严重程度触发条件修复建议中英文空格警告中文与英文/数字相邻且无空格在中间插入一个空格标点统一警告同一段落内中英文标点混用按上下文语言统一标点列表一致性建议同一列表内结尾标点或大小写不一致统一为同一种风格标题层级错误标题层级跳跃或编号不连续调整层级或补全编号链接有效性错误链接格式错误或指向不存在修正链接地址3.2 代码层面的规则清单代码类内容的校验重点和文本不同更关注命名、结构和可读性。命名风格统一是第一条。同一个项目里变量名有的用驼峰有的用下划线函数名有的动词开头有的名词开头读起来就很割裂。规则会统计所有标识符的命名模式找出偏离主流的那些。这里有个细节不能简单地规定“必须用驼峰”因为不同语言和团队的约定不一样。我的做法是先检测主流风格再标记偏离项这样更灵活。函数长度和复杂度是第二条。一个函数超过50行或者嵌套层级超过4层可读性就会明显下降。规则会计算每个函数的行数和圈复杂度超过阈值就给出建议。阈值可以配置因为不同场景容忍度不同工具函数可以短一些业务逻辑可以适当放宽。注释覆盖率是第三条。不是要求每行都写注释而是检查关键逻辑是否有说明。规则的做法是识别函数、类、复杂条件分支等节点检查其上方或内部是否有注释。没有注释的标记为建议注释过于简单的也标记出来。重复代码检测是第四条。连续多行完全相同的代码块或者结构高度相似的函数都值得提取成公共部分。规则用简单的滑动窗口算法检测连续重复行用结构指纹检测相似函数。这部分不需要做得太复杂能覆盖明显的重复就够了。3.3 文档结构层面的规则清单文档结构类内容介于文本和代码之间关注的是整体组织。章节完整性是第一条。一份技术文档通常需要包含概述、安装、使用、配置、常见问题等章节。规则会检查文档是否包含这些必要章节缺失的给出提示。当然不同文档类型要求不同所以章节模板要可配置。段落长度分布是第二条。全是长段落读起来累全是短段落又显得零碎。规则会统计段落长度分布如果超过80%的段落都在同一长度区间就建议调整节奏。这条规则比较主观所以只作为建议输出。术语一致性是第三条。同一个概念在文档里用了不同的叫法读者会困惑。规则会提取文档中的专业术语检测是否有同义词混用的情况。实现上可以用简单的词频统计加人工维护的同义词表不需要上模型。实操心得规则不是越多越好。我一开始写了三十多条规则结果每次校验输出几百条告警用户根本看不过来。后来砍到十几条核心规则把其余的降级为可选使用体验才正常。规则的价值在于精准不在于数量。4. 实操落地从零搭建一个可用的校验流程4.1 环境准备与依赖选择这个项目的技术栈选择比较自由我用的是Python因为文本处理和规则引擎的生态最成熟。核心依赖只有两个markdown-it-py用于解析Markdownpygments用于代码语法分析。如果要做代码AST解析按语言选对应的解析库比如Python用内置的ast模块JavaScript用esprima。为什么不选更重的框架因为校验工具的核心是规则逻辑框架越轻规则写起来越直接。我试过用现成的lint框架结果发现它们的规则接口和自己的需求不匹配改造成本比从头写还高。所以最终方案是自己写一个轻量规则引擎核心代码不到200行但完全可控。环境准备步骤很简单python -m venv venv source venv/bin/activate pip install markdown-it-py pygments如果你要做CI集成再加一个click用于命令行参数解析就够了。不需要数据库不需要Web框架保持最小依赖。4.2 解析层的实现要点解析层的核心是把不同格式的输入统一成节点树。以Markdown为例markdown-it-py会把文档解析成token流我需要把它转成自定义的节点树。每个节点包含以下字段class Node: def __init__(self, type, content, line, column, childrenNone): self.type type # 节点类型heading/paragraph/list/code等 self.content content # 原始文本 self.line line # 起始行号 self.column column # 起始列号 self.children children or []转换过程中有几个坑要注意。第一行号和列号要准确否则报告里定位不到具体位置用户改起来很麻烦。markdown-it-py的token自带map字段但只到行级别列号需要自己算。第二代码块内的内容不要解析否则代码里的Markdown语法会被误判。第三嵌套列表要保留层级关系否则列表一致性规则没法判断哪些项属于同一个列表。解析完成后我会对节点树做一次预处理合并相邻的文本节点、标记每个节点的语言环境中文为主还是英文为主、提取所有链接和图片地址。这些预处理结果会被后续规则复用避免每条规则都重新计算。4.3 规则引擎的核心逻辑规则引擎的设计目标是简单、可扩展、可配置。每条规则是一个类实现两个方法check(node)返回问题列表fix(node)返回修复后的内容可选。规则注册到一个全局列表引擎遍历节点树对每个节点调用所有启用的规则。class Rule: name base severity warning def check(self, node, context): raise NotImplementedError def fix(self, node, context): return None class Engine: def __init__(self, rules): self.rules rules def run(self, root, context): issues [] for node in walk(root): for rule in self.rules: if rule.enabled: issues.extend(rule.check(node, context)) return issues这里的关键设计是context对象它携带了全局信息文档语言、配置项、已解析的术语表等。规则通过context获取需要的信息而不是自己去重新计算。这样既提高了效率也保证了规则之间的一致性。配置方面我用一个YAML文件管理规则的启用状态和阈值rules: spacing: enabled: true severity: warning punctuation: enabled: true severity: warning heading_level: enabled: true severity: error function_length: enabled: true max_lines: 50 severity: suggestion这种配置方式的好处是非开发者也能调整。运营同学如果觉得某条规则太吵直接改YAML就行不用动代码。4.4 报告输出与CI集成报告输出我做了三种格式。终端格式用颜色区分严重程度红色是错误黄色是警告蓝色是建议。每条问题显示文件名、行号、规则名和修复建议。Markdown格式输出成表格方便贴到PR评论或文档里。JSON格式输出结构化数据方便CI脚本判断是否通过。CI集成的逻辑很简单如果存在错误级别的问题退出码为1阻断合并如果只有警告和建议退出码为0但输出报告供参考。这样既保证了硬性质量又不会因为风格问题卡住流程。# CI脚本示例 python -m impeccable check --format json --output report.json if [ $? -ne 0 ]; then echo 发现错误级别问题请修复后重新提交 exit 1 fi注意CI集成时要把规则配置也纳入版本管理否则不同分支的校验标准不一致会出现“本地通过、CI失败”的情况。我的做法是把配置文件放在项目根目录和代码一起提交。5. 常见问题与排查技巧实录5.1 误报太多怎么办这是最常见的问题。我一开始跑全量规则输出几百条告警其中大部分是误报。排查下来主要有三个原因。第一规则没有考虑上下文。比如中英文空格规则在代码块和URL里也触发了。解决办法是给规则加白名单代码块、行内代码、链接地址、HTML标签内的内容都跳过。第二阈值设置不合理。比如函数长度限制设成20行结果大部分函数都超标。解决办法是先跑一遍统计看看实际分布再把阈值设在合理位置。第三规则之间互相干扰。比如标点统一规则和列表一致性规则同时触发同一条问题被报了两次。解决办法是给问题加唯一标识报告层去重。我的经验是新规则上线前先在历史内容上跑一遍人工抽查误报率。误报率超过10%的规则要么改逻辑要么降级为建议。5.2 解析失败怎么定位解析层出问题的时候报错信息往往很模糊比如“解析到未知token”。这时候需要把原始输入和解析结果都打印出来对比。我的做法是在解析层加一个debug模式开启后输出每个token的类型和内容以及转换后的节点树结构。常见的解析失败原因有两个。一是输入格式不规范比如Markdown里混了HTML标签或者代码块没有正确闭合。二是编码问题文件不是UTF-8编码导致中文字符解析出错。前者需要在解析前做一次预处理把不规范的格式修正后者需要在读取文件时强制指定编码。5.3 规则冲突怎么处理规则冲突的典型场景是一条规则要求加空格另一条规则要求去空格。比如中英文空格规则要求“使用 Python”但某个术语表规则要求“使用Python”作为固定搭配。这种冲突不能靠规则本身解决需要在引擎层面加优先级机制。我的做法是给每条规则一个优先级数值冲突时高优先级的规则生效。同时在报告里标注“该问题与其他规则存在冲突”提醒用户手动确认。更彻底的做法是引入规则组概念同一组内的规则互斥只能启用一个。比如“空格风格”组里有“加空格”和“不加空格”两条规则用户选一个就行。5.4 性能优化经验内容量大的时候校验会变慢。我实测下来10万字的Markdown文档全量规则跑一遍要3秒左右。优化到1秒以内的关键有三点。第一避免重复解析。解析层的结果缓存起来规则层直接复用不要每条规则都重新遍历节点树。第二规则按需启用。不是所有规则都需要跑比如纯代码文件不需要文本类规则。第三用生成器代替列表。遍历节点树的时候用生成器避免一次性把所有节点加载到内存。问题类型排查思路解决方法误报太多检查规则上下文判断和阈值加白名单、调整阈值、降级严重程度解析失败打印token流和节点树对比预处理不规范格式、强制指定编码规则冲突检查规则优先级和互斥关系设置优先级、引入规则组性能瓶颈统计各阶段耗时缓存解析结果、按需启用规则、用生成器5.5 独家避坑技巧最后分享几个我在实际使用中总结的技巧。第一规则描述要写清楚“为什么”。用户看到告警时不仅要知道哪里错了还要知道为什么这是问题。比如中英文空格规则描述里要说明“加空格是为了提升可读性这是中文排版惯例”。第二提供一键修复功能。对于确定性高的规则比如空格和标点直接提供自动修复用户按一个键就全部改好。第三定期回顾规则效果。每个月统计一次各规则的触发次数和修复率触发次数高但修复率低的规则说明要么误报多要么用户不认可需要调整。这个项目后续还可以往几个方向扩展接入更多文件格式比如Word、PDF、支持自定义规则脚本、增加团队协作功能比如规则配置共享。但核心思路不变把“无可挑剔”拆成一条条可执行、可验证的规则让质量校验从主观感受变成客观流程。