做为一名自信的 QA,对于测试经过的项目,若是有人反馈有问题,脑海中的第一反应必定就是:不可能!必定是操做有问题。入职以来经手大大小小的项目也有 40 多个,一直没出过问题,也让我在年度的总结上自信地写到:全部项目按时按质发版,未出现线上问题。npm
可是,这种自信让我掉以轻心,使得微信小程序 SDK 的第一个线上问题也随之而来了。小程序
对于线上问题,可能不少人都觉得把问题解决了就完事了,并不重视对问题的复盘。事实上,复盘的做用可能远大于解决问题自己。微信小程序
在神策的企业文化中重要的一项就是复盘,每个问题对于咱们来讲都是一笔宝贵的财富。经过对于问题的复盘,总结经验教训,可以更好地促进咱们成长。微信
下面咱们来看下对于这个问题是如何进行复盘的。并发
神策微信小程序 SDK 的目标是实现对于主流小程序开发框架的全埋点功能。可是,在测试过程当中发现因为 Taro3.0 框架从新定义了标签点击行为的逻辑,使得一次点击行为会触发 SDK 的两次点击事件 $MPClick,形成了埋点数据重复。app
所以,此次发布的 v1.14.3 版本旨在解决 Taro3.0 框架下点击事件重复触发的问题,实现神策微信小程序 SDK 真正意义上的无框架障碍全埋点采集。框架
整个问题的生命周期从 2020 年 12 月 17 日 19 : 05 发版,到 2020 年 12 月 19 日 15 : 00 全部客户通知完成,总共历时 44 个小时。能够分为如图 4-1 中的 6 个阶段:函数
图 4-1 线上问题生命周期测试
此次问题是我经历的第一个线上问题,此次经历不只让我完整了解到线上问题的处理流程,更让我充分地感觉到各个环节上团队人员的密切配合。从问题开始到问题解决,小程序团队全员时刻处于待命状态,每一个环节都争分夺秒,确保了这次问题的快速修复,没有形成较大的影响。this
回顾线上问题的整个过程以后,咱们须要分析产生这个问题的具体缘由。
在进行具体的缘由分析以前,咱们先来看下神策微信小程序 SDK 自动采集点击事件的原理。
一、在重写 Page 函数时,先经过 _.getMethods 获取除 Page 钩子之外的自定义事件处理函数集合 methods:
`Page =` `function` `(option) {` `//` `先判断 mpClick 是否配置自动采集,若配置为真则获取自定义方法并代理重写` `var methods = sa.para.autoTrack && sa.para.autoTrack.mpClick && _.getMethods(option);` `if``(!!methods) {` `for``(var i = 0, len = methods.length; i < len; i++) {` `//` `对 methods 集合的每个自定义事件处理函数进行重写` `click_proxy(option, methods[i]);` `}` `}` `}` `//` `_.getMethods 方法,获取用户自定义的全部事件` `_.getMethods =` `function``(option) {` `var methods = [];` `for` `(var m` `in` `option) {` `if` `(typeof(option[m])===``'function'` `&& !mpHook[m]) {` `methods.push(m);` `}` `}` `return` `methods;` `}`
二、对 methods 集合的每个自定义事件处理函数进行重写,获取事件触发时的 type 类型,type 为 tap、longpress 或者 longtap 则触发 $MPClick 事件,将 wxml 文件标签中 dataset 定义的属性做为事件属性:
`//` `点击事件代理处理函数` `function` `click_proxy(option, method) {` `var oldFunc = option[method];` `option[method] =` `function``() {` `//` `在重写 oldFunc 以前就已经判断是一个方法类型,此处是作一次重复的校验` `var res = oldFunc.apply(this, arguments);` `var prop = {},` `type` `=` `''``;` `if``(_.isObject(arguments[0])) {` `//` `将 wxml 标签中 dataset 定义的属性做为事件属性` `var dataset = current_target.dataset || {};` `type` `= arguments[0][``'type'``];` `prop[``'$element_id'``] = current_target.``id``;` `prop[``'$element_type'``] = dataset[``'type'``];` `prop[``'$element_content'``] = dataset[``'content'``];` `prop[``'$element_name'``] = dataset[``'name'``];` `}` `if``(``type` `&& _.isClick(``type``)) {` `prop[``'$url_path'``] = _.getCurrentPath();` `sa.track(``'$MPClick'``, prop);` `}` `return` `res;` `}` `};` `//` `点击类型判断方法` `_.isClick =` `function``(``type``) {` `var mpTaps = {` `"tap"``: 1,` `"longpress"``: 1,` `"longtap"``: 1,` `};` `return` `!!mpTaps[``type``];` `}`
点击事件的自动采集不只能采集到用户的点击行为,还能自动采集点击标签的相关属性。
只要在 wxml 文件的标签中经过 data- 定义的属性均可以采集到,能够自动采集的属性如表 4-1 所示:
表 4-1 自动采集的属性
建议在元素中定义 id 、data-content、data-name 这三个元素之一做为元素标识,若无这三个属性,则在神策分析平台没法进行标识。
接下来,咱们来看一个自动采集点击事件的例子。
一、配置以下的 button 标签:
`<button bindtap=``"test"` `data-name=``"button"` `id``=``"button"` `data-content=``'button'` `data-``type``=``"button"``>测试<``/button``>`
二、点击 button 后触发的事件内容以下所示:
`{` `"distinct_id"``:``"1610349175397-726909-0e567a51188708-20891891"` `"lib"``:{` `"$lib"``:``"MiniProgram"` `"$lib_method"``:``"code"` `"$lib_version"``:``"1.14.4"` `}` `"properties"``:{` `"$lib"``:``"MiniProgram"` `"$lib_version"``:``"1.14.4"` `"$network_type"``:``"wifi"` `"$manufacturer"``:``"devtools"` `"$model"``:``"iPhone 6/7/8 Plus"` `"$screen_width"``:414` `"$screen_height"``:736` `"$os"``:``"devtools"` `"$os_version"``:``"10.0.1"` `"$timezone_offset"``:-480` `"$app_id"``:``"wx82a49f7cb5547449"` `"$url_path"``:``"pages/index/index"` `"$element_id"``:``"button"` `"$element_type"``:``"button"` `"$element_content"``:``"button"` `"$element_name"``:``"button"` `"$is_first_day"``:``false` `"$ip"``:``"117.71.111.48"` `"$browser"``:``"WeChat"` `"$browser_version"``:``"7.0.4"` `"$is_login_id"``:``false` `"$city"``:``"合肥"` `"$province"``:``"安徽"` `"$country"``:``"中国"` `}` `"anonymous_id"``:``"1610349175397-726909-0e567a51188708-20891891"` `"type"``:``"track"` `"event"``:``"$MPClick"` `"time"``:1615194119222` `"is_login_id"``:``false` `"map_id"``:``"1610349175397-726909-0e567a51188708-20891891"` `"user_id"``:-8183290914376425000` `"recv_time"``:1615194119222` `"project"``:``"gongcheng"` `}`
了解了微信小程序 SDK 是如何实现自动采集点击事件的原理,这次问题的缘由就比较容易分析了,下面咱们看下致使这次问题的具体缘由是什么。
一、首先咱们须要了解下小程序的页面逻辑,每一个页面都有一个单独的 JS 文件为页面组件添加执行逻辑,全部方法都写在 Page( { } ) 中,主要包含三个部分:页面的初始数据,小程序自己带有的生命周期函数和自定义的函数方法。例以下面示例中定义的两个方法 testA 和 testB:
`Page({` `/**` `* 页面的初始数据` `*/` `data: {` `},` `/**` `* 生命周期函数--监听页面加载` `*/` `onLoad:` `function` `(options) {` `},` `/**` `* 自定义方法 testA` `*/` `testA:` `function` `() {` `console.log(``'执行方法 B'``,this.B())` `},` `/**` `* 自定义方法 testB` `*/` `testB:` `function` `() {` `return` `'执行方法 B'` `}` `})`
二、根据上一节提到的点击事件自动采集原理,咱们对客户小程序的全部自定义方法进行了重写代理,判断 type 类型为点击时触发 $MPClick 事件,但前提必定是不能影响客户自定义方法的执行;
三、小程序 SDK v1.14.3 版本在更新现有逻辑时,修改了代理方法的返回值,由返回客户方法的执行结果改为了直接返回 false,如图 4-2 所示:
图 4-2 小程序 SDK v1.14.3 版本代码 diff 图
四、这就使得上面代码中 Page 自定义的方法 testB(),本来客户业务逻辑是 “return '执行方法 B'”,可是通过咱们 SDK 的方法重写,变成了 “return false”。
五、testA() 原本应该打印出 testB() 中定义的返回值,不过因为 SDK 代理使得 testB() 返回 false,致使 testA() 的执行结果不符合预期,如图 4-3 所示:
图 4-3 testA() 的错误执行结果
正确的业务逻辑执行结果应该如图 4-4 所示:
图 4-4 testA() 的正确执行结果
知道了问题的缘由以后,解决问题就比较容易了。只须要在代理客户方法时修改返回值为客户原来方法的返回值,如图 4-5 所示:
虽然这次线上问题的缘由比较简单,可是通过深入的检讨以后,我总结了以下几点经验教训:
经过这次线上问题暴露了本身做为一名 QA 所存在的一些问题:
通过这次线上问题的复盘,有以下行动做为改进的方向:
本文经过对于一次线上问题的复盘,介绍了复盘的总体流程,但愿经过本文能给你们提供一些复盘相关的参考。