从一个小型记账规则语言出发,把目的、语法、语义、类型、错误、运行时和工具链逐步设计出来
| 问题 | 如果不用新语言会怎样 | 语言设计里的含义 |
|---|---|---|
| 财务人员想配置“餐饮超过 200 元需审批” | 每次都要找程序员改 Java 代码 | 需要一种业务人员可读、工程师可验证的规则表达 |
| 规则越来越多,散落在代码和配置里 | 没人知道哪条规则生效,改一处可能影响很多场景 | 语言需要统一语法、版本和解释规则 |
| 错误规则可能导致错报销、错分类 | 上线后才发现,靠人工追账 | 语言必须能在运行前做校验,而不是只靠运行时报错 |
| 同一规则要被后端、报表、审计系统理解 | 每个系统各写一套逻辑,结果不一致 | 语言需要清晰语义和可共享的执行结果 |
| 设计选择 | 为什么这样选 | 暂时不做什么 |
|---|---|---|
规则以 rule name 开头 | 每条规则要可命名、可追踪、可禁用 | 先不做复杂模块系统 |
when 写条件,then 写动作 | 业务人员能看懂触发条件和后果 | 先不支持任意函数和循环 |
| 字段只允许访问账目对象 | 防止规则随意读写外部系统 | 先不开放数据库查询 |
| 动作只有少数白名单 | 避免规则语言变成不可控脚本 | 先不允许执行任意代码 |
| 语法问题 | 设计判断 | LedgerRule 的选择 |
|---|---|---|
| 关键字要不要像自然语言 | 越自然越易读,但歧义也更容易出现 | 使用 rule / when / then,保持短而固定 |
| 表达式要不要支持括号 | 括号能消除歧义,但会增加学习成本 | 支持括号,并明确 and 优先级高于 or |
| 字符串、数字、日期怎么写 | 字面量格式决定解析和错误提示复杂度 | 字符串用双引号,金额用数字,日期先不纳入第一版 |
| 缩进是否有意义 | 有意义的缩进更整洁,但复制粘贴容易出错 | 第一版不让缩进承担语义,只用换行分隔 |
| 语义问题 | 必须说清什么 | 如果不说清会怎样 |
|---|---|---|
| 多条规则同时命中 | 是全部执行,还是按优先级只执行一条 | 同一账目可能被重复标记或重复审批 |
| 动作执行顺序 | 先打标签还是先审批,是否影响后续规则 | 不同系统执行出不同结果 |
| 条件里的空值 | 字段不存在时是 false、报错,还是 unknown | 规则在缺字段数据上表现不稳定 |
| 规则失败 | 一条规则失败是否中断全部规则 | 审计和补偿无法判断哪些规则生效过 |
| 错误写法 | 类型系统应该提示什么 | 背后的设计点 |
|---|---|---|
amount contains "200" | amount 是 Money / Number,不能使用字符串包含操作 | 每个字段要有类型,操作符也要有适用类型 |
category > 100 | category 是 String,不能和数字比较大小 | 比较操作不能只按符号解析,还要看类型 |
require_approval(300) | 审批人参数必须是角色名字符串 | 动作也需要签名和参数类型 |
tag() | tag 需要一个标签参数 | 函数调用要检查参数数量 |
| 差的错误提示 | 好的错误提示 | 为什么重要 |
|---|---|---|
| Parse error at line 2 | 第 2 行 amount > 后面缺少数字,例如 amount > 200 | 用户能立即修正 |
| Invalid operator | contains 只能用于字符串字段,amount 是数字 | 提示要解释规则,不只指出错误 |
| Unknown field | 未找到字段 merchent,你是否想写 merchant | 拼写建议能显著降低挫败感 |
| Runtime failed | 规则 meal_limit 执行动作 require_approval 失败:角色 manager 不存在 | 规则名、动作名、原因都要可追踪 |
| 执行方式 | 适合什么 | 代价 | LedgerRule 选择 |
|---|---|---|---|
| 解释执行 | 规则经常改,立即生效 | 性能上限较低,运行时检查多 | 第一版采用解释执行 |
| 编译成 Java / 字节码 | 规则稳定,性能要求高 | 实现复杂,调试和错误映射更难 | 第二版再考虑 |
| 编译成 SQL | 大量数据批量筛选 | 动作语义难映射,数据库差异大 | 只允许条件部分下推 |
| 远程规则服务 | 多个系统共享规则 | 网络延迟、版本和可用性治理 | 生产化后再做 |
| 通用问题 | 在 LedgerRule 里的对应物 | 扩展到通用语言时会变成什么 |
|---|---|---|
| 这门语言服务谁 | 财务规则配置者和后端工程师 | 语言的用户画像、学习曲线和生态定位 |
| 程序是什么结构 | 规则列表,每条规则有条件和动作 | 模块、函数、类、包、命名空间 |
| 值有哪些类型 | 数字、字符串、布尔、金额、动作参数 | 类型系统、泛型、空值、所有权、代数数据类型 |
| 代码如何执行 | 解释器逐条匹配规则 | 解释器、编译器、VM、JIT、AOT、运行时 |
| 错误如何暴露 | 解析错误、类型错误、动作执行错误 | 编译期错误、运行时异常、诊断系统 |
| 语言如何演进 | 规则语法版本、动作白名单版本 | 标准库、兼容性、版本语义、弃用策略 |
| 误区 | 为什么会走偏 | 更好的做法 |
|---|---|---|
| 先设计漂亮语法 | 语法好看但不知道解决谁的问题 | 先写 5 个真实程序样例,再抽语法 |
| 第一版就做成通用语言 | 特性爆炸,语义和工具链很快失控 | 先做小领域语言,把边界收窄 |
| 只写 parser,不定义语义 | 能读懂文本,但不知道执行是什么意思 | 每个语法结构都写清执行规则 |
| 忽视错误提示 | 用户实际学习语言时,看到最多的是错误 | 错误提示要有位置、原因、修正建议和规则名 |
| 语言能执行任意代码 | 灵活性变成安全、权限和审计风险 | 从白名单动作开始,逐步扩展能力 |
| 练习 | 要输出什么 | 验收问题 |
|---|---|---|
| 写 5 条 LedgerRule 样例 | 餐饮、交通、住宿、超额、缺发票五类规则 | 业务人员能不能读懂,工程师能不能执行 |
| 定义字段类型表 | amount、category、merchant、invoice、date 等字段及类型 | 每个操作符能不能知道自己适用哪些字段 |
| 写一页语义说明 | 规则顺序、缺失字段、多规则命中、动作失败处理 | 两个实现者是否会写出相同执行结果 |
| 设计 5 条错误提示 | 语法、类型、字段拼写、动作参数、角色不存在 | 用户看提示后能不能自己修正 |
| 决定执行模型 | 解释执行、编译执行、SQL 下推或规则服务 | 选择是否匹配规则变化频率、性能和治理成本 |