當前位置:
首頁 > 最新 > 困擾我三個月的bug解決了!

困擾我三個月的bug解決了!

人在江湖飄,哪能不挨刀。

我挨了重重一bug。嚴格來講這可能是我職業生涯以來的首個悲慘經歷,因為憑我的知識儲備和經驗,基本上任何可重現的bug都是可解的。然而這個bug卻困擾了我三個月之久,它具有以下生理特徵:

1. 後台日誌能統計到異常,偶發、低頻

2. 報異常的用戶設備不具有規律性,什麼手機都有

3. 我們自己無法復現,任何設備、任何環境都沒復現

4. 打電話回訪報異常的用戶,確實存在問題

5. 客服未接到用戶主動反饋這個異常

此bug並不是js報錯,而是一個業務邏輯的錯誤。表現是,用戶提交的數據莫名缺失。場景是以下這個界面

當用戶填滿所有的空之後,提交按鈕變為可用狀態,數據放進一個數組中提交上來。後台有報錯日誌顯示,用戶提交上來的有些是空數組,有些是數組中缺了幾項。

問題在於,提交前是有校驗的,用戶不可能提交上來這種未通過校驗的數據。並且還是偶發的啊,如果是邏輯寫錯了,那應該全部會報錯,我們在測試的時候肯定會發現。

最棘手的地方是,我們壓根沒重現過這個情況,找各種同事、各種手機、各種胡亂操作,一次都沒重現出來。這就給調試帶來很大的麻煩,只能是猜測哪裡可能出問題,然後去驗證。但是根本沒法去驗證啊。。。重現不了,又如何判斷是成功fix了。

看來能驗證的手段就只剩一個:線上日誌。猜問題、上線、看日誌。

這是一個痛苦的過程。界面雖然簡單,這卻是一個龐雜的項目。因為題型眾多,抽離了很多組件,為了公用和靈活擴展,組件嵌套深度有五層之多。其架構複雜程度在我的職業生涯中也能排TOP3.

拿題乾的渲染來說,就有:公式圖片轉LaTeX、mathjax渲染公式、渲染公式上的空、給空編號、模擬游標、自動focus空、動態計算字體大小等諸多流程。而且下方那個鍵盤還是我們H5模擬的,並不是系統鍵盤。更別提還有校驗邏輯、判分邏輯。

前n次嘗試

看距離上一版有哪些改動,抹去有嫌疑的改動,看日誌是否正常。尷尬的是,這是一次重大重構,改動的地方還特別多。於是一場盲人摸象式的遠程debug行動開始了。

一次又一次的上線、觀察日誌、下線。不斷排除了一些相關的功能,始終未能診斷到問題所在。甚至連我很確信的地方都嘗試了,還是找不到問題。前前後後嘗試了二十多次吧,改到我都懷疑人生了。領導看了這些上線記錄都怒了,說你這上上下下的搞雞毛呢。我也很崩潰啊。

看來用這個盲人摸象手段是搞不定了,我意識到了情況的嚴重性,暗暗感覺這可能不是輕易能解決的,呂某一定使出畢生所學,為民除害。

第n+1次嘗試

既然有那麼多的用戶日誌,我們自己為何重現不了?這是我一直糾結的。於是再次進行瘋狂測試。

皇天不負有心人,我竟然真的給重現出來了!操作是這樣的:填好空,兩個手指同時按下提交按鈕和刪除按鈕。這樣的話既通過了校驗,又能在提交之前把數據給刪了。

發現這個騷操作的時候我是很興奮的,但是會有那麼多用戶這麼操作嗎?顯然不太可能。此時我又想到,提交按鈕和刪除按鈕是挨著的,會不會是用戶按提交的時候誤觸了刪除鍵。這還算比較合理,畢竟用戶是小學生嘛,操作不一定那麼精準的。

我興奮不已的進行驗證。在刪除鍵和提交鍵之間加了「下一空」按鈕(通過配置),這樣用戶保證不會誤觸了。

上線,日誌依舊。我摔啊,看來並不是誤觸的事。

第n+2次嘗試

隨著bug拖的時間越來越長,我的心態也有點焦躁。但思路還是聚焦在刪除按鈕上,畢竟這是好不容易發現能重現的。

如何能夠既點提交又點刪除呢?這時候我想到了點擊穿透(鍵盤為了響應快,使用了touchstart事件)。因為在點完提交的時候,模擬鍵盤會收起來,而收起的過程中刪除按鈕會經過提交按鈕的位置。根據點擊穿透的原理,如果此時派發的click事件作用到了刪除按鈕上,那豈不是就算點到了?

我都有點佩服我的想像力了,黔驢技窮了啊,試吧。避免點擊穿透有兩種方式,阻止click事件的默認動作,或者是讓元素收起的時間延遲。我選了後者。

上線,日誌依舊。我吐血。後來一想,刪除按鈕根本都沒監聽click事件啊,哪來的穿透。真是病急亂投醫了。

第n+3次嘗試

掃代碼,發現一個很重的疑點。答案是個數組,是引用類型。由於複雜的組件關係,這個引用類型的數據可以被多個組件訪問到。

使用可變數據的時候有個隱患,它可能在你不知道的地方被修改。代碼是vue寫的,有些組件中含有watch,搞不好是意外進了哪裡的watch,在點完提交的時候也會把數據給更改了。

這個猜測我覺得是合理的,在開發階段我就曾因為未使用immutable數據而隱隱擔心過。好了,快速驗證吧。在點完提交按鈕的時候,我把答案數據給克隆了一份,然後再進行判分和提交的操作。這下就不擔心已經拿到的數據被篡改了。

上線,日誌依舊。繼續吐血。

不過這次也縮小了嫌疑範圍,看來數據不是在點完提交的時候被篡改了,而是提交上來的就有問題。匪夷所思的是,用戶是如何繞過校驗把數據提交上來的呢?難不成是我的校驗函數有問題,這個地方把數據給改了?掃了一遍代碼,無果。

第n+4次嘗試

此時聚焦到了用戶在填寫答案的時候發生了什麼。我像偵探一樣用放大鏡一遍遍看代碼,然而好多天的追蹤,並沒有找到什麼有用線索。

直到有一天,那天陽光明媚天空飄著朵朵白雲,感覺有什麼好事要發生。QA在反饋群里發了一張截圖,說公式解析的那個點點點一直不消失(正在解析的狀態),而且空里也輸不進內容去。如下圖:

我敏感的神經頓時嗅到了一絲線索。題干使用了mathjax來解析公式,而mathjax在解析的過程中會按需載入一些字體文件,而且還會掃描頁面節點,並生成大量的DOM節點。這對瀏覽器來說是個壓力不小的事情,更何況是移動端。

我馬上再掃描公式處理的代碼,由於有些空會在公式上出現,所以代碼是在等公式渲染完後統一給空編序號,然後進行自動focus,而且自動focus的時候還會首先給答案賦值。天吶,問題該不會出現在這裡吧!公式的渲染過程可能有延時,用戶可能在這個時間進行點什麼操作!

首先這符合偶發這個事實,因為公式解析中出現抖動網路延遲什麼的也是偶然現象。再者公司的網路快,用戶的網路可能慢,這也符合我們一直未重現的事實。感覺這次很靠譜了!很多偵探電視都是這麼演的啊,主人公通過別人無意的一句話聯想到了線索,然後案件破解,真相大白!對對對,就是這個感覺!

趕快在代碼層面做優化,儘可能早地處理沒有公式的空,有公式的地方也確保執行完後用戶才能輸入。

優化完畢,回歸測試,萬事俱備,只等線上驗證,一錘定音!

結果......日誌還有啊!啊噗!,電視里都是騙人的啊!

等等!日誌雖然還有,但好像少了耶!難道這次的優化是有作用的?雖然從理論上能解釋一些作用,但還存在的日誌又表示什麼呢?難道造成丟答案的原因不止一個?

第n+5次嘗試

時間一天天過去,我還是沒找到什麼有力線索。中陸續有一些猜測,打了一些日誌點後還是無果。看著QA同事緊縮的眉頭,領導關切的詢問,我也越發焦慮了起來。因為我這是一個公共組件庫,有其他項目在等著使用,如果我的bug解決不了,將影響其他項目的進度。

又是陽光明媚的一天,天空飄著朵朵白雲。我無意跟另一位後端同事聊到了這個話題,他隨口一說:應該是超時自動提交的吧。

什麼?什麼!自動提交?!我突然像被閃電擊中。因為我寫的這是個公共組件,同時也對外暴漏了一些API,比如提交答案就是其中一個。我提供的是答題界面的組件,但是別人項目中有倒計時的場景,超時後會調用我的提交API,把用戶答案提交上去。

如果超時的時候,用戶什麼也沒填,那豈不是把空答案給提交上去了!!!根本沒有校驗函數什麼事,是別人調API提交的啊。

我哭了。難怪沒用戶反饋呢,時間到了自動提交空答案,他們沒理由反饋啊。難怪我們自己沒重現呢,一直沉浸在怎麼亂點出來。就算QA同學看到了超時提交的時候,也無法意識此時是空答案。

沒錯,真相就是它了,修改相關邏輯後上線,果然報錯日誌沒了。困擾我三個月的bug終於解決了!我閉上眼睛,心裡默默放了一把鞭炮。

總結

前前後後三個月時間,總算是找到了問題所在。其實第n+4次是解決了一些問題的,最後一次是徹底解決,我用實際行動證明了,真相不止一個。而這件沸沸揚揚的丟答案bug事件,也給了我很多啟示。

1. 做重構的時候要格外小心邏輯更改,重構後一定要跑通所有case。

2. 排查問題的方式,這期間我使用了各種對照試驗,各種源碼級別的排查

3. 使用vue做複雜項目的時候要格外注意組件的嵌套層數,少寫watch,避免程序執行順序的混亂

4. 設計對外API時,要考慮健壯性,不光考慮傳入參數的不穩定,還要考慮當前上下文的不穩定。

以上。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 大豹雜說 的精彩文章:

2017年終總結——恍恍惚惚又一年

TAG:大豹雜說 |