JexLang:一次编写,随处验证 — 跨平台表达式语言

发布: (2025年12月14日 GMT+8 02:51)
8 min read
原文: Dev.to

Source: Dev.to

🎯 我们共同面临的问题

想象一下:你正在构建一个多平台应用——React Web 应用、Android 移动端、Spring Boot 后端,甚至可能还有 iOS 应用。一切看似可控,直到你碰到熟悉的墙——校验逻辑

你的产品经理走进来说:

“我们需要校验用户年龄在 18 岁以上,邮箱必须是公司域名,如果他们选择 Premium 计划,则必须拥有有效的信用卡。”

听起来很简单,对吧?现在把它在各平台上实现:

// JavaScript (Web/React Native/NodeJS)
if (
  user.age >= 18 &&
  user.email.endsWith('@company.com') &&
  (user.plan !== 'Premium' || user.hasValidCard)
) {
  // valid
}
// Java (Backend/Android)
if (
  user.getAge() >= 18 &&
  user.getEmail().endsWith("@company.com") &&
  (!user.getPlan().equals("Premium") || user.hasValidCard())
) {
  // valid
}
// Swift (iOS)
if user.age >= 18 &&
   user.email.hasSuffix("@company.com") &&
   (user.plan != "Premium" || user.hasValidCard) {
  // valid
}

现在想象你的应用里有 50+ 条校验规则。每个平台都有自己的实现,需要分别测试,而且每条规则都可能隐藏细微的 bug。

欢迎来到校验地狱。 🔥

😫 真正的痛点

1. 代码重复的噩梦

每条校验规则都会出现在多个地方。业务规则变更时,你需要在 3‑4 个不同的代码库里修改代码。漏掉一个?就会出现不一致的行为,折磨你的调试过程。

2. 微妙的平台差异

不同语言对同一件事的处理方式略有不同:

  • 字符串比较:=== vs .equals() vs ==
  • 空值处理:undefined vs null vs Optional<>
  • 类型强制转换:JavaScript 那怪异的 "5" + 3 = "53" vs Java 的严格类型

这些差异会导致只在特定平台上出现的 bug——最棘手的那类 bug。

3. 硬编码逻辑 = 部署痛苦

修改一条校验规则意味着一次代码改动、PR 审核、测试、预发布以及部署——每个平台都要单独进行。如果校验逻辑可以放在数据库或配置里会怎样?如果非开发人员也能在不触碰代码的情况下修改简单规则会怎样?

4. 测试开销

50 条校验规则 × 4 个平台 = 至少 200 条测试用例。而且你还要确保它们的行为完全一致。祝你好运。

💡 如果有更好的办法会怎样?

如果你可以用一种简单、熟悉的语法一次性编写校验逻辑:

user.age >= 18 && endsWith(user.email, "@company.com") && (user.plan != "Premium" || user.hasValidCard)

并且它能够在以下环境完全相同地执行:

  • ✅ JavaScript/TypeScript(浏览器 & Node.js)
  • ✅ Java(Android & 后端)
  • 🚧 Swift(iOS)— 敬请期待!

没有平台特有的怪癖。没有代码重复。没有部署负担。

这正是我创建 JexLang 的原因。

🚀 介绍 JexLang

JexLang 是一种轻量级、平台无关的表达式语言,采用 类 JavaScript 语法。它极其擅长解决一个问题:一次编写,处处运行,行为一致

为什么采用类 JavaScript 语法?

大多数开发者都熟悉 JavaScript 语法。即使你主要是 Java 或 Swift 开发者,也一定在某个时刻接触过 JavaScript。使用熟悉的语法,JexLang 的学习成本几乎为

如果你能写出:

user.age >= 18 && user.active === true

那么你已经会 JexLang 了。

🛠️ JexLang 工作原理

JexLang 由三个核心组件组成:

1. 词法分析器(Tokenizer)

将表达式拆分为 token:

"user.age >= 18" → [IDENTIFIER:user, DOT, IDENTIFIER:age, GTE, NUMBER:18]

2. 解析器(Parser)

把 token 转换为抽象语法树(AST):

{
  "type": "BinaryExpression",
  "left": {
    "type": "MemberExpression",
    "object": "user",
    "property": "age"
  },
  "operator": ">=",
  "right": {
    "type": "NumericLiteral",
    "value": 18
  }
}

3. 解释器(Interpreter)

在给定的上下文数据上评估 AST 并返回结果。

神奇之处在于:相同的 AST 结构和求值逻辑在每种语言中都实现得完全一致——相同的解析规则、相同的运算符优先级、相同的类型强制转换、相同的结果。

📖 实际使用场景

1. 动态表单校验

将校验规则存放在后端,所有平台共享:

{
  "fields": [
    {
      "name": "email",
      "validations": [
        { "rule": "required(email)", "message": "Email is required" },
        { "rule": "contains(email, '@')", "message": "Please enter a valid email" }
      ]
    },
    {
      "name": "age",
      "validations": [
        { "rule": "age >= 18", "message": "You must be 18 or older" }
      ]
    }
  ]
}

你的 React 应用、Android 应用以及后端都可以抓取这些规则并使用 JexLang 进行评估。唯一的真相来源。

2. 业务规则引擎

无需代码部署即可配置业务逻辑:

// Rules stored in database
const discountRules = [
  { condition: "customer.tier === 'Gold' && cart.total > 100", discount: 20 },
  { condition: "customer.tier === 'Silver' && cart.total > 200", discount: 10 },
  { condition: "cart.items.length > 5", discount: 5 }
];

// Evaluate at runtime
const applicableRule = discountRules.find(rule =>
  jexlang.evaluate(rule.condition, { customer, cart })
);

营销团队想把 Gold 级别的阈值从 100 改成 150?只需更新数据库——无需部署。

3. 条件化 UI 渲染

根据可配置规则显示/隐藏 UI 元素:

{
  "component": "PremiumBanner",
  "showWhen": "user.subscription === 'free' && user.daysActive > 30"
}

4. 工作流自动化

构建条件可由用户自行配置的工作流引擎:

IF order.total > 1000 AND customer.verified === true
  → Route to priority fulfillment

IF order.items.length > 10
  → Apply bulk processing

IF contains(order.shippingAddress.country, 'EU')
  → Apply VAT calculation

5. 复杂逻辑的功能标记

超越简单布尔标记:

const featureRules = {
  newCheckout: "user.region === 'US' && user.accountAge > 30 && !user.hasActiveIssues",
  betaFeatures: "user.role === 'developer' || contains(user.email, '@internal.com')"
};

⚡ 快速示例

基础表达式

// Arithmetic
jexlang.evaluate("10 + 5 * 2"); // 20

// Comparisons
jexlang.evaluate("100 >= 50"); // true

// Logical operators
jexlang.evaluate("true && false || true"); // true

// String operations
jexlang.evaluate("'Hello' + ' ' + 'World'"); // "Hello World"

使用上下文

const context = {
  user: {
    name: "John Doe",
    age: 28,
    email: "john@company.com",
    roles: ["admin", "editor"]
  },
  settings: {
    maxUploadSize: 100,
    allowedFormats: ["jpg", "png", "pdf"]
  }
};

// Access nested properties
jexlang.evaluate("user.name", context); // "John Doe"
jexlang.evaluate("settings.maxUploadSize > 50", context); // true
Back to Blog

相关文章

阅读更多 »