對(duì)前端質(zhì)量保障的思考
上次聽(tīng) 趙海平 的講座,他提到 Facebook 沒(méi)有測(cè)試人員,以前和現(xiàn)在都沒(méi)有,以后也不打算有。還提到上線之后就開(kāi)發(fā)者坐在系統(tǒng)前等著,只要有bug,系統(tǒng)能夠在五分鐘之內(nèi)檢測(cè)到,并提供快捷方式修復(fù)。我驚嘆的是他們能夠在五分鐘之內(nèi)監(jiān)控到所有的問(wèn)題,實(shí)時(shí)回饋并及時(shí)修復(fù)。
當(dāng)然在探討質(zhì)量保障這個(gè)話題前,我們需要明確幾個(gè)關(guān)鍵點(diǎn):編碼前、提交代碼、測(cè)試、上線、回滾、上線后。針對(duì)這幾個(gè)點(diǎn),下面我談一談我的看法。
一、編碼前
來(lái)阿里之前在百度實(shí)習(xí)過(guò)三個(gè)月,實(shí)習(xí)期間印象最深的交流是參與編碼規(guī)范討論,當(dāng)時(shí)我還呼呼的整理了兩份文檔:前端編碼規(guī)范之JavaScript,前端編碼規(guī)范之CSS。后來(lái)也看到團(tuán)隊(duì)在各種工具上添加控制和提示,如 Sublime Text 添加 jslint 配置,項(xiàng)目目錄下添加 .jslint 配置,打包工具提示代碼的不規(guī)范,強(qiáng)制修復(fù)等等。
上面提到的代碼規(guī)范主要是代碼展現(xiàn)層面的規(guī)范,他可以讓團(tuán)隊(duì)寫(xiě)出來(lái)的代碼就跟一個(gè)模子刻出來(lái)似的,結(jié)構(gòu)、命名、函數(shù)體大小等等很接近,看著很舒服。舉幾個(gè)例子說(shuō)明他的重要性。
1. 統(tǒng)一使用 UTF8 編碼
我平時(shí)開(kāi)發(fā)都是使用的 UTF8 編碼。有次從倉(cāng)庫(kù)拉下來(lái)發(fā)現(xiàn)很多文件都是 GBK 編碼,修改時(shí)一個(gè)文件忘記轉(zhuǎn)換編碼,提交發(fā)現(xiàn) 錕斤拷 出來(lái)了。
2. TAB 縮進(jìn)
我比較喜歡使用四個(gè)空格作為 TAB 縮進(jìn)。一次多人開(kāi)發(fā)的時(shí),發(fā)現(xiàn)同事的代碼是兩個(gè)空格的縮進(jìn),結(jié)果,我改成了四個(gè)空格提交之后,又被改回來(lái)兩個(gè)空格,然后我接著改回去…
3. 加不加分號(hào)
以前寫(xiě)過(guò)一篇文章,談了下自己對(duì)分號(hào)的看法:Javascript分號(hào),加還是不加?,我的回答是加但非必須。
代碼的規(guī)范,對(duì)程序本身的意義并不是很大,他不會(huì)作用在程序的邏輯上,作用點(diǎn)在于團(tuán)隊(duì)合作。一個(gè)項(xiàng)目可能是多人開(kāi)發(fā),也可能是今天我開(kāi)發(fā),明天托付給你。如果兩個(gè)人在編碼習(xí)慣上的差異很大,就會(huì)偏頭痛…有一點(diǎn)需要特別提出來(lái),就是寫(xiě)注釋!某次排查一個(gè)線上問(wèn)題,找到了問(wèn)題所在的文件,但是文件中的邏輯實(shí)在是太過(guò)復(fù)雜,四五百行代碼僅三行注釋,眼睛都看花了。其實(shí)只要在大段的代碼前加幾句注釋,說(shuō)明本段代碼的大意,在排查定位問(wèn)題的時(shí)候就可以忽略一部分代碼塊,可以為修復(fù)線上bug爭(zhēng)取不少時(shí)間。
二、提交代碼
這部分特指工具。可以說(shuō)過(guò)了工具這一道關(guān)卡,代碼基本就獲得自由,bug 也就開(kāi)始橫飛了。目前工具可以為我們做的事情:
1. 檢測(cè)
現(xiàn)在并沒(méi)有做 jslint 之類的配置,所以代碼的展示是沒(méi)怎么規(guī)范的。
編碼應(yīng)該統(tǒng)一為 UTF-8 格式,如果不是這種格式,工具應(yīng)該有所提示。
代碼塊過(guò)長(zhǎng)提示,一個(gè)函數(shù)不應(yīng)該寫(xiě)到幾百上千行,拆分代碼剛開(kāi)始是辛苦,一旦后續(xù)復(fù)用的時(shí)候,就會(huì)很爽很爽了(當(dāng)然,剛開(kāi)始編碼的時(shí)候就應(yīng)該考慮一個(gè)函數(shù)的顆粒度控制)。
更重要的是對(duì)語(yǔ)法的檢測(cè),我們可能把 document 拼寫(xiě)成了 doucment,甚至使用 for in 來(lái)遍歷一個(gè)數(shù)組,這種問(wèn)題時(shí)而出現(xiàn),工具是否考慮幫助我們處理掉一些簡(jiǎn)單的愚蠢的錯(cuò)誤。
2. 壓縮
壓縮代碼的時(shí)候,我踩過(guò)坑:gulp打包壓縮css遇到的坑,我相信很多人都認(rèn)識(shí) grunt 和 gulp,但是一定鮮有人自己配置過(guò)這些東西,并投入到項(xiàng)目中。
代碼的壓縮,一方面可以減少線上流量,一方面也是出于安全的考慮。壓縮后的代碼線上報(bào)錯(cuò)很難定位到準(zhǔn)確的位置,有些問(wèn)題只能在用戶的電腦上復(fù)現(xiàn),“代理到本地這個(gè)法子”遠(yuǎn)程操作的時(shí)候是不靠譜的。壓縮不僅僅應(yīng)該把代碼縮短,還要考慮線上排查問(wèn)題的難度。
在壓縮的時(shí)候可以考慮添加空行,將網(wǎng)頁(yè)錯(cuò)誤定位范圍縮減到單個(gè)文件。也可以使用 sourceMap 之類的輔助方式。在這篇文章中有過(guò)一些討論。
3. 合并
很多事情,別人不考慮,工具就得考慮。
這里有一個(gè)思考,HTTP2.0 支持多路復(fù)用,一個(gè)連接可以進(jìn)行多次 HTTP 的傳輸,那以后的 sprite 圖、文件的合并等是不是也應(yīng)該重新考慮了。文件的全部合并真的是最省資源的方式么?是否可以考慮更多的合并方案?
三、測(cè)試
趙海平 說(shuō),技術(shù)實(shí)踐中的三件套:功能 + 測(cè)試 + 監(jiān)控。很多大公司的工程師,深諳功能開(kāi)發(fā)之道,測(cè)試方面也能達(dá)到 60 分的水平,但是程序的監(jiān)控上,做的很差,包括 Facebook 的程序員。三件套,對(duì)一個(gè)優(yōu)秀的工程師來(lái)說(shuō),缺一不可。
這里要說(shuō)的是程序開(kāi)發(fā)三板斧的第二板,測(cè)試。
我們很自然地聯(lián)想到了QA,阿里有一大波的測(cè)試人員。寫(xiě)完代碼提測(cè),好像剩下的就只是測(cè)試同學(xué)找BUG,我們等著修BUG。前端的測(cè)試跟后端還不太一樣,邏輯可以測(cè),但是 UI 效果、交互效果不好測(cè),只能靠幾雙眼睛盯著看,幾個(gè)鼠標(biāo)不停地點(diǎn)點(diǎn)點(diǎn)。。。
雖說(shuō)邏輯可以通過(guò)寫(xiě)測(cè)試用例進(jìn)行測(cè)試,會(huì)去寫(xiě)測(cè)試用例的人卻不多。我記得當(dāng)時(shí)學(xué)習(xí) AOP 編程的時(shí)候,給 ajax 添加了一些 mock 功能,可以在頁(yè)面上模擬請(qǐng)求測(cè)試效果(如jquery-mockjax)。
編寫(xiě)測(cè)試用例確實(shí)可以解決很多的問(wèn)題,但是如何培養(yǎng)編寫(xiě)測(cè)試用例的習(xí)慣,如何更加便利的測(cè)試我們的測(cè)試用例,這又是一個(gè)值得思考的話題。
自動(dòng)化工具一大缺點(diǎn)是很難捕獲到特定環(huán)境下的錯(cuò)誤。據(jù)統(tǒng)計(jì),不管你的代碼寫(xiě)得多健壯,在一千個(gè)用戶下,總有那么一個(gè)用戶,因?yàn)闉g覽器安裝了插件、網(wǎng)絡(luò)問(wèn)題等導(dǎo)致代碼報(bào)錯(cuò),再比如我們?cè)谧龌叶葴y(cè)試的時(shí)候,讓用戶名首字母為 a-m 的用戶命中灰度時(shí)出現(xiàn)的錯(cuò)誤等等,這些錯(cuò)誤自動(dòng)化測(cè)試工具是無(wú)法發(fā)現(xiàn)的。
所以我們要把 錯(cuò)誤日志統(tǒng)計(jì) 靈活地使用起來(lái),他能夠使你深入用戶,拿到最原始的錯(cuò)誤信息。
四、上線
現(xiàn)在涉及到前端上線的,有多個(gè)地方(公司有很多發(fā)布系統(tǒng)):
TMS發(fā)布
aone2發(fā)布
gitlab發(fā)布
awp發(fā)布
etc.
gitlab發(fā)布通過(guò)域名嚴(yán)格區(qū)分測(cè)試、預(yù)發(fā)和線上環(huán)境,操作界限明確,出錯(cuò)的概率還是很低的(這要求開(kāi)發(fā)者對(duì) git 命令的操作十分熟練),如果幾次 reset revert stash 之后便開(kāi)始犯蒙,那出問(wèn)題的概率就增大了。每次打下 tag 之前,我都會(huì)很仔細(xì)地 diff 下代碼,看看本次發(fā)布和上次發(fā)布之間做了哪些修改,確認(rèn)這些修改點(diǎn)再 push tag。
aone2的發(fā)布,并不是每個(gè)人都用過(guò),它的靠譜在于有三種發(fā)布方式:
- 全網(wǎng)發(fā)布,半小時(shí)完成
- 小淘寶環(huán)境灰度發(fā)布,兩小時(shí)完成
- 分三次發(fā)布,小流量上線,一天完成
同時(shí)也提供了十分方便的回滾機(jī)制,只要擁有應(yīng)用的權(quán)限,可以隨時(shí)回滾代碼,效率極高。
TMS 的發(fā)布,我覺(jué)得是問(wèn)題最多的。首先,前端和運(yùn)營(yíng)都會(huì)擁有發(fā)布權(quán)限,運(yùn)營(yíng)喜歡“瞎搞”,部分頁(yè)面(如JSON輸出)并沒(méi)有提供頁(yè)面預(yù)覽,運(yùn)營(yíng)填完之后也不會(huì)跑到頁(yè)面查看效果,于是就出問(wèn)題了。。TMS發(fā)布每次修改只發(fā)布一個(gè)文件,CDN 發(fā)布一個(gè)文件的速度是很快的,當(dāng)你點(diǎn)擊發(fā)布的那個(gè)瞬間,整個(gè)同步就基本完成了。可是,當(dāng)某個(gè)節(jié)點(diǎn)同步出錯(cuò),TMS 并沒(méi)有給出提示,這是第二個(gè)隱患。第三個(gè)點(diǎn),TMS坑爹的沒(méi)有灰度,對(duì)一些重要的發(fā)布,沒(méi)有灰度就需要十分十分的謹(jǐn)慎,雖說(shuō)出錯(cuò)可以及時(shí)回滾,但萬(wàn)一沒(méi)有看到隱性的錯(cuò)誤,那就悲慘了。
五、回滾
沒(méi)人可以保證自己寫(xiě)的東西絕對(duì)不出問(wèn)題,因?yàn)橛刑嗟沫h(huán)境因素是我們想也想不到的,比如最近某類控件在小淘寶環(huán)境下全掛了,試問(wèn),前端怎么會(huì)想到這是Nginx 的灰度系統(tǒng)出問(wèn)題了,在灰度發(fā)布的時(shí)候文件沒(méi)有同步成功,導(dǎo)致整個(gè)灰度環(huán)境出錯(cuò)。
所以,一定要給你的程序想一套快速回滾方案。尤其是在做 ABTest 的時(shí)候,新版的效果不好需要回滾到之前的狀態(tài),這種事情經(jīng)常有。
回滾需要注意兩點(diǎn):
- 要快。
- 上一個(gè)狀態(tài)要保證無(wú)錯(cuò)誤。
只要我們能夠保證發(fā)到線上的每一個(gè)版本都是穩(wěn)定版,那回滾就是 0 風(fēng)險(xiǎn)的事情。
六、監(jiān)控
程序開(kāi)發(fā)三板斧的第三板,監(jiān)控。前端對(duì)測(cè)試就不太重視,更不用提監(jiān)控了。沒(méi)有監(jiān)控就只能提心吊膽的過(guò)日子。
其實(shí)我們使用自動(dòng)化工具測(cè)試、每天用肉眼頂著自己的頁(yè)面看,這些都屬于監(jiān)控,但是深入到用戶的監(jiān)控,我們做的太少!
七、小結(jié)
看到老大在群里發(fā)了幾條研發(fā)相關(guān)的紅線:
- 禁止代碼未經(jīng)測(cè)試發(fā)布;
- 禁止代碼發(fā)布后不進(jìn)行線上驗(yàn)證;
- 禁止核心應(yīng)用發(fā)布沒(méi)有對(duì)應(yīng)的回滾方案。
毫無(wú)疑問(wèn),這些都是必須嚴(yán)格遵守的。規(guī)范會(huì)先把壞習(xí)慣壓住,進(jìn)而被理解,最后被消化吸收。
前端質(zhì)量保障之路,任重而道遠(yuǎn)!