这几年的开发中,最让人头疼的事情之一就是数据统计。这里就来看看以单向数据流的角度如何改进统计系统的设计。
我非常的反对使用面向切面埋点来处理用户行为,理由有三个:
以前在使用切面埋点的时候,就遇到很多的问题,虽然说每个数据点都不可能漏埋或者错埋,但是每次上线后数据分析都需要跑过来让开发给他们看看这些行为的埋点数据是怎么样的。这样也很难实现一个多期的版本对比。
那么按照标准的用户行为统计又有哪些问题呢?
目前我们的埋点方案主要有3点:
以上都没有一个很好的方案能够解决数据回归测试的问题。而回归测试也只能靠人工执行。
统计即是数据,那么当然也非常符合数据流模型,那么我们就用数据流模型来简化埋点方案,增加每个模块的独立性和复用性,同时也把埋点放到一个地方去做,减少埋点数据在整个应用内的散乱分布。
以上就是这套方案的大概结构。用户触发行为时,和之前直接统计行为不同,而是创建一个Action对象,将统计所需要的参数,或者自身包含数据包装在Action内,发送给Store。Store作为一个数据中心,负责接收和分发数据,他将收到的数据分发给订阅者Subscriber,最后由Subscriber完成统计数据,并上报服务器。
Store、Action是完全可复用的,同时这两者并不关联实际业务,所以完全可以模块化,同时只要行为足够完整,也不需要关系具体业务方统计数据的样式。这样就可以让其他模块完全的复用了。
那么如何提升复用性,我们来关联下之前讨论过的MVP。
这里,红色框内的部分都是逻辑性的,是完全可复用的;View也是独立与逻辑的,也是可复用的;只有Subscriber和Controller是和业务强相关的,是不可复用的。那么我们就可以知道需要把哪些东西放到不可复用的地方,哪些东西放到可以复用的地方了。
同时我们也需要考虑下测试的问题,来解决埋点数据的完整性和正确性。
只要我们mock了Store部分,就可以轻易的检查发生的Action,或者向订阅者发送对应的Action,这样就可以比较简单的去回归测试数据了。只不过这样做的收益可能并不高。
这里我们来看看实现的方式。
首先定义基础的Store和Action
class StatAction {
var type: String?
var params: [String: Any]?
}
protocol StatSubscriber {
func newStatAction(action: StatAction)
}
class StatStore {
func dispatch(_ action: StatAction) {}
func subscribe(_ subscriber: StatSubscriber) {}
func subscribe(_ subscriber: (StatAction)->Void) {}
}
那么在ViewController里就可以这样配置。
func viewDidLoad() {
super.viewDidLoad()
self.store = StatStore()
self.store?.subscribe({ action in
// ... switch case action.type.
// Track
});
self.submodule.store = self.store
}
而子模块中只需要使用store来分发行为就可以了。
let action = StatStore(type: "star", params: ["id": "1234"])
self.store?.dispatch(action)
这里订阅者甚至可以自己创建独立的类来处理这些情况,这样就更加的分离了行为统计这种不能划分为任何模块的内容了。
这个方案将行为统计从整个app中剥离出一个单独的模块,同时实现了高度可复用性,而且使得统计也成为可以单元测试的了。唯一的缺点是在具体统计的时候需要大量switch...case...来区分不同的行为。
本文来自网易实践者社区,经作者段家顺授权发布。