游戏中的ABTest思考

ABTest在游戏前期会发挥非常重要的作用,它会帮助产品判断某个功能的AB走向哪种更加受用户喜欢。功能虽好,但ABTest所带来的问题是极大的影响业务逻辑的唯一性。我所参与的曾经一个项目初期大量使用这种手段来对功能进行决策,随着功能越来越多,其凸显的问题也越来越明显。主要体现在一下几点:

  1. 当业务变得繁重后,ABTest的分布维度变得复杂。不同的AB并不处于同一个水平维度中。这就简介导致某些隐藏BUG产生。
  2. 一些已经确定的功能,在代码中存在越来越多的冗余。甚至在一些相对底层的通用模块中存在AB情况,这就导致上层逻辑变得难以理解。
  3. 在不运行的情况下,代码审核变得困难,你不知道逻辑到底运行哪条分支。不同分支路径下,是否能到达同一终点。

实际上针对这些问题,我们并没有找到一个完美的解决方案,只能在某些程度上相对让开发和调试变得简单一些。做过一些总结,在实际过程中可减少一些干扰因素。

针对AB的状态,我定义了三种不同的优先级,分别为lock,local,global。lock指的是那些经过ABTest之后,已经明确要使用的路径。凡是lock所定义的AB路径会变成单一路径,不存在第二情况。 local指的是不同模块根据需求来动态切换AB两条路径的状态。可以立即为“进行中”的ABTest。这种类型的AB所有切换经过服务器判定给出结果,在登录之初下发到客户端中。最后一种global为用户AB,当某个功能在lock和local中都不存在定义时,则按照global等级进行AB路径选择。

三种AB优先级分别为 lock > local > global。

最佳实践是在游戏登录之初,就将所有的AB状态下发。当业务运行时向AB系统询问到底执行哪条路径。伪代码如下:

if(ABTest.isA("model_1"))
{
	//执行A路径逻辑
}
else
{
	//执行B路径逻辑
}

好了,回到我们的客户端环境中。在实际开发中,上面这种代码完全没问题。但当我们做调试时,就必须使用断点或者打印来判断执行路径。面对这种问题,我在UNITY进行了一步简单的封装,伪代码如下:

ABTest.Run("model_1", this.func_a, this.func_b);

使用这种接口调用后,系统会根据实际 model_1 的路径决断来决定到底执行哪个函数。这样写有何好处?事实上,在 Run 函数中,除了简单的判断加执行意外,还包含一个监控工具支持。系统执行时,会将当前的决定和执行的函数名称发送到监控工具中,在监控面板可以清晰的查看到执行日志,如下图:

除了以上方法,实际开发中我们还是要通过结构设计来避免业务逻辑的混乱。如果读者还有其他更好的方法或者经验欢迎EMAIL我来进行讨论。