而一旦這些問題導(dǎo)致了 JavaScript 報(bào)錯(cuò)(如空指針異常),并且沒有被有效地隔離,就有可能引發(fā)頁面的白屏、無法交互等線上問題。
在雙 11 準(zhǔn)備期間,我們收集了過往一年前端相關(guān)的線上問題,在收集的 21 個(gè)案例中,竟有一半的問題都與「數(shù)據(jù)異常觸發(fā)頁面顯示異?!惯@個(gè)原因有些相關(guān)。
如何將錯(cuò)誤的影響隔離在一定范圍內(nèi),顯得尤為重要。
這篇文章就和大家一起來聊一聊我們嘗試過的一些方案,及遇到的問題。
從空指針異常說起
數(shù)據(jù)引發(fā)的最常見的問題就是空指針異常。
var result = a.b.c.d;
這樣的代碼如同地雷,一旦 a 是一個(gè)動態(tài)數(shù)據(jù),那么問題一觸即發(fā)。
封裝一個(gè) get 的方法來取值,當(dāng)數(shù)據(jù)不存在時(shí),返回 undefined ,可以快速避免此類問題。
var result = get(a, 'b.c.d'); 但如同我們期望大家在取值前,都先做判斷一樣,并不能保證所有人都這么用了,用不用全靠自覺。 if (a && a.b && a.b.c) { var result = a.b.c.d; }
所以,有了以下的一些方案:
異步數(shù)據(jù)校驗(yàn)
對異步數(shù)據(jù)校驗(yàn)的想法是,在數(shù)據(jù)獲取后、使用前,先做一遍schema校驗(yàn),檢測重要數(shù)據(jù)缺失、類型不對等異常情況。
與此方案對應(yīng)的,我們在 fetch 的基礎(chǔ)上封裝了 fetch-checker 注1 組件。
fetch-checker 強(qiáng)制要求用戶在請求數(shù)據(jù)的同時(shí),提供數(shù)據(jù)對應(yīng)的 schema:
let schema = { "rule": { "type": "string", }, "banner": { "type": "object", "required": true, "default": { "url": "https://item.taobao.com/item.htm?id=527331762117" } } };
這份 schema 需要描述:
每個(gè)字段的類型
字段是否 required
當(dāng) required 的字段缺失時(shí),是否需要打底數(shù)據(jù)
fetch-checker 在拿到數(shù)據(jù)后,先做一層校驗(yàn),如有需要的話,補(bǔ)上缺失的數(shù)據(jù),然后再返回給調(diào)用者。這樣,使用者拿到的數(shù)據(jù)就一定是符合預(yù)期的。
然而,這個(gè)方案面臨的挑戰(zhàn)是:
如何確保調(diào)用者提供了完整的 schema 描述。不想寫 schema,完全可以提供一個(gè)粗略的 schema 描述,來通過校驗(yàn)。
schema 如何精簡。即不會對 bundle 大小造成太大影響,又能滿足校驗(yàn)的功能。
代碼編譯
受 babel 的啟發(fā),這個(gè)方案是對存在 NPE 隱患的代碼,在編譯階段,將其轉(zhuǎn)換成等價(jià)的安全代碼。如下所示:
var a = {}; // input var result = a.b.c; // output var result = (_object2 = (_object3 = a) == null ? null : _object3.b) == null ? null : _object2.c;
當(dāng) a 為空對象時(shí),執(zhí)行編譯后的代碼會返回 null ,從而避免因?yàn)榇a拋錯(cuò),阻斷后續(xù)進(jìn)程。
在 babel-plugin-safe-member-expression 注2 這個(gè) Babel 插件中,我們做了上述的嘗試。目前,cake項(xiàng)目中,已經(jīng)可以通過 enableSafeMemberExpression 這個(gè)配置,選擇性的啟用該功能。
這個(gè)方案相比來說接入成本較低,開發(fā)者無需對現(xiàn)有的代碼做出調(diào)整,但同樣存在挑戰(zhàn):
開發(fā)階段問題不易暴露,明明應(yīng)該報(bào)錯(cuò)的場景,卻沒有任何反饋。理想的狀態(tài)是:開發(fā)調(diào)試階段盡可能多的暴露問題,線上則盡可能的減少報(bào)錯(cuò)。
隱患的代碼如何界定。目前所有的 a.b 的調(diào)用方式都會按上述方案進(jìn)行編譯,雖然測試過程中還沒有發(fā)現(xiàn)問題,但只處理有隱患的代碼才更安全。
靜態(tài)校驗(yàn)
以 flow 為代表的靜態(tài)校驗(yàn)工具,可以在一定程度上檢測出 NPE 隱患。 type res = { data ?: Object } let name = res.data.name; // property `name`. Propery cannot be accessed on possibly undefined value
如上面的代碼所描述的,使用者需要首先理清自己的數(shù)據(jù)是否允許為空值,當(dāng) data 被允許為空值時(shí),通過 flow 檢測, data.name 類似這樣調(diào)用便會被檢測出錯(cuò)誤。
然而,如何來推進(jìn)所有的業(yè)務(wù)都接入靜態(tài)校驗(yàn),接入后,又如何保證開發(fā)者描述了所有的類型,卻同樣是個(gè)難點(diǎn)。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com