【译】用React和PlanOut来做产品的AB测试

React

攻城师,设计师和产品经理每天为了达到一些KPI做一些盲目的决策。我们想去重新设计一个页面因为我们相信这样可以提升用户体验和提高用户的留存。我们想要去增加一个品牌的新特性或者扩展一个已有的特性因为我们相信通过使产品对用户更有价值可以提高产品的可用性。

每一个决策最终归结于这个问题:这个变更真的会带来预期的效果吗?这又可以归结为因果性推断:决策X能导致结果Y的发生吗?在Web应用中,我们可以使用金本位制(以黄金为本位币的货币制度)用来回答因果性推断的问题:随机化实验英文解释)。

在大型的公司里面,市场,销售和售前售后支持都能影响的用户的行为,这样把实验当作一个工具去做产品级的决策尤为重要。这套实验方法,从某种程度上来讲,可以帮你把其他各式各样的因素给排除掉。最终表明,这套实验方法在Sidekick小组(这篇文章出自于Hubspot Sidekick小组)中处于产品的核心地位。我们在不断迭代的使用这种方式这样我们的用户能容易的理解而且从Sidekick获取价值。这套实验方法帮助我们实现快速变革而且对于一个功能的生命周期的各个阶段都有更多的自信。它还使我们更理解我们的用户,而且能把这些理解应用于该产品的其他部分。最重要的是,它帮助我们细致的打造着产品的未来。

找寻一套实验框架和解决方案

要开启这套实验进程,在需要通过公司实现它的规模和确保测试的可统计性的同时,还需要有工具让它容易的去安装,部署和分析实验结果。在我们看来,这套实验框架应该具有如下的特性:

  1. 针对一个特定的实验,用户总是收到相同的参数值。除了带来糟糕的用户体验,不同的会话指定不同的参数值也会使之从实验中获得有价值的东西变得非常困难。
  2. 关心该实验框架的全流程实验日志记录,这样实验的分析过程将是自然的,可预测的而且无Bug产生(这里的无Bug是指根据log很容易的查出Bug)。全流程日志记录用一种简单的记录方法用来记录参与实验的用户和用户在该实验中受到的对待。
  3. 实验规模取决于你实施了的实验数量和基于用户的产品增长。
  4. 实验的设计应该抛开复杂的设计,应该是可执行性强的而且能容易的被公司里的每个人理解。
  5. 这套框架要确保一些实验不会拖拉的时间太长导致代码库做无用的增长。

根据上述这些限制,我们开始使用Facebook的PlanOut实验框架(用Python编写)。我们发现PlanOut有着不可思意的作用,但是当我们慢慢的把基础框架从服务端实现转换成客户端渲染时,我们发现把实验信息,参数还有实验日志从服务端传输到客户端时,变得笨重,慢还导致产生了一些可避免的Bug。

为了适应框架的变化,我们决定放弃使用Python的部署,而使用基于JavaScript的部署。结果,我们做了一套基于JavaScript的PlanOut部署框架[Github],该框架能让我们在客户端定义实验和管理实验。这套部署框架相对于服务端的部署有很多的好处。第一,自从我们无需把实验参数从服务端传送到客户端后基本没有性能瓶颈。自从我们深刻的认识到性能上的下降能影响到我们的一些重要指标后这一点显得非常重要。

第二,基于同一套代码定义和实施用户界面的实验变得更容易而且很少出现Bug,我们现在运行的大部分实验都是这样。自从PlanOut部署更适应我们之后把实验集成在标准的单页面APP里面变得非常容易。每一个实验为了实施合适的随机化实验都需要一套输入的集合,但是如果在服务端部署PlanOut,在实验初始化时就需要把参数定义好。我们的部署允许输入在单页面程序引导时注册而非初始化时,这样可以允许我们的一些实验类的初始化和实验输入的注册进行分离。这样可以很容易的使各种扩展的服务相互作用,而且能最小化日志的重复。

React Experiments

用PlanOut去测试传播特性

一个案例是我们在SideKick上尝试去影响传播邀请发送的质量和数量。作为Sidekick的免费用户,如果他发送注册邀请给他的朋友,而且他的朋友接受了邀请,他们都会获得一个月的无限制的通知服务。一个使用户发送邀请的巨大驱动力是该组件(邀请发送组件)能让用户一键邀请。我们的这个实验是通过改变该组件的相关参数能否让用户发送更多的邀请。在该实验中我们不想让付费用户参与,所以我们把付费用户排除在外(也不会对他们进行日志记录)。我们对免费用户进行了2x3的实验(两个测试点,一个测试点有2种属性,另一个测试点有3种属性),这两个点是建议邀请发送的数目和显示给邀请方账户的建议话述。我们把建议邀请发送的数据做为测试点是为了去测试真正发送邀请的数据是否会按照建议的那样去增长还是是否存在一个点这个数字的建议对实际的发送量根本无影响或者还有负面的影响。另外一个测试点是在邀请按钮上显示两种不同的文字,上面显示一种是自私的动机(邀请)和无私的动机(礼物)。代码如下:

//InviteSuggestionsExp.js hosted with ❤ by GitHub

class InviteSuggestions extends Experiment {
  //do some logger configuration
    
  setup() {
    this.setName(‘InviteSuggestions’);
  }

  assign(params, args) {
    if (!args.paying_user) {
      return false;
    }
    params.set(‘suggestionCount’, new PlanOut.Ops.Random.UniformChoice(choices=[3, 4, 5], unit=user_id));
    params.set(‘inviteText’, new PlanOut.Ops.Random.UniformChoice(choices=[‘Invite’, ‘Gift’], unit=user_id));
  }
}; 

执行一项测试需要定义实验和进行适当的部署,我们会需要一种方式自然的从实验定义到实验的部署。在HubSpot大部分的客户端Javascript应用我们使用的React框架实现其View层,自从我们开始实施一些实验,我们自然的找到了一种方式在React框架上无逢的实施实验。结果是产生了一个小型的库 [react-experiments]

React实验系统介绍

这个库的核心是它建立一个单对单的映射,从PlanOut的实验参数映射到React组件的Props(React里从父节点传递到子节点的数据称为 props,是属性(properties)的缩写),而且为了一个特定的用户用随机化实验的分配当作合适的组件的props。这把PlanOut的参数当作变量优雅的集成了进来。这个结果让定义和部署间的连接变更更易理解,使得不至于有难以捉摸的Bug让实验失效,而且使实验不断的实施而不是不断的积累无用的东西。让我们来看看实施上文提到的实验有多容易用React。

这是实施实验之前的代码:

//invite-suggestions.jsx hosted with ❤ by GitHub
const InviteSuggestions = React.createClass({
  getDefaultProps() {
    return { 
      numSuggestionsToShow: 0,
      inviteButtonText: null
    };
  },
    
  renderInviteSuggestions() {
    const suggestions = this.props.suggestions.slice(0, this.props.numSuggestionsToShow);
    return suggestions.map((suggestion) => {
      return (
        <InviteSuggestion
      inviteButtonText={this.props.inviteButtonText}
          suggestion={suggestion} 
        />
      );
     });
  },

  render() {
    if (!this.props.numSuggestionsToShow) { return null; }
    return (
      <div>
    {this.renderInviteSuggestions()}
      </div>
    );
  }
});

export default InviteSuggestions;

现在,去实施该实验需要做的是用下面的代码替换上面代码的最后一行

//invite-suggestions-exp.jsx hosted with ❤ by GitHub
export default parametrize(InviteSuggestionsExperiment, ['suggestionCount', 'inviteText'], InviteSuggestions);

该库的基础是一个参数化的组件,它使其余的库的能力更有力。比如,一个高序的参数化组件当所有的props在同一个组件当中时是一个非常方便的封装。当你想在一个组件中实施一个实验而又发现感兴趣的props又在子组件当中时,你可以直接使用参数化组件的基类,联合子组件相关的高序附着实验参数组件一起使用去提供心要的props的参数化。下面是一个利用参数化组件随同高序附着实验参数组件的实例。

附着实验参数组件和高序参数化组件都为实验提供了强大的抽象性。在某些情况下,我们想去做完整的重新设计在不同的设计之间去设置不同区间的参数。对于这些实验,react实验系统提供了一套AB测试组件,提供一套方法在你的组件当中去定义不同的参数。这些开放的API能显而易见的让读者通过代码发现可以通过实验发现若干变量和这些变量导致的行为之间的关系。这个组件,类似于高序的组件,同时又是一个基于参数化组件的非常方便的封装。

带着实验性倾向的产品设计和研发对于任何产品团队都是一种资产,而且能让产品团队把关注点放在正确的事情上还能快速的进行迭代研发。我们发现用PlanOut.js和react实验系统实施实验有很多的优点,我们希望你能用我们提供的开源项目做和我们一样的事情。[在GitHu上的项目]

译者述

React是一套Facebook研发的前端开源框架,只负责View层
PlanOut是Facebook研发的一套AB测试框架,也是开源项目[GitHub PlanOut]
本人非前端攻城师,所以可能有些地方翻译的有些问题欢迎指出,非常感谢。AB测试这个概念很早就已提出,但是是Facebook把他用得炉火纯青,现在个人有点崇拜Facebook,今天还看了覃超的文章,觉得Facebook已经把数据化驱动做得非常好,是大部分公司都应该学习的榜样。

[查看原文]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容