The hard ways

Parsing in practice

现在工作中的项目是一个数据展示类的项目,主要负责将后端分析过的数据,通过 charts 显示到页面中。项目是前后端分离的,它构架是:

前端页面 -> nodejs 中间层 -> java 服务

比如现在页面中需要显示三个指标:访客数(UV),支付订单数(PAY_ORDER_CNT),退货率

对于前两个指标,要得到它们的数值非常简单,就是把括号中的字母内容作为 key 提交给接口服务,接口服务就会返回 key 所对应的数值,前端得到数据直接显示即可。我们可以暂且把这种可以直接由 key 取到内容的指标成为 "简单指标",并且由于接口不支持多个 key 同时查询,所以每个简单指标必须独立为一个接口请求。

对于退货率这个指标,我们可以称之为 "复合指标",它是由几个简单指标计算得来的,比如这个退货率,它在后端提供的文档中描述的表达式是:

RETURN_ORDER_CNT / (RETURN_ORDER_CNT + SUB_PAY_ORDER_CNT)

这个表达式中涉及的两个简单指标是:退货订单数(RETURN_ORDER_CNT) 和 正向支付订单数(SUB_PAY_ORDER_CNT)。然而,在实际操作中,会有各种简单指标进行运算组合的复合指标,而且由于复合指标的需求是会不断更改和增加的,所以如果只是针对每个复合指标不断写子处理程序的话,那么工作量以及维护成本将会很高。对于复合指标的计算,本应该放在服务端运行,由接口直接提供计算结果,但是由于它们机械繁琐的工作量,后端没人愿意做。

毕竟到了前端就没人可以说理去了,总不能让用户自己来算复合指标吧。这时我们的编译技术排上了用场。

首先复合指标都是一些简单的数学表达式,把这些复合指标的计算表达式形容成 DSL 也不过份吧。

要计算复合指标,只需要分两步:

  1. 分别请求计算复合指标所需的每个简单指标的值
  2. 对表达式进行求值

好在这个 DSL 非常的简单,只要处理表达式的解,而且只有双目运算,就省去了结合性的考虑,只要处理运算符优先级就可以了。

因为只是处理运算符的优先级,利用 Shunting Yard Algorithm 就好了。将表达式由 中缀(infix) 转换成 后缀(postfix) 形式,保存在一个栈中,

RETURN_ORDER_CNT/(RETURN_ORDER_CNT+SUB_PAY_ORDER_CNT)

就被处理成了

// 栈底 -> 栈顶
RETURN_ORDER_CNT RETURN_ORDER_CNT SUB_PAY_ORDER_CNT + /

然后逐个弹出栈中的内容,如果是运算符,就分别弹出两个操作数,如果操作数又是运算符,那么就递归调用,如果操作数是标识符,那么就带入之前的简单指标请求的结果,最后根据不同的运算符来对两个操作数进行运算。

这是一个在线的预览 复合指标计算,点击了 run 之后,打开 console 看下是否有没有通过的 assert 就可以了。

    Made with gadget