🙋🏻♀️ 编者按:本文作者是蚂蚁集团前端工程师单凉,介绍了云凤蝶的类 EXCEL 的公式体系,云凤蝶是蚂蚁集团内部的表单、流程、页面搭建平台,为蚂蚁员工提供无代码/低代码的标准应用搭建服务。
公式是什么?
提到 EXCEL 表中的公式,大家应该都有所了解。比如这个最简单的 SUM 函数,用来求取某个区域内的数值之和:
EXCEL SUM 函数
EXCEL 用户可以使用公式对数据进行汇总、排序、过滤、甚至还有一些公式函数可以用来调整表格的布局。
总的来说:EXCEL 公式为静态的表格注入了动态产出数据的能力。
云凤蝶应用中的公式
先来看看云凤蝶应用是什么:
云凤蝶是蚂蚁集团内部的表单、流程、页面搭建平台,为蚂蚁员工提供无代码/低代码的标准应用搭建服务。不需要技术支持、不需要写代码,业务人员可以独自完成表单搭建和投放,以及收集到数据后的报表和数据分析。
云凤蝶应用的表单搭建能力
那么,如何实现这样一个功能:对于如下的权限申请表单,当申请人输入「申请人工号」时,「申请人姓名」自动展示为对应人员名称?
表单搭建
在解决这个问题之前,我们先来看一下上面的权限申请表单:
用户每次提交数据,都会在该表单下新增一条表单实例,这条实例由四个不同字段组成(申请人工号、名称、部门、申请原因)。
对应 EXCEl 的示例
因此,每个表单都可以简单理解为一张如上图所示的 EXCEL 表,而类比于 EXCEL 中公式的作用,云凤蝶应用下也产出了自己的公式形态:
在云凤蝶应用下,只需要将「申请人名称」的值配置器切换为「公式绑定」,然后配置对应的公式:
切换为公式绑定
「申请人名称」公式配置
简而言之,云凤蝶应用提供了低技术门槛的数据收集相关能力,而公式体系为其中的表单搭建带来了动态业务规则的书写能力。
核心诉求:低上手门槛
基于上面的讨论可以看到,打造公式体系,而不是使用现有编程语言的原因,便是用户群体的差异,因此公式体系的设计核心,必须是简单好用,即便没有代码基础的用户也能快速上手使用。
产品形态
公式编辑
EXCEL 公式的核心,便是将表格中的数据经过计算与转化,输出结果到另一部分单元格中。类比在表单场景下,也无非是让用户使用一些处理函数,针对自己的业务场景,对于表单项的值进行业务处理逻辑的书写:
- 内置的处理函数:帮助用户处理文本、数字、复杂逻辑、信息获取等各种场景;
- 表单项即变量:能够看到当前表单中的表单项,并可以将表单项的值作为公式参数使用;
- 低门槛的逻辑书写器:拥有自己的编辑器,提供高亮、提示、校验等能力,帮助用户书写公式。
公式编辑器
公式执行
而该场景下,用户书写的业务逻辑,也一定是一种自动化的计算关系:当我所使用的变量发生变化时,我的公式执行结果总是对的。
即,一旦依赖项变化,公式应该重新执行:
申请人工号变化时,名称需要联动
主要能力
云凤蝶应用在公式能力上,集众家之长:
❌:不支持 ✅:支持 ✅ 💯:优秀
左右滑动查看
如何实现一套公式体系?
先看看一套公式体系都包含哪些内容:
规范定义
语法规范
由于公式是为了没有代码基础的用户设计的,所以语法也应该是简单易懂的:
- 操作符少:以云凤蝶公式为例,仅支持二元操作符(
A + B
)与方法调用(SUM(1, 2, 3)
)两种基础语法格式; - 数据类型少:摒弃复杂的对象概念,用户仅需理解基本的文本、时间、数字、集合操作就能书写逻辑;
- 内置常用逻辑:将循环、分支、数据请求等编程领域的复杂逻辑抽象为公式函数;内置机制抹掉同步异步等概念。
同步异步 & 计算 & 逻辑
变量规范
变量规范是公式体系与业务间的桥梁,基本的变量定义主要包括:
- 变量名称:业务变量名称,用于书写数据处理规则;
- 变量 Schema:声明变量对应的数据类型与结构,编辑器用来进行参数类型等校验;
- 编译表达式:定义变量在编译之后会被替换成怎样的代码。
编辑器中的物料及变量列表
物料规范
物料是公式体系最核心的扩展能力,复杂的平台逻辑可以简化为一个函数,交给用户使用
USERINFO 函数简化了用户信息的获取
一个基本的公式物料包含以下部分:
- 基础定义:函数名称、描述、文档、示例等,交由编辑器展示;
- 类型定义:参数及返回值定义,由校验器进行解析并校验,编辑器提示;
- 物料实现:具体的内部逻辑代码,交由执行器执行;
能力实现
编译器
不管公式语法如何特殊,最终一定是在我们的代码中执行的。
由于公式语法的特殊性,不能直接在任何一门语言中执行,因此需要一个编译的过程,将用户书写的公式转换成我们执行时的语言(云凤蝶的场景下为 JS 代码)。
// 用户公式:如果变量A 填写的是单凉,就展示单凉的工号;否则展示 000000 IF(变量A = "单凉", USERINFO("单凉", "work_id"), "000000") // 编译产物: IF( doCalc(vars.A1234, "===", "单凉"), await USERINFO("单凉", "work_id"), "0000" )
思路:使用 AST 树来表达用户所书写的公式,并根据目标环境的区别(如:编辑时回显公式、运行时执行代码)选取对应的编译规则,将这棵 AST 树中的特殊语法节点做转化。最后再转化成对应的代码文本。
这是一个针对于上面例子的简易转化图例(高亮部分为需要转化的特殊语法):
了解了实现原理后,问题就变简单了,接下来只需要解决两个问题:如何在 AST 树与代码之间互转,以及如何修建 AST 树。
云凤蝶选用的是 Antlr:一个可以高度自定义的开源语法解析器,适用于自定义语言的开发。(https://www.antlr.org/)
校验器
校验器本质上是根据业务中定义的语法规则,遍历 AST 树,验证每个节点与定义是否一致的过程。如下面的公式:
在之前的物料及变量定义的过程中,分别定义好了:
- USERINFO 及 TRUE 函数对应的类型定义:
declare function USERINFO(workId: string, infoType?: string): string; declare function TRUE(): boolean;
- 「申请人」变量对应的变量 Schema:
{ type: "string" }
在 AST 树遍历中进行对比这些信息,便可得到校验结果:
执行器
解决了公式编译的问题,用户书写的公式便可以编译成可以真实运行的运行时代码,此时,执行器可以简单的理解为:在运行该代码的执行环境中,注入所依赖的变量和函数的值,从而得到运行结果
不过大多数业务场景下,我们还需要解决一个问题:如何在依赖的变量变化时,自动执行公式?
// 变量A 从 1 变到 10 1 => 10 // 配置公式:f(x) = A + 5 6 => ???
这里可以对照前端框架的响应式原理来进行实现:
将公式中用到的变量作为表达式的依赖,借助 Proxy 监听这些变量的修改,当变量修改后,重新执行当前公式。
业务接入
走到这里,我们便已经探索完了公式体系的设计与核心实现。
接下来,当业务方系统中有这样非开发人员书写动态规则的诉求时,如何帮他们接入公式体系呢?
例如,后端同学可能会用到数据加工表达式:(https://help.aliyun.com/document_detail/129147.html)
- 定义好业务侧公式函数:如上面的
e_search
、e_set
、e_drop_fileds
等; - 构建变量列表,其中:
- 变量定义 + 公式编辑器,搭建自己的编辑链路,如上面的
DROP
; - 变量消费表达式 + 公式执行器,完成执行链路,如
DROP
对应的实际调用。
- 自定义生成逻辑:生成运行环境下容易理解的运行时代码,该场景下为 JAVA 代码;
- 自定义语法(可选):完成更高级别的公式语法定制。
通过上面几步定义,便可以仅消耗周级别的开发时间,接入业务规则书写能力。