Bug 是絕佳的學習機會。所以我們怎樣儘可能多地從修復的 bug 中吸取經驗?我至今已經用了 13 年的一個方法是,寫下這個 bug 的簡單描述、修復方法以及吸取的經驗。
遙想 2002 年,我偶然發現一篇描述這個方法的部落格(很不幸,我沒再找到)。我從那時起就使用這個方法了,並且相信它能幫助我成為一個更出色的程式設計師。
每當我修復一個特別棘手或有趣的 bug,我總會花幾分鐘寫下一些這個 bug 的資訊。這有一個典型記錄的例子:
例子
【日期】:2004-08-17
【問題】:當解碼 Q.931 信令時無限迴圈
【原因】:當在Q.931信令中發現一個未知的元素id時,我們試圖通過讀取它的長度來跳過它,並且將位置指標遷移幾個位元組。但是,在這個例子中的長度是零,導致我們反覆跳過相同的元素id。
【怎麼發現的】:在解碼一個 Ethereal 從 Nortel 追蹤到的安裝資訊時發現了這個問題。他們的資訊是 1016 位元組長度(包含大量快速啟動元素),但我們的 MSG_MAX_LEN 是 1000。通常我們會收到一條來自 common/Communication.cxx 的資訊,但現在,當直接輸入需要解析的資料時,陣列末端記憶體訪問越界,其恰好是 0,暴露了這個問題。
為了找到它,我僅僅在 9931 解碼中新增一些列印輸出。但很幸運資料恰好是零。
【修復】:如果長度是零,設定為 1。這方式總是行得通。
【在哪些檔案修改了】:
callh/q931_msg.cxx
callh/q931_msg.cxx【我導致的】:是的
【解決Bug的時間】:1小時
【教訓】:信任收到資訊中獲得的資料。不僅僅是產生大量可能導致問題的資料。顯示長度為 0 也同樣不好。
實施方式
我有一個命名為 bugs.txt 的純文字檔案。在檔案的頂部是一個具備所有標題的模板,但是沒有包含資訊。當我新增一個新的記錄,我複製模板部分,貼上在模板下面。然後完成記錄並且填滿資訊。大多數展示在上面例子中的 fields 應該是不需要宣告的。
有必要指出這並不是 bug 追蹤器。我並沒有新增所有我修復的 bug 。例如,如果我只是忘記在程式碼中新增宣告,一旦發生這種情況,我就意識到問題出在哪裡了,我並不會新增這條記錄。僅僅當 bug 本身,或是修復,或是除錯過程特別有趣,我才會新增一條新的記錄。通常,是我導致了這個 bug。但是偶爾,特別是花幾天時間才追蹤到的困難 bug,儘管不是我造成的我也會新增一條記錄。
一旦修復了一個 bug,我的第一反應是鬆了口氣然後繼續前進。我試著一更正後就寫下這個記錄。這時我的腦海中依然清晰保留所有的細節。拖延會讓精確回憶發生的事情變得很困難(或者我壓根忘記寫下這條記錄)。
至今,我已經有 194 條記錄,平均每個月有一條新的記錄。最重要的是教訓部分。這裡需要自我反省。是什麼導致這個 bug 的特殊性?我發現經驗常常來自於三個不同的方面:
編碼。我在程式碼中犯了什麼錯誤?我是否忘記了 else 部分程式碼?是否系統呼叫失敗,但沒有檢查 response?我在未來應該怎麼調整程式碼來防止這些問題?
測試。一般不包括本可以在測試中捕獲的 bug 。要是這樣的話,應該在哪個階段測試——單元、功能、系統?丟失了哪個測試用例?
除錯。我本可以怎樣快速定位 bug?我是否使用了正確的方法?我是否假定了太多?我是否需要在程式碼中使用更好的日誌記錄?
益處
Nassim Nicholas Taleb 在 《Antifragile》中寫到:“錯誤包含豐富的資訊”。我完全同意這個觀點。Bug 幫助我們更好地理解系統,告訴我們怎樣提高編碼、測試和除錯技巧。所以我認為儘可能從 bug 中學習經驗,是再正常不過的事了。
我發現為每個有趣的 bug 記錄下來,讓我輕易學習到很多。在記錄的行為中我會對發生的事情思考得更深刻。同樣,一旦記錄下來,我可以在之後檢查發生的事情。偶爾,我也會瀏覽檔案,只閱讀教訓部分,對我認為是從 bug 中學到的最有價值的經驗加強記憶。
我記錄 bug 檔案至今已經有 13 年了。這是一段漫長的時間,但是我堅持下來了,因為作為一名程式設計師,它幫助我進步。嘗試一下吧,看看它是否也對你有益!
打賞支援我翻譯更多好文章,謝謝!
打賞譯者
打賞支援我翻譯更多好文章,謝謝!
任選一種支付方式