5.1 领域特定语言


5.1 领域特定语言

本节摘要:领域特定语言(DSL)不是某门具体语言,而是一种策略:为特定问题域定制一套专门的词汇与语法,让领域专家能用接近母语的方式表达逻辑。本节辨析内部与外部 DSL 两条实现路线,用装饰器与宏各完成一版规则引擎 DSL,并给出"建与不建"的判断标准。

语言的边界之外

通用编程语言为普适性设计,代价是表达特定领域时的绕远:一条"工作日九点后拒绝大额转账"的规则,写出来是条件、函数、配置的混合体,领域专家(风控人员)读不懂,更无法自己修改。DSL 的动机就一句话:把表达权交还给领域专家,把执行权留给宿主语言

实现路线有两条。外部 DSL发明独立语法,需要自己的词法与解析器(SQL、正则表达式是鼻祖);内部 DSL寄生在宿主语言里,借用宿主的语法规则表达领域词汇——没有解析器要写,但要迁就宿主的语法约束。本节聚焦内部路线,它正是元编程机制的直接应用。

一、装饰器版:规则即声明

背景:营销系统需要可配置的资格规则——"满减活动只对注册满一个月、近三月有订单的用户生效"。规则由运营定义、随活动增删,写死在代码里不可接受。

操作:用 4.1 的装饰器机制把规则做成声明式注册:

RULES = {} def rule(name): def decorator(func): RULES[name] = func # 定义即注册 return func return decorator @rule("注册满一个月") def registered_over_month(user): return user.age_days >= 30 @rule("近三月有订单") def recent_order(user): return user.orders_in(90) > 0 # 规则组合也做成领域词汇 ALL = lambda *names: lambda u: all(RULES[n](u) for n in names) campaign_check = ALL("注册满一个月", "近三月有订单")

结果:运营视角看到的是一张规则名词表,技术视角是一组可测试的普通函数,组合逻辑一行。解读:这套 DSL 的"语法"几乎全靠 Python 原生机制——装饰器负责登记、关键字参数负责配置、高阶函数负责组合。内部 DSL 的本质是把宿主语言的语法当免费解析器用,这是它相对外部路线最大的成本优势。

二、宏版:当宿主语法不够用

装饰器版的局限在表达式:想让运营写出"注册满一个月 并且(近三月有订单 或者 客单价超过阈值)"这样的嵌套逻辑,高阶函数的嵌套括号很快变得反人类。Lisp 风格的宏版可以直接造出贴合领域的语法:

(defrule premium-user (and (registered-over (months 1)) (or (has-orders (last 90 :days)) (avg-order-over 500)))) ;; 宏展开为普通的条件判断代码,与手写同等效率

操作→结果:规则文本与业务口语几乎一一对应,展开产物是编译器眼中的普通表达式。解读:宏版多出来的表达力是有价格的——语法是你定的,文档、错误提示、编辑器支持也全靠你建。表达式越靠近自然语言,离工具链的支撑就越远,这笔账在决定"造多深的语法"时必须当面算清。

图:两种 DSL 路线的能力光谱

图:两种 DSL 路线的能力光谱

三、建与不建:三条判断题

DSL 的失败率不低,动工前先过三道判断。第一题:词汇稳定吗? 领域概念还在频繁发明和废弃的阶段,DSL 的词汇表会跟着反复推倒——先等词汇沉淀。第二题:使用者是谁? 若最终表达者就是工程师,通用语言加好函数命名通常已够;DSL 的价值随"非工程师表达者"的存在而成立。第三题:寿命够长吗? DSL 本身是个需要文档、测试、培训的资产,用三个月就散的临时需求,养不起它。

三题里有任何一题答反,更务实的替代是"领域词汇表 + 普通代码":把领域术语做成函数与类型名,语法完全借用宿主。表达力的损失有限,维护成本几乎为零。反过来,SQL、Kubernetes 资源描述这类存活了几十年的 DSL 都证明:判断题全过时,DSL 是复利惊人的长期投资。

建成之后:DSL 的健康度体检

判断题全过、DSL 落地,只是起点。它上线后仍需要定期体检,四个指标足够:报错质量——运营写错一条规则,报错能不能指出错在哪条、错在哪个词?指着栈深处的解析器报错,说明错误通道还没做领域化。表达闭环——最常见的规则改动,运营能否不经工程师完成?抽样统计"仍需工程师代写的规则比例",超过两成就该扩词汇表。方言蔓延——近半年新增的语法构造,是否都能映射回最初的几个原语?原语数量持续增长就是方言化的前兆。文档活性——词汇表文档的更新频率是否与 DSL 演化同步?文档滞后一步,新规则就会写歪一步。

体检不通过的处理原则与 6.5 的降级模式一致:先补错误通道与文档(零行为变更),再考虑收缩词汇。DSL 是个需要持续喂养的资产——这一点在建判断题的"寿命"一题里就该有心理预期。

还有一个从教训里来的提醒:给 DSL 留逃生门。无论词汇表设计得多周全,总会遇到表达不了的边角需求。成熟的 DSL 都预留一条通往宿主语言的后门——自定义函数注册、逃生钩子、或"高级模式"直接写代码。没有逃生门的 DSL 会逼着使用者扭曲需求去迁就语法,或者干脆绕开 DSL 另起炉灶,两者都是资产流失。逃生门的配套纪律也要跟上:走后门的代码要标注理由,定期回看哪些后门用法该沉淀为正式词汇——DSL 的词汇表就是这样一轮一轮从逃生门里长出来的。

本节要点回顾

  • DSL 动机:把表达权交还领域专家,执行权留给宿主语言。
  • 两条路线:外部 DSL 自建语法与解析,内部 DSL 借宿主语法——元编程机制是内部路线的地基。
  • 装饰器版:声明式登记加高阶函数组合,是成本最低的内部 DSL 形态。
  • 宏版:能造贴合口语的嵌套语法,代价是自建文档、提示与错误体系。
  • 三道判断题:词汇稳定性、使用者身份、资产寿命——答反任何一题就退回"词汇表加普通代码"。

DSL 把逻辑写得更像人话。下一节转向另一个维度:让类型系统替人把逻辑查对。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U