审核规则为什么做成 DSL,而不是写死在代码里
今年做的第二个系统是工程图纸审核:图纸(扫描件、PDF、DWG)→ 模型抽取尺寸、公差、粗糙度、材料等要素 → 按规则审核 → 出问题清单。这篇不讲识别,讲规则。
规则是活的
上线前一周,客户给的规则文档改了四版。上线后每个月都有新规则。规则来自国标、行标、企业内部标准,还有”我们厂一直这么要求”的口头规则。
一开始规则写在代码里,一条规则一个方法。改一条就要发版,而且规则越多,方法之间的相互影响越难看清。三周后我停下来重做。
做成 DSL
规则表达成一段小语言,大概长这样:
when element.type == "linear_dimension"
and element.tolerance is missing
and element.value > 100
then issue("大于100的线性尺寸缺少公差", level="major")
字段(element.type、element.tolerance)和函数(is missing、issue)是有限集合。解析器很小,执行器也很小,难的不在这。
单一事实来源
难的是:字段和函数的清单在几个地方要用,怎么保证一致。
- 校验器要知道哪些字段合法,用户写错了要报错。
- 执行器要知道字段怎么取值。
- 后来加了”让模型从规范文档里抽规则”,模型的提示词里也要列出可用字段和函数。
三个地方各维护一份,早晚会漂。我的做法是用反射从要素模型和函数注册表里生成这份清单,校验器、执行器、提示词都从同一个生成结果取。加一个字段,三处自动同步。
要素模型(代码) ──反射──► 字段清单 ──┬──► DSL 校验器
函数注册表(代码) ──反射──► 函数清单 ──┼──► DSL 执行器
└──► 模型抽取提示词
这是整个规则模块里最值的一个决定。之后半年没出过”提示词里有、执行器里没有”这种事。
规则和要素的关系
还有一层:审核哪些要素是客户在管理页勾的。勾了”公差”才审公差相关规则,没勾就不审。审核引擎启动时按启用的要素动态装配规则集,禁用的要素对应的规则真的不参与,而不是跑了之后再过滤掉。这样客户关掉一个要素,速度也跟着快。
判据放哪
有些规则依赖国标里的表格,比如一般公差按尺寸段查表。这类判据不放规则里,放领域层的常量表,规则里只写”查表”。表改了规则不用改,规则改了表不用动。
代价
DSL 的代价是多了一层学习成本——客户的工程师要学这套写法。实际情况是他们不学,他们把规范文档丢过来,让模型抽。这就引出了下一篇要写的问题:模型抽出来的规则能不能直接上线。