和各種詭異Bug打交道13年,我總結了18條經(jīng)驗
時間:2021-08-19 15:19:05
手機看文章
掃描二維碼
隨時隨地手機看文章
[導讀]編譯:伯樂在線-鄭蕓,英文:HenrikWarne我寫了我是怎樣追蹤這些年遇到的最有趣bug的。最近我重新瀏覽了這所有的194個條目(歷時13年),看看我從這些bug中學到了學到了那些重要的經(jīng)驗教訓。我分為編碼、測試和調(diào)試三大類。編碼這些都是過去給我?guī)砑謆ug的問題:1.事件...
編譯:伯樂在線 - 鄭蕓,英文:Henrik Warne我寫了我是怎樣追蹤這些年遇到的最有趣 bug 的。最近我重新瀏覽了這所有的 194 個條目(歷時 13 年),看看我從這些 bug 中學到了學到了那些重要的經(jīng)驗教訓。我分為編碼、測試和調(diào)試三大類。
編碼
這些都是過去給我?guī)砑?bug 的問題:1.事件順序
當處理事件時,問以下問題富有成效:事件是否可以以不同的順序到達?如果沒收到這些事件怎么辦?如果事件在同一行出現(xiàn)兩次怎么辦?即使這通常不會發(fā)生,在系統(tǒng)的其他部分(或交互系統(tǒng))中的bug也會導致它發(fā)生。2.處理太早
這是上述“事件順序”中的一個特殊情況,但是它已導致了一些棘手的bug,所以它自成一派。例如,如果信令信息接收得過早,在配置和啟動程序完成之前接收,許多奇怪的行為就會發(fā)生。另一個例子,當一個連接在被放入空閑列表之前就被標記為斷開。當我們處理這個問題時,我們通常假設它處在空閑列表狀態(tài)時被標記為斷開(但是當時它為什么沒有從這個列表上撤下?) 沒考慮到事情有時發(fā)生過早是由于我們沒有想到。3.隱蔽故障
例如,一些最難找的的 bug 是由于出現(xiàn)了隱蔽故障而繼續(xù)執(zhí)行而不是給出錯誤的代碼導致的。例如,系統(tǒng)調(diào)用(如綁定)返回未檢查的錯誤代碼。另一個例子:當遇到一個錯誤元素時,直接返回而不是給出錯誤的解析代碼。調(diào)用在故障的狀態(tài)下持續(xù)了一段時間,使得調(diào)試的難度加大。一旦故障被檢測出,最好要及時返回這個錯誤。4.If 語句
含有多個條件的If語句(if (a or b),尤其是當嵌套時,if (x) else if (y)),給我導致了許多 bug。即使If語句在概念上很簡單,當它有多個條件需要追蹤時,很容易出錯。最近我嘗試重新把代碼寫得簡潔,避免出現(xiàn)復雜的If語句。5.Else
有一些bug的產(chǎn)生是由于沒有恰當?shù)乜紤]如果條件為假,什么應該發(fā)生。在幾乎所有的情況下,每個If語句都應該有個else部分。而且,如果你在If語句的一個分支中設置了一個變量,你也許應該在其他分支也設置該變量。與此相關的是標志(flag)被設定的情況。僅僅添加設定標志的條件很容易,但是容易忘了添加應該重新設定標志的條件。任由永久性設定的標志留在那里可能會在將來導致 bug。6.改變假設
一開始最難預防的許多bug是由不斷變化的假設引起的。例如,最初僅僅只有一個客戶,在這個假設下寫了很多代碼。后來某個時候,設計發(fā)生了變化,允許每天有多個客戶事件。當這種情況發(fā)生,就很難改變受到新設計影響的所有情況。很容易找到顯式依賴該變化的所有項,但是難的部分是,找到隱式依賴舊設計的所有情況。例如,可能有代碼讀取給定某一天的所有客戶事件。一個隱式的假設可能是,結果集中元素的數(shù)量絕對不會大于客戶數(shù)量。我沒有好的方法可以預防這類問題,歡迎讀者建議。7.日志記錄
深入了解程序所做的任務是至關重要的,尤其是當邏輯復雜的時候。確保添加足夠的(但也別太多)日志記錄。那樣你就能弄清楚為什么程序在執(zhí)行它執(zhí)行的任務。讓一切運轉良好時,它無關緊要。但是只要問題發(fā)生(這不可避免),你會很慶幸你添加了合適的日志記錄。測試
作為一名開發(fā)者,除非進行了測試,否則我不會說完成一項功能。起碼這意味著每一行新代碼或更改后的代碼至少執(zhí)行了一次。此外,單元測試或功能測試也很好,但不夠。新功能還必須在類似產(chǎn)品的環(huán)境下進行測試和探究。唯有這樣,我才可以說完成了一項功能。下面是 bug 在測試方面給予我的一些重要的經(jīng)驗教訓:8.零(zero)和空(null)
務必要以零和空(合適的情況下)來進行測試。對于字符串而言,這意味著既指長度為零的字符串,又指內(nèi)容為空的字符串。另一個例子:在發(fā)送任何數(shù)據(jù)(零字節(jié))之前,測試 TCP 連接的斷開。沒有使用這些組合來測試是 bug 悄然出現(xiàn)的頭號原因,我在測試時是原本可以發(fā)現(xiàn)這些 bug 的。9.添加和刪除
新功能常常需要能夠為系統(tǒng)添加新配置,比如說用于電話號碼翻譯的新配置文件。我們會自然而然的添加一個配置文件,來驗證功能是否正常。然而,我發(fā)現(xiàn)很容易忘了還要測試配置文件的刪除。10.錯誤處理
處理錯誤的代碼常常很難測試。最好由自動測試來檢查錯誤處理代碼,但有時這不可能。這種情況下,我有時采用的一招就是,臨時修改代碼,讓錯誤處理代碼運行。要做到這一點,最容易的方法就是反轉if語句,比如說將 if 語句由error_count > 0
反轉為error_count == 0
。另一個例子是誤拼數(shù)據(jù)庫列名,讓所需的錯誤處理代碼運行。11.隨機輸入
另一種往往能夠發(fā)現(xiàn) bug 的測試方法是進行隨機輸入。例如,H.323 協(xié)議的 ASN.1 解碼可處理二進制數(shù)據(jù)。通過發(fā)送有待解碼的隨機性字節(jié),我們發(fā)現(xiàn)了解碼器中的幾個 bug。另一個例子是使用測試調(diào)用生成腳本,其中調(diào)用持續(xù)時間、回復延遲、第一方掛斷等都是隨機生成的內(nèi)容。這些測試腳本暴露了無數(shù) bug,尤其是接踵而至的事件引起的干擾。12.檢查什么不該發(fā)生
通常測試包括檢查一些需要的行為發(fā)生。但是很容易忽略他的對立面——檢查不該發(fā)生的事確實沒發(fā)生。13.自制工具
通常,我創(chuàng)建了自己的小工具來使測試更簡易。例如,當我處理面向 VoIP 的 SIP 協(xié)議時,我寫了一個小的腳本可以返回正標題和值。這個工具使得測試許多個別場景變得簡單。另一個例子是可以調(diào)用 API 的命令行工具。從小的開始,逐漸添加一些需要的功能,我最終有許多有用的工具,寫自己的小工具的優(yōu)勢是我得到我想要的功能。在測試中要發(fā)現(xiàn)所有的 bug 幾乎不可能。有一次,我在一種情況下,我對處理關聯(lián)號碼做了改變,包括兩部分:路由地址前綴(總是相同),和從 000 到 999 的動態(tài)分配號碼。問題是,當查找相關性時,動態(tài)分配的數(shù)字的第一個數(shù)字在查找之前被錯誤地刪除。所以,不是尋找 637 之類的號碼,你尋找的是 37,而這個號碼不在表中。這意味著,它一直尋找到 100,所以前 100 個調(diào)用正常,而之余的所有 900 個調(diào)用失效。所以除非我在重新啟動之前測試了 100 多次,否則在測試時發(fā)現(xiàn)不了這個問題。調(diào)試
14.討論
在過去對我?guī)椭畲蟮恼{(diào)試方法就是與同事討論問題。我常常只要向同事描述問題,就足以認識到問題是什么。此外,即使同事不是很熟悉相應代碼,常常也能給出好主意,表明哪里可能有問題。我在處理最棘手的 bug 時,與同事討論特別有效。15.密切注意
往往是當調(diào)試一個問題很長時間時,是因為我做了錯誤的假設。例如,我認為這個問題發(fā)生在一個特定的方法中,事實上,這個問題甚至根本不會出現(xiàn)在這個方法中。或者拋出的異常并不是我認為的那個。或者我認為最新版的軟件在運行,但它其實是較老的版本。因此,一定要驗證細節(jié),而不是假設。它使你容易看到你所期望看到的問題,而不是實際發(fā)生的問題。16.最近的一次改動
本該運行的程序停止了,它通常是由最后的一次變動導致。有一次,最近的一次變動僅僅是日志,但是日志中的一個錯誤導致了更大的問題。為了讓諸如此類的回歸更容易找到,有必要在不同的提交代碼中實行不同的變更,并且要清楚說明變更。17.相信用戶
有時當一個用戶反饋問題時,我的本能反應是:這不可能,他們一定搞錯了。但是我已經(jīng)意識到我不應該這樣做。我也不想這樣,但更多次,事實證明他們報告的問題實際上發(fā)生了。所以這些天,我認真對待他們的反饋。當然,我仍然反復測試所有的一切被正確地設置了。但是我碰過好多情況下,之所以發(fā)生奇怪的問題,是由于不同尋常的配置或意料之外的使用,而我的默認假設是他們是對的,程序是錯的。18.測試修復的效果
如果你已經(jīng)修復了 bug,還需要再測試。首先運行修復前的代碼,然后觀察 bug。然后運用修復再次測試?,F(xiàn)在 bug 的問題應該被消除了。繼續(xù)這些步驟確保它確實是一個 bug,確保你的修復已經(jīng)修復這個問題。簡單但很必要。其他心得
過去 13 年,我一直在記錄我遇到的最棘手的 bug,很多事情發(fā)生了改變。從小的嵌入式系統(tǒng),到大的電信系統(tǒng),網(wǎng)頁系統(tǒng)都做過。我使用的語言包括 C 、Ruby、Java 和 Python,若干類的 bug 在我使用 C 的日子里就已經(jīng)不再出現(xiàn)了。像堆棧溢出,內(nèi)存損壞,字符串的問題以及某些形式的內(nèi)存泄漏。其他的問題,像回路錯誤和極端案例,我見的少得多,因為我單元測試了更多邏輯,但這并不意味著那里沒有 bug。這篇文章總結的經(jīng)驗教訓,幫助我在編碼、測試和調(diào)試這三個階段盡量減小破壞。如果你發(fā)現(xiàn)了其他的技巧或者有用的技巧來預防或者找到 bug,請在評論區(qū)留言。編譯:伯樂在線 - 鄭蕓版權歸原作者所有,如有侵權,請聯(lián)系刪除。