本節書摘來自華章出版社《effective debugging:軟體和系統調試的66個有效方法》一書中的第1章,第1.3節,作[希]迪歐米迪斯·斯賓奈裡斯(diomidis spinellis),更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視
修理電子裝置的時候,我們首先要檢查供電是否正常,也就是檢查電流有沒有從電源子產品正确地流入該裝置的電路中。在很多情況下,這項檢查都能幫助我們找出問題。計算機程式與之類似,很多問題也可以通過對例程的入口點(entry point)與出口(exit)進行檢查而得以确定。入口點就是前置條件(precondition),它指的是程式在即将執行例程時所具備的狀态,以及傳遞給該例程的輸入值,出口則是後置條件(postcondition),它指的是程式執行完例程之後的狀态及其傳回值。如果前置條件得不到滿足,那說明用來設定這些前置條件的代碼裡面有錯誤,若是後置條件得不到滿足,則說明該例程本身有問題。如果兩者都正确,那麼應該轉向其他地方去尋找bug。
我們可以在例程開始的地方、調用例程的地方或關鍵算法開始執行的地方設定斷點(參見第30條)。為了判斷前置條件是否得到滿足,我們應該仔細檢查算法的參數,包括傳入的參數值,調用方法時所針對的對象,以及可疑代碼所使用的全局狀态。尤其要注意以下幾點:
找出那些本來不應為null,但實際上卻為null的值。
調用數學函數的時候,確定傳入的值位于該函數的定義域之内,例如,調用log函數時傳入的值應該大于0。
檢視對象、結構體與數組的内部細節,確定其内容符合要求。這也可以幫你查出無效的指針。
檢查變量的取值是否在合理範圍之内。如果變量具有6.89851e-308或61007410這樣的可疑取值,那通常表明它還沒有初始化。
檢查傳給例程的資料結構是否正确,例如,map有沒有包含預期的鍵與值,雙向連結清單(doubly linked list)能不能正确地周遊。
然後,我們應該在例程結束的地方、調用完例程的地方或關鍵算法執行完畢的地方設定斷點,以判斷該例程的執行效果是否正确:
計算出來的結果看上去合理嗎?有沒有處在預期的範圍之内?
如果結果合理,而且位于預期的範圍之内,那麼實際的值是否正确?我們可以通過手算來演練相應的代碼,以驗證計算機的執行結果是否正确(參見第38條),也可以将執行結果與已知的正确值相對比,或是采用其他工具或方法來進行驗算。
例程的副作用是否符合預期?可疑代碼所接觸到的其他資料是否遭到破壞或擁有了不正确的取值?有些算法在周遊資料結構時,會把一些維護其工作所用的資訊記錄在資料結構中,對于這些算法來說,尤其應該進行這樣的檢查。
算法所獲得的資源,如檔案句柄及鎖,有沒有正确地釋放?
同樣的方法也可以用在更為高層的操作與配置環境中。例如,如果要驗證sql語句是否正确地建構了某張表格,我們可以檢視它所掃描的那些表格及視圖,并且看看它建構出來的那張表格是什麼樣子。如果要判斷基于檔案的處理流程是否正确,我們可以檢查其輸入檔案與輸出檔案。如果要調試某個建構在web服務上面的操作,我們可以檢查其中每項web服務的輸入與輸出。如果要排解整個資料中心的故障,我們可以檢查其中每個元素所需要的及所提供的機制是否正确,其中包括網絡連接配接、dns、共享存儲、資料庫以及中間件等。在這些情況下,我們都必須親自驗證(verify),而不能想當然地接受假設(assume)。
要點
仔細檢查例程的前置條件與後置條件。