姓名:李興宇? 學(xué)號:16030110084
轉(zhuǎn)載自:http://home.bdqn.cn/thread-113125-1-1.html,有刪節(jié)。
【嵌牛導(dǎo)讀】:你是否曾經(jīng)修復(fù)了一個 bug ,隨后又發(fā)現(xiàn)了一個跟剛修復(fù) bug 有關(guān)的 bug ,又或是修復(fù) bug 的方式引起了另一個 bug ?
【嵌牛鼻子】:編程,修復(fù)bug,設(shè)計(jì)評審,代碼審查
【嵌牛提問】:我們該如何找到并修復(fù)所寫程序中出現(xiàn)的 bug?
【嵌牛正文】:
當(dāng)我修改 bug 時(shí),我會問自己三個問題,以確保我已經(jīng)仔細(xì)考慮了它的意義。每次你認(rèn)為發(fā)現(xiàn)并修改了一個 bug 時(shí),可以使用這些問題來提高生產(chǎn)力和代碼質(zhì)量。
這些問題背后的主要思想就是:每一個 bug 都是底層進(jìn)程的一個不良表現(xiàn)。你必須處理這些癥狀,但如果你僅僅是處理這些外在癥狀,你就會有永遠(yuǎn)解決不完的問題。你應(yīng)該找到產(chǎn)生 bug 的進(jìn)程,并且修復(fù)這個進(jìn)程。當(dāng)你確定究竟發(fā)生了什么和發(fā)生這些的原因時(shí),也許你就會明白產(chǎn)生 bug 的基礎(chǔ)進(jìn)程不是隨機(jī)的,而是可控的。
在問這三個問題前,你需要克服面對 bug 的這種天生的抗拒,仔細(xì)分析 bug 。查看代碼并解釋出錯的原因,從能觀察到的現(xiàn)象開始,向后分析,不斷地問為什么,直到你可以找到產(chǎn)生 bug 的模式。通常,你該跟同事一起做這件事, 因?yàn)榻忉屇阏J(rèn)為會發(fā)生的事情,將迫使你面對一些假設(shè)——這些程序是做什么的。
“它溢出了,因?yàn)橄聵?biāo)J越界了。”
“為什么?”
“J 是 10,但數(shù)組最大下標(biāo)為 9。”
“為什么?”
“J 是一個字符串長度,數(shù)組的起始下標(biāo)是 0,所以字符串長度為 1 的最后一個字符的索引是 0。”
找到 bug 后,查找其他意外情況。檢查程序出錯時(shí)主要的程序變量的值,是否可以解釋這些值。
“為什么 name 是 null?”
“為什么它總是輸出錯誤信息呢?”
記錄下你做了哪些操作,發(fā)生了哪些變化。你需要知道究竟發(fā)生了什么,這樣做就意味著你時(shí)刻有一把標(biāo)尺和歷史記錄。
當(dāng)完成這些步驟后,你可以準(zhǔn)備問第一個問題了。
1. 其他地方也會出現(xiàn)這個錯誤嗎?
查看代碼中使用相同模式的地方,系統(tǒng)地改變模式找出類似的 bug 。
“我還在其他什么地方使用長度作為下標(biāo)的嗎?”
“所有數(shù)組的起始下標(biāo)都一樣嗎?”
“對于一個長度為 0 的字符串會發(fā)生什么?”
試著描述這部分代碼中應(yīng)該是正確的,但是這些 bug 沒有遵循的規(guī)則。尋找這個不變量的過程將幫助你找到其他潛在的 bug 。
“起始偏移加上長度減去1就是結(jié)束的下標(biāo),除非數(shù)組長度為 0”。
對于你發(fā)現(xiàn)的每一個 bug ,你都可以解決一批 bug ,這是非常高效的。嘗試用概括性的語言描述這些 bug 也能提升你對程序的理解程度,并幫助您避免在程序中引入更多的 bug 。
2. 這個 bug 后面隱藏著什么其它的 bug ?
一旦你確定了如何修復(fù)這個 bug ,你就需要考慮一下修復(fù)后會發(fā)生什么。這個執(zhí)行失敗的語句后面的語句也可能有問題,但是程序還沒有執(zhí)行到此就不知道有沒有 bug ,或者有些代碼因?yàn)槟阈迯?fù) bug 而第一次出現(xiàn)在程序中,這些代碼也可能有問題。查看這些未執(zhí)行的語句,檢查代碼中的 bug 。
“下一條語句會正常運(yùn)行嗎?”
當(dāng)你在想程序的控制流的時(shí)候,可以弄清楚還有哪些地方程序沒有執(zhí)行到。
“是否有我從來沒有測試過的功能組合?”
在程序中插樁(instrumentation)并不會耗費(fèi)太多時(shí)間,在運(yùn)行程序各個部分的過程中就可以進(jìn)行檢查,但是你會驚訝地發(fā)現(xiàn)開發(fā)者測試過的代碼還有很多都不能正常運(yùn)行。
“我可以測試出所有的錯誤信息嗎?”
注意一個地方的改動可能會引起其他地方的 bug 。一些變量的局部改動可能會在執(zhí)行時(shí)違反后來的假設(shè)。
“如果僅是從 J 中減去 1,當(dāng)長度為 0 時(shí),后面的語句會操作數(shù)組中 -1 位置的元素。”
如果程序已經(jīng)做了大量改動,就要仔細(xì)考慮是否有必要增加另外一個補(bǔ)丁,或者是時(shí)候考慮重新設(shè)計(jì)和重新實(shí)現(xiàn)了。
3.我應(yīng)該做些什么防止類似 bug 的產(chǎn)生呢?
問問自己如何改變編程方法,根據(jù)定義避免 bug 的出現(xiàn),通過改變方法或者工具,經(jīng)常可以移除整個類的錯誤而不用一個一個的解決 bug 。
從“ bug 是何時(shí)引入的”這個問題開始:在程序開發(fā)生命周期的哪一個階段可以阻止 bug 的產(chǎn)生?
“設(shè)計(jì)是沒問題的;我在編程時(shí)引入了 bug 。”
仔細(xì)檢查 bug 產(chǎn)生的原因,考慮 bug 產(chǎn)生時(shí)正在運(yùn)行的進(jìn)程,并想想怎么改變它來阻止 bug 的產(chǎn)生。
“將偏移的數(shù)據(jù)類型和長度分離出來將會在編譯時(shí)捕獲這個錯誤。”
“每一個文本項(xiàng)可以用隱藏了下標(biāo)計(jì)算的宏輸出,然后我就可以一次找到它。”
不要滿足于膚淺的答案。假如你對于一個 bug 的解釋是,“我記不清了”,那還怎么改進(jìn)這個過程,讓你不再需要記住它?你可以更改編程語言,使被忽略的細(xì)節(jié)可以完全隱藏,否則你遺漏的部分會被檢測到從而導(dǎo)致編譯問題。對這個問題域,你可能使用了預(yù)處理器或者智能的編輯器,有默認(rèn)值,錯誤檢查,智能提示和快速文檔。這個 bug 可能是編程團(tuán)隊(duì)溝通的問題,亦或是需要討論的設(shè)計(jì)沖突。
思考發(fā)現(xiàn) bug 的方式,并問問自己如何能更早發(fā)現(xiàn)它。測試怎么可以更嚴(yán)密一些?能否進(jìn)行自動化測試?是否要添加代碼實(shí)時(shí)檢測功能,以便可以及時(shí)捕獲錯誤信息?
“我應(yīng)該在我的測試單元中嘗試長度為 0 的數(shù)組”。
“我應(yīng)該進(jìn)行下標(biāo)檢查,提前捕獲不符合的下標(biāo)”。
有必要創(chuàng)建一些系統(tǒng)方法和自動化工具,用于編譯、構(gòu)建和測試,它們可以減少長時(shí)間的調(diào)試和查明具體事實(shí)的過程。
這個技巧的應(yīng)用
養(yǎng)成這樣一種習(xí)慣:每當(dāng)你發(fā)現(xiàn)一個 bug 時(shí),問自己這三個問題,甚至你不必等到有 bug 時(shí)才使用這三個問題。
在設(shè)計(jì)和審查過程中,你都可以用這三個問題來處理你得到的每一條意見。審閱意見是潛在的溝通過程的結(jié)果,使你可以有所改進(jìn)。如果你認(rèn)為讀者評論是錯誤的,比如,你可能會問是什么使你的文章沒被理解,如何更好地與審稿人溝通。
設(shè)計(jì)評審和代碼審查是找出 bug 的強(qiáng)有力手段,你可以對審查過程出現(xiàn)的每一個缺陷都提出三個問題。如果審查徹底,前兩個問題不會出現(xiàn)太多新的 bug ,但第三個問題可以幫助你找到方法,用來避免未來可能會出現(xiàn)的 bug 。