如何處理偶現的 Bug
- 2019 年 10 月 7 日
- 筆記
閱讀本文大概需要 5 分鐘
01
偶現bug是噩夢
偶現 bug 是一種類似於外太空50足隱身蠍子的東西。這種噩夢是如此稀少以至於它很難觀察,但其出現頻率使得它不能被忽視。你不能調試因為你不能找到它。
儘管在8個小時後你會開始懷疑,偶現的 bug 必須像其他事情一樣遵循相同的邏輯規律。但困難的是它只發生在一些未知的情形。嘗試着去記錄這個 bug 出現時的情景,這樣你可以去推測到底是什麼樣的可變性。情況可能跟數據的值相關,比如「這只是在我們把Wyoming作為一個值輸入時發生」,如果這不是可變性的根源,下一個懷疑應該是不合適的同步並發。
02
重現bug
嘗試,嘗試,嘗試去在一種可控的方式下重現這個 bug。如果你不能重現它,用日誌系統給它設置一個圈套,來在你需要的時候,在它真的發生的時候,記錄你猜想的,需要的東西。重新設計這個圈套,如果這個bug只發生在產品中,且不在你的猜想中的話,這可能是一個漫長的過程。你從日誌中得到的(信息)可能不能提供解決方案,但可能給你足夠的信息去優化這個日誌。優化後的日誌系統可能花很長時間才能被放入產品中使用。然後,你必須等待 bug 重新出現以獲得更多的信息。這個循環可能會繼續好幾次。
03
幾個解決偶遇bug案例
我曾創建過的最愚蠢的偶現 bug 是在用一個函數式編程語言里為類工程做多線程實現的時候。我非常仔細地保證了函數式程序的並發估計, CPU 的充分使用(在這個例子里,是8個 CPU)。我卻簡單地忘記了去同步垃圾回收器。系統可能運行了很長一段時間,經常結束在我開始任何一個任務的時候,在任何能被注意到的事情出錯之前。我很遺憾地承認在我理解我的錯誤之前,我甚至開始懷疑硬件了。
在工作中我們最近有這樣一個偶現的 bug 讓我們花了幾個星期才發現。我們有一個多線程的基於 Apache™ 的 Java™web 服務器,在維護第一個頁面跳轉的時候,我們在四個獨立線程而非頁面跳轉線程里,為一個小的集合執行所有的 I/O 操作。每一次跳轉會產生明顯的卡頓然後停止做任何有用的事情,直到幾個小時後,我們的日誌才讓我們了解到底發生了什麼。因為我們有四個線程,在一個線程內部發生這種情況並不是什麼大問題,除非所有的四個線程都阻塞了。然後被這些線程排空的隊列會迅速填充所有可用的內存,然後導致我們的服務器崩潰。這個 bug 花了我們一個星期去揪出這個問題,但我們仍然不知道什麼導致了這個現象,不知道它什麼時候會發生,甚至不知道它們阻塞的時候,線程們在幹什麼。
這表明了有關使用第三方軟件的一些風險。我們在使用一段授權的代碼,從文本中移除HTML標籤。受它的起源的影響,我們把它叫做法國脫衣舞者。儘管我們有源代碼,我們沒有仔細研究它,直到查看我們服務器的日誌的時候,我們最終意識到是「法國脫衣舞者」使郵件線程阻塞了。
這個工具在大多數時候工作得很好,除了處理一些長而不常見的文本時。在那些文本里,代碼複雜度是 N 的平方或者更糟。這意味着處理時間與文本的長度的平方成正比。正式由於這些文本通常都會出現,所以我們才可以馬上發現這個 bug。如果他們從來都不會出現,我們永遠都不會發現這個問題。當它發生時,我們花了幾個星期去最終理解並且解決了這個問題。
偶遇bug經常是花費很多時間去解決不了,這時候你需要冷靜的去分析你的程序,你的業務邏輯,技術實現等,逐步去排除可能,最後定位。