Skip to main content

BLOGE 0.9.8-RC1 在线版 · 事实校验 2026-09-15 · English

附录 F —— 决策表

本附录是 decision_table 节点类型的权威速查手册。此处列出的每条语法规则、命中策略保证、输出形状和错误码均直接来源于 bloge 源码与合规测试装置。

当你需要查询精确语义时,请直接使用本附录,无需重读第 5 章。若想从概念引导出发,请从 第 5 章——做决策的分支开始。若需动手练习,请参见 第 34 章 Lab 6


语法速查

decision_table <id>(
<param1> = <expr1>,
<param2> = <expr2>,
...
) [hit=<first|unique|any|collect>] [-> <OutputType>] {
rule (<param>: <predicate>, ...) -> <output>
...
[otherwise -> <output>]
}
  • hit= 默认为 first
  • -> <OutputType> 可省略;省略时编译器从第一条规则的输出推断类型。
  • 每条 rule(...) 中声明要测试的参数。
  • otherwise 仅在所有显式 rule 都不匹配时才执行(或对 collect 策略而言,总是追加为一个条目——见§otherwise 语义)。

最小示例 —— hit=first + 标量输出:

graph creditScreening {
decision_table credit_tier(score = applicant.output.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
}

命中策略矩阵

策略多条规则命中无匹配且无 otherwise抛出异常
first取声明顺序的第一个匹配RUNTIME_DECISION_TABLE_NO_MATCHCODE_NO_MATCH
unique抛出 RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCHRUNTIME_DECISION_TABLE_NO_MATCHCODE_AMBIGUOUS_MATCH / CODE_NO_MATCH
any若所有匹配输出相等(Objects.equals)则允许;否则抛出 RUNTIME_DECISION_TABLE_CONFLICTING_MATCHRUNTIME_DECISION_TABLE_NO_MATCHCODE_CONFLICTING_MATCH / CODE_NO_MATCH
collect收集所有匹配到 output.items返回 { items: [] },不抛异常

first 示例 —— 第一条命中的规则获胜:

graph decisionBasicFirst {
decision_table credit_tier(score = applicant.output.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
}

unique 示例 —— 至多一条规则命中,否则报错:

graph decisionBasicUnique {
decision_table credit_band(score = applicant.output.score) hit=unique -> String {
rule (score: score >= 750) -> "prime"
rule (score: 650 <= score < 750) -> "standard"
otherwise -> "manual"
}
}

any 示例 —— 多条规则可以同时命中,前提是输出值相同:

graph decisionBasicAny {
decision_table approval(score = ctx.score) hit=any -> String {
rule (score: score >= 700) -> "approved"
rule (score: score >= 750) -> "approved"
otherwise -> "manual"
}
}

collect 示例 —— 所有命中规则都贡献到 output.items

graph decisionBasicCollect {
decision_table applicable_discounts(score = applicant.output.score) hit=collect -> String {
rule (score: score >= 700) -> "loyalty"
rule (score: score >= 760) -> "premium"
}
}

otherwise 语义

策略存在 otherwise不存在 otherwise
first无显式规则命中时触发无匹配 → RUNTIME_DECISION_TABLE_NO_MATCH
unique无显式规则命中时触发无匹配 → RUNTIME_DECISION_TABLE_NO_MATCH
any无显式规则命中时触发无匹配 → RUNTIME_DECISION_TABLE_NO_MATCH
collect无论显式规则是否命中,总是追加一个条目空匹配 → 返回 { items: [] },不抛异常

collect 的特殊之处:对于 firstuniqueanyotherwise 是"兜底"——只在其他规则都不匹配时才触发。对于 collect,它是一条无条件规则,总是把自己的输出追加到 items 中。这一差异由 decision-table/collect-otherwise lint 规则(INFO 严重度)标记,提示你确认这是否是有意为之。


输出形状参考

共三种形状。同一张表中的所有 rule(包括 otherwise必须使用相同的形状——混用形状会导致编译错误(COMPILER_DECISION_TABLE_OUTPUT_SHAPE_MISMATCH)。

形状声明语法下游访问方式
标量(scalar)-> "value" / -> 3.5<node>.output.value
命名字段(named fields)-> { rate: 3.5, maxTerm: 30 }<node>.output.rate<node>.output.maxTerm
收集条目(collect items)hit=collect + 任意输出<node>.output.itemsList 类型)

多输出(命名字段)示例:

graph decisionMultiOutput {
decision_table loan_terms(
score = applicant.output.score,
amount = loan.output.amount
) hit=unique -> { rate: Decimal, maxTerm: Int } {
rule (score: score >= 750, amount: amount <= 500000) -> { rate: 3.5, maxTerm: 30 }
otherwise -> { rate: 6.0, maxTerm: 5 }
}
}

in 运算符

in 在规则谓词中测试集合成员关系,共两种形式。

形式 A —— 字面量数组

rule (type: type in ["vip", "enterprise", "gov"]) -> "priority"
  • 右侧是字面量数组[lit1, lit2, ...]
  • 每个元素必须是 String、Number 或 Boolean 字面量——不允许表达式、变量或节点路径。
  • 违反此规则会触发编译错误:COMPILER_DECISION_TABLE_IN_LITERAL_ONLY
  • 空数组type in [])永远不匹配任何输入。

示例:

graph decisionTableInStatic {
decision_table customer_tier(type = ctx.type) hit=first -> String {
rule (type: type in ["vip", "enterprise", "gov"]) -> "priority"
otherwise -> "standard"
}
}

形式 B —— 动态参数引用

rule (type: type in allowed) -> "priority"
  • 右侧是单个已声明参数名,在运行时必须解析为 Collection 类型。
  • 不允许深路径如 ctx.allowedobj.lista[0]——请通过 decision_table(...) 头部的参数绑定把集合传入。
  • 若参数在运行时解析为非 Collection 类型(如 StringInteger),bloge 抛出带有 RUNTIME_DECISION_TABLE_INVALID_COLLECTION_PARAM 代码的 DecisionTableViolationException。异常消息包含 in 表达式的行列号和实际运行时类型。
  • 左值为 null 时直接短路返回 false(不抛异常)。

示例:

graph decisionTableInDynamic {
decision_table customer_tier(type = ctx.type, allowed = ctx.allowed) hit=first -> String {
rule (type: type in allowed) -> "priority"
otherwise -> "standard"
}
}

等价语义 —— 两种形式一致

形式 A 和形式 B 内部均使用数值等价比较(evaluateBinaryOp(EQ_EQ))。这意味着 Integer(700)BigDecimal(700.0) 被视为相等。你不会因为不同图节点产生的数值类型不同而遭遇无声的不匹配。


常见陷阱

1. 作用域越界 —— 在规则中引用非参数路径

// ❌ 编译错误:COMPILER_DECISION_TABLE_RULE_SCOPE_VIOLATION
rule (score: applicant.output.score >= 750) -> "prime"

// ✅ 先通过参数绑定传入值
decision_table credit_band(score = applicant.output.score) hit=unique -> String {
rule (score: score >= 750) -> "prime"
...
}

2. 输出形状不一致

// ❌ 编译错误:COMPILER_DECISION_TABLE_OUTPUT_SHAPE_MISMATCH
decision_table tier(score = ctx.score) hit=first {
rule (score: score >= 750) -> "prime" // 标量
otherwise -> { label: "manual", flag: true } // 命名字段——形状不一致!
}

3. in 右侧不是简单参数名

// ❌ 编译错误:COMPILER_DECISION_TABLE_IN_INVALID_RIGHT
rule (type: type in ctx.allowed) -> "priority" // 深路径不允许

// ✅ 在参数列表中绑定
decision_table tier(type = ctx.type, allowed = ctx.allowed) hit=first -> String {
rule (type: type in allowed) -> "priority"
...
}

4. in 右侧参数在运行时不是 Collection

若绑定给 in 右侧的参数在运行时解析为非 Collection 值(如 StringInteger),bloge 抛出:

RUNTIME_DECISION_TABLE_INVALID_COLLECTION_PARAM

异常消息会指出具体行列号和实际运行时类型。请修复产生错误类型的上游节点或绑定表达式。

5. collect + otherwise —— 总是追加,而不只是兜底

// [INFO] decision-table/collect-otherwise
// 这里的 otherwise 子句会在每次运行时都将 "baseline" 追加到 items,
// 即使其他规则也命中了。请确认这是否是有意为之。
decision_table discounts(score = ctx.score) hit=collect -> String {
rule (score: score >= 700) -> "loyalty"
otherwise -> "baseline" // 总是被追加
}

运行时错误码

所有运行时违规都以 DecisionTableViolationException 抛出。code 字段携带以下冻结的 wire 字符串之一:

Wire 字符串触发时机
RUNTIME_DECISION_TABLE_NO_MATCHfirstuniqueany——没有规则命中且未声明 otherwise
RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCHunique——超过一条规则命中
RUNTIME_DECISION_TABLE_CONFLICTING_MATCHany——多条规则命中但输出值不相等
RUNTIME_DECISION_TABLE_INVALID_COLLECTION_PARAMin <param>——参数未解析为 Collection

这些字符串是冻结契约。在应用程序错误处理器或测试断言中匹配时,请原文复制。


编译期错误码

bloge 编译器在编译期拒绝无效的决策表定义。主要错误标识符(用于诊断信息和 CI lint 输出):

标识符触发原因
COMPILER_DECISION_TABLE_IN_LITERAL_ONLYin [...] 数组包含非字面量元素
COMPILER_DECISION_TABLE_IN_INVALID_RIGHTin <expr> 右侧不是单个参数名
COMPILER_DECISION_TABLE_RULE_SCOPE_VIOLATION规则谓词引用了已声明参数之外的内容
COMPILER_DECISION_TABLE_OUTPUT_SHAPE_MISMATCH规则使用了不一致的输出形状(标量 vs. 命名字段)
COMPILER_DECISION_TABLE_OUTPUT_TYPE_INVALID命名输出类型注解中的字段不符合必需的 name: Type 形式
COMPILER_DECISION_TABLE_UNKNOWN_PARAMETER规则条件引用了表头中未声明的参数
COMPILER_DECISION_TABLE_DUPLICATE_CONDITION两条规则的条件完全相同
COMPILER_DECISION_TABLE_PARAM_RESERVED参数名与 DSL 关键字冲突
COMPILER_DECISION_TABLE_PARAM_DUPLICATE表头中存在重复参数名
COMPILER_DECISION_TABLE_PARAM_NODE_CONFLICT参数名与图节点名冲突

最小可运行示例

示例 1 —— hit=first + otherwise

graph ex1First {
decision_table credit_tier(score = applicant.output.score) hit=first -> String {
rule (score: score >= 750) -> "platinum"
rule (score: 680 <= score < 750) -> "gold"
otherwise -> "rejected"
}
}

预期:score=720credit_tier.output.value == "gold"

示例 2 —— hit=unique —— 歧义匹配错误路径

graph ex2Unique {
decision_table risk_band(score = ctx.score) hit=unique -> String {
rule (score: score >= 650) -> "eligible"
rule (score: score >= 700) -> "preferred"
// 无 otherwise —— 若 score >= 700 则两条规则都命中 → RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCH
}
}

预期:score=720 → 抛出 DecisionTableViolationException,code 为 RUNTIME_DECISION_TABLE_AMBIGUOUS_MATCH

示例 3 —— hit=collect + 命名字段输出

graph ex3Collect {
decision_table loan_badges(
score = applicant.output.score,
amount = loan.output.amount
) hit=collect -> { label: String, discount: Decimal } {
rule (score: score >= 700) -> { label: "loyalty", discount: 0.02 }
rule (score: score >= 760) -> { label: "premium", discount: 0.05 }
rule (amount: amount <= 100000) -> { label: "small-loan", discount: 0.01 }
}
}

预期:score=780, amount=80000loan_badges.output.items 包含三个条目:loyaltypremiumsmall-loan


另请参阅