稍微有点标题党, 不过很短, 可以看
背景
最近一直在参加开发公司新运营系统的引擎部分, 写了很多很多代码, 也逐渐产生了一些自己对此类业务的解决方案的简单想法运营系统干的事, 其实就是支持运营人员配置活动所谓活动其实可以简单定义为: 一系列条件都满足就执行某个 (些) 动作对应到程序员的黑话就是: 一坨 if else 之后执行某 (几) 个函数
if else
说到写 if else, 大多数程序员可能都会会心一笑, 毕竟不论写啥系统, 代码里大部分语句还是 if else 业务系统就是对 PM 的需求堆 if else, 基础服务就是对 OS 资源和网络问题堆 if else 越来越多的需求造成了代码的膨胀, 如何来管理这坨 if else, 就衍生出了设计模式: 通过各种手段来把 if else 划分到更小的粒度当然, 还没有任何一套方法论可以解决 if else 复杂性的问题, 因为 if else 反映的其实是真实业务的需求, 它其实代表了业务的复杂性就像鲁迅或者马云或者巴菲特说的: 不论用什么方法, if else 不会凭空消失, 只会从一个地方转移到另一个地方但是, 在面对特定的场景, 特定的业务, 代码的阅读性可维护性以及扩展性是可以得到提升的
规则引擎
规则引擎不是一个新东西, 而且已经存在了很长时间了, 市面上也有各种规则引擎比如大家常用的 iptables, 就是一种规则引擎, crontab 也是一种规则引擎我们平时写的业务逻辑, 其实也是规则引擎, 只是用一种不那么明显的方式来体现比如: 如果重置的密码没有包含大小写和标点符号, 就不让提交, 如果用户是抽奖送的 VIP 虽然不播广告但给他弹窗这些业务逻辑, 无论怎么优化, 要处理的代码分支是一定存在的规则引擎通过自定义的一套语法(DSL), 提供了编程语言所不具备的语法糖, 来最大限度减少开发量同时大部分规则都需要支持用户随意配置, 因此 DSL 大多是解释执行用户在后台配置一条规则比如, 游戏中用户在线 1000 分钟后扣 20 的点卡:
- When
- {
- ?customer: Customer(totalTime >=1000);
- }
- Then
- {
- execute {?customer.setAmount(getAmount()-20.00);
- }
市面上比较常用的几款规则引擎都属于非常重量级的, 用户必须去学习它的 DSL 才能使用, 当然基本上都是 JAVA 的包这有一篇文章是几款规则引擎的比较没有深度用过, 我也没法对此进行评价, 但由于其概念实在太多且使用场景受限, 我觉得它始终是个临时方案, 毕竟大道至简而且 DSL 会越写越复杂, 真正用到语法糖的地方还是少数, 到最后变成另一种蹩脚的而且领域受限的编程语言了虽然如此, 但由于其规则可动态增加, 老规则便于复用, 对于提供通用解决方案的一些软件公司来说, 规则引擎还是很有吸引力的毕竟代码不需要动, 加一些规则就能再次销售
但是对于进行业务开发的 RD 来说, 其实需求很简单, 如果一个东西:
正确
简单
能减少繁琐的工作
那么这便是一个有吸引力的解决方案, 如果它能再快点儿, 那就是一个绝妙的主意了对于减少 if else, 我觉得 Linq 是一个非常好的方案
Linq
Linq 是 C# 中的一种常见技术, 用过的都觉得爽它就是一种语法糖, 编译器会编译成真正的代码而不是在运行期解释执行, 因此效率也很高 Linq 代码大概就是这样:
- // Specify the data source.
- int[] scores = new int[] { 97, 92, 81, 60 };
- // Define the query expression.
- IEnumerable<int> scoreQuery =
- from score in scores
- where score > 80
- select score;
可以看到, 在代码中一些逻辑如果能这样表达, 那代码将会变得非常简单 linq 基本上就是把 sql 语句搬到了 C# 中, 唯一的区别就是它把 select 放在了最后而 sql 是在前面这其实也是因为 IDE 智能提示的需求, 而不是查询结构本身要这么设计把 select 放在最后, IDE 就知道你前面用过哪些变量, 就可以分析出你可以 select 什么从而进行提示但是 Linq 是 C# 中的技术, 和编译器是强相关的, 想移植到别的语言, 还是很不容易的当然, 社区也是有一些尝试的, 比如 go 的移植版 linq-go 然而, 你懂的, 由于 go 本身不支持泛型, 编译器也不支持 linq 语法, 因此使用起来当然还是比较蹩脚, 而且肯定是 interface{}和类型推断满天飞了
sql
其实我们常用的 sql 中的 where 部分是完备的, Linq 也是把 sql 搬到了 C# 中换句话说, sql 的 where 部分其实可以组合任意的布尔逻辑所有 RD 都会使 sql, 而且是经常使 sql, 既然想从编译器的方向搞事情太麻烦(尤其是 go 是一门以简单著称的语言, 官方对于加关键字是深恶痛绝的, 更别说语法糖了), 那么把 mysql 查询功能干掉, 解释执行 where 比较的那部分功能做成一个函数提供出来呢, 比如查询一个人是不是顶级程序员可以这么查:
- sql := `sex='male'
- and (
- dislike in ('girl', 'woman', 'female')
- or
- hobby in ('dress up', 'makeup')
- )`
但是由于没有表数据, 那么真正用于比较的数据就需要业务方自己来提供了, 以上功能可以写为:
- func isTopProgrammer(userInfo User) bool {
- sql := `sex='male'
- and (
- dislike in ('girl', 'woman', 'female')
- or
- hobby in ('dress up', 'makeup')
- )`
- ok,_ := yql.Match(sql, map[string]interface{}{
- "sex": userInfo.Sex,
- "dislike": userInfo.Dislike,
- "hobby": userInfo.Hobbies,
- })
- return ok
- }
yql 其实就是我最近搞的一个 lib(求 star, 求 pr, 求指导), 帮你执行 sql 的比较
如果这么搞, 其实业务逻辑中经常变动的部分可以抽象到配置文件里, 比如:
- // 先定义好不同 case 的处理函数
- type handleFunc func(map[string] interface {}) error
- var handlers = map[int] handleFunc {
- 1 : sendEmail,
- 2 : sendCoupon,
- 3 : sendSms,
- 4 : sendAlert2Boss,
- 5 : runAway,
- }
- // 从当前请求中解析数据
- data: =resolveDataFromPostParams(request.Body)
- // 从配置文件加载规则
- rules: =loadRuleFromConf()
- // 枚举每条规则进行匹配, 如果匹配成功则执行对应 handler
- for _,
- rule: =range rules {
- success,
- err: =yql.Match(rule.SQL, data) if nil != err || !success {
- continue
- }
- handler: =handlers[rule.ID] handler(data) break
- }
当然这种方式也有利有弊, 比如说, 把规则分离到配置文件中, 在 debug 时得几个文件之间跳来跳去地看代码, 比较蛋疼 (少写点 bug 就行了, 哈哈) 优点之一比如说可以利用推送平台实时更新配置文件, 从而达到代码热更新的目的
最后
其实 yql 还有很多应用场景(我感觉), 当然也有一些不足, 比如功能还比较单一, 解释执行, 快不快其实也没和谁对比过不过希望大家能够尝试尝试, 如果能够提升工作效率, 还是不错的求关注, 求 star
来源: https://juejin.im/post/5a780e195188257a624ca675