這幾年的開發(fā)中告私,最讓人頭疼的事情之一就是數(shù)據(jù)統(tǒng)計不恭。這里就來看看以單向數(shù)據(jù)流的角度如何改進(jìn)統(tǒng)計系統(tǒng)的設(shè)計缠诅。
面向切面埋點
我非常的反對使用面向切面埋點來處理用戶行為宿礁,理由有三個:
- 統(tǒng)計數(shù)據(jù)極度依賴視圖結(jié)構(gòu),或者需要將每個數(shù)據(jù)綁定到視圖上茬射。
- 不能完成復(fù)雜的交互統(tǒng)計鹦蠕,僅能實現(xiàn)簡單的事件數(shù)據(jù)。
- 視覺上的修改會影響統(tǒng)計結(jié)果在抛。
以前在使用切面埋點的時候钟病,就遇到很多的問題,雖然說每個數(shù)據(jù)點都不可能漏埋或者錯埋,但是每次上線后數(shù)據(jù)分析都需要跑過來讓開發(fā)給他們看看這些行為的埋點數(shù)據(jù)是怎么樣的肠阱。這樣也很難實現(xiàn)一個多期的版本對比票唆。
用戶行為統(tǒng)計
那么按照標(biāo)準(zhǔn)的用戶行為統(tǒng)計又有哪些問題呢?
- 每個數(shù)據(jù)深入業(yè)務(wù)底層屹徘。需要統(tǒng)計要么把事件層層代理到Controller走趋,要么在底層這些看似不合理的地方埋點。
- 復(fù)用問題噪伊。業(yè)務(wù)雖然一樣吆视,但是埋點信息并不能完全保持一致,而且有些場景下也無法保持一致酥宴,因為可能會有重復(fù)場景。
- 埋點數(shù)據(jù)回歸測試您觉。由于是人工埋點拙寡,所以可能會漏埋錯埋的情況發(fā)生。
目前
目前我們的埋點方案主要有3點:
- 數(shù)據(jù)盡量保持統(tǒng)一琳水。相同的業(yè)務(wù)埋相同的點肆糕,然后根據(jù)頁面區(qū)分。這樣就能夠?qū)崿F(xiàn)重用在孝,缺點是有少部分需要特殊化的場景诚啃。
- 代理到業(yè)務(wù)層,然后再埋私沮。缺點是如果中間層次過多始赎,會出現(xiàn)多級代理,而僅僅是為了埋點仔燕。
- 子類化造垛。專門子類化該頁面的專有子類。缺點是子類的目的就是為了區(qū)分埋點晰搀,有點多余五辽。
以上都沒有一個很好的方案能夠解決數(shù)據(jù)回歸測試的問題。而回歸測試也只能靠人工執(zhí)行外恕。
單向數(shù)據(jù)流方案
統(tǒng)計即是數(shù)據(jù)杆逗,那么當(dāng)然也非常符合數(shù)據(jù)流模型,那么我們就用數(shù)據(jù)流模型來簡化埋點方案鳞疲,增加每個模塊的獨立性和復(fù)用性罪郊,同時也把埋點放到一個地方去做,減少埋點數(shù)據(jù)在整個應(yīng)用內(nèi)的散亂分布建丧。
以上就是這套方案的大概結(jié)構(gòu)排龄。用戶觸發(fā)行為時,和之前直接統(tǒng)計行為不同,而是創(chuàng)建一個Action對象橄维,將統(tǒng)計所需要的參數(shù)尺铣,或者自身包含數(shù)據(jù)包裝在Action內(nèi),發(fā)送給Store争舞。Store作為一個數(shù)據(jù)中心凛忿,負(fù)責(zé)接收和分發(fā)數(shù)據(jù),他將收到的數(shù)據(jù)分發(fā)給訂閱者Subscriber竞川,最后由Subscriber完成統(tǒng)計數(shù)據(jù)店溢,并上報服務(wù)器。
Store委乌、Action是完全可復(fù)用的床牧,同時這兩者并不關(guān)聯(lián)實際業(yè)務(wù),所以完全可以模塊化遭贸,同時只要行為足夠完整戈咳,也不需要關(guān)系具體業(yè)務(wù)方統(tǒng)計數(shù)據(jù)的樣式。這樣就可以讓其他模塊完全的復(fù)用了壕吹。
那么如何提升復(fù)用性著蛙,我們來關(guān)聯(lián)下之前討論過的MVP。
這里耳贬,紅色框內(nèi)的部分都是邏輯性的踏堡,是完全可復(fù)用的;View也是獨立與邏輯的咒劲,也是可復(fù)用的顷蟆;只有Subscriber和Controller是和業(yè)務(wù)強(qiáng)相關(guān)的,是不可復(fù)用的缎患。那么我們就可以知道需要把哪些東西放到不可復(fù)用的地方慕的,哪些東西放到可以復(fù)用的地方了。
同時我們也需要考慮下測試的問題挤渔,來解決埋點數(shù)據(jù)的完整性和正確性肮街。
只要我們mock了Store部分,就可以輕易的檢查發(fā)生的Action判导,或者向訂閱者發(fā)送對應(yīng)的Action嫉父,這樣就可以比較簡單的去回歸測試數(shù)據(jù)了。只不過這樣做的收益可能并不高眼刃。
實現(xiàn)
這里我們來看看實現(xiàn)的方式绕辖。
首先定義基礎(chǔ)的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來分發(fā)行為就可以了擂红。
let action = StatStore(type: "star", params: ["id": "1234"])
self.store?.dispatch(action)
這里訂閱者甚至可以自己創(chuàng)建獨立的類來處理這些情況仪际,這樣就更加的分離了行為統(tǒng)計這種不能劃分為任何模塊的內(nèi)容了。
最后
這個方案將行為統(tǒng)計從整個app中剝離出一個單獨的模塊,同時實現(xiàn)了高度可復(fù)用性树碱,而且使得統(tǒng)計也成為可以單元測試的了肯适。唯一的缺點是在具體統(tǒng)計的時候需要大量switch...case...來區(qū)分不同的行為。