除錯是程式人在撰寫程式之外的主要工作,對於開發的生產力而言,其重要性不亞於撰寫程式碼,可以說是程式人另一個重要的能力。

優秀的程式人除了能寫下品質優良的程式碼外,對於發掘程式中的臭蟲並且加以解決,也很有一套。而除錯需要憑藉著一套系統性的方法,倘若只是隨機憑藉靈感,想要找到程式的臭蟲,往往是曠日廢時。

除錯的第一步,是找出可以重新複製(Reproduce)臭蟲的方式。這個動作很重要,因為它能幫助我們先確立日後反覆測試的基礎。在除錯的過程中,總是會需要不斷地執行程式,使系統能夠重複發生錯誤,你才能反覆從中收集到造成錯誤的各項資訊,包括程式的執行路徑、在執行路徑中每一點的狀態等。

倘若你無法反覆、確切地重現錯誤,那麼收集這些資訊的難度就會提高很多,而這些資訊便是據以研判錯誤究竟位在何處、為何為發生的線索。倘若線索難得,那麼自然難以找出錯誤。

找出重新複製臭蟲的方式,也有助於我們的推論,因為在特定的輸入條件、特定的程式執行步驟才會發生錯誤,意謂著錯誤的發生原因,和這些輸入條件及程式執行步驟有關。

因此,找出有效(夠精簡)的臭蟲複製方式,是除錯成功的重要第一步。
想要找到發生錯誤的原因,得先觀察它是如何發生的
找到重新複製臭蟲的方式之後,便可以繼續下一步──找到發生錯誤的原因。想要找到發生錯誤的原因,得先觀察到錯誤是如何發生的。觀察的方式主要有兩種,第一種是觀察程式執行的路徑,第二種則是觀察程式執行過程中的狀態。

那麼,除錯者該如何做觀察呢?大多數的除錯者會利用除錯器(Debugger)。除錯器的主要作用,是讓除錯者得以在執行程式的過程中設立中斷點(Breakpoints),並且能夠觀察變數的值。

像GNU大名鼎鼎的除錯器gdb,便是一款可以在命令列模式下操作、功能強大的除錯器。不過,或許更多的程式人過去熟悉在整合開發環境(IDE)下除錯。

絕大多數的整合開發環境都內含了除錯器,這使得程式人可以更輕易地在除錯器裡整合、搭配其他的資訊一同除錯,例如更輕易地知道程式執行的位置、所觀察的每個變數的現值等。

無論是命令列形式,或整合開發環境中的除錯器,都能為除錯者找出錯誤的所在,提供很大的幫助。除錯器提供的功能很多,但我們假設主要會運用到設定中斷點及觀察變數值這兩個功能,並且來看看應如何利用除錯器。

當你能夠反覆地重現錯誤時,接著當然是要找到造成錯誤的所在,這是我們稱之為追蹤(Trace)的動作。而所謂追蹤程式碼,便是依據可以重新複製臭蟲的方式,讓程式持續往前走,並且在必要的地點觀察特定的變數。

這樣的動作想確定的是,程式的執行路徑是否符合預期,以及在特定的地點上,程式的狀態是否符合預期。基本上,追蹤程式碼就是一種比對程式實際執行行為,和設計者的預期結果之間是否有所不同的動作。

程式的執行路徑和你假想的不同,那麼必然是有某個地方出了錯,當你發現程式的執行路徑在某一點之後和設想的不同,那麼便可確定,應該以這一點做為一個基準點,再往前回溯尋找造成執行路徑錯誤的原因,因為這很有可能就是最終錯誤發生的原因。

執行路徑都已經和預期的不同了,程式當然是以非預期的行為在運作著,又怎麼能期待最後得到的結果正確呢?例如你假設某一個函式應該會被呼叫到,但它始終沒有,那麼造成這段程式碼未被執行的原因,就很有可能是造成錯誤的原因。

程式執行路徑的錯誤,大多數是來自於程式碼所計算、取得的變數(也就是程式狀態)不正確。因為執行路徑的改變,是程式碼的分支動作造成的(像C語言中的if-else、switch之類的指令),而分支動作在進行時的判斷依據,通常便是特定的變數值,這是我們之所以要利用除錯器觀察變數的原因之一。

有時程式執行路徑都正確,但程式的執行結果還是不對,可能這便是程式中運用到的變數值不正確的關係。你必須觀察程式中的變數值,從何時開始和預期不同。能夠找到程式偏離常軌的地點,就幾乎可以找到發生錯誤的所在。

除錯是反覆提出假設,接著驗證假設是否成立的過程
觀念上來說,我們在追蹤程式碼除錯時,便是持續的檢驗程式的執行路徑和變數值是否合乎預期。這個道理說起來簡單,執行起來卻時常會有難度。之所以會有困難,在於程式碼的範圍可能很大,即使我們能重新複製出錯誤,把可能的執行路徑減少成一條,但這條路徑上所通過的程式行,或甚至是函式個數都有可能很多,想要逐一檢視每條程式行執行後的結果,將會十分耗費心力。因此,有策略、技巧性地設置你要檢查的點,便是縮短除錯時間的要點之一。

除錯是一個反覆提出假設,接著驗證假設是否成立的過程。當程式碼的規模夠大時,你無法像大海撈針般,遍設中斷點於程式碼中,你必須縮小要檢視的範圍。

除錯時所謂的假設,就是你對問題根本原因的猜想。例如,你猜想某個臭蟲是某個原因所導致的,那麼你便可以針對和該原因有關的程式碼設定中斷點,並且透過觀察變數之值來檢查你的假設是否成立。例如,你假設是因為作業系統的關係(因為根據測試結果,必須在特定的作業系統上問題才會發生),導致某個硬體無法正常驅動,那麼你可以在初始化該硬體的程式碼周圍設定中斷點,檢查初始化動作的回傳值是否正確。

在實際執行後,便可以驗證你的猜想是否正確,倘若正確,那麼便可以著手擬定修正的方式。倘若猜測錯誤,那麼便得試著再提出下一個假設,重新進行驗證的動作。

通常,有經驗的除錯者提出假設的命中率較高,因為同樣類型的錯誤可能已經發生過許多次了,因此,當一個似曾相識的錯誤情況再度出現時,便可以很快地推論最有可能的原因,接著透過除錯器驗證假設。

準確的假設是除錯的重要環節
要怎麼做出更準確的假設呢?除了過去同類型的錯誤經驗之外,你可以依據造成錯誤的組態環境,以及重新複製錯誤的方式,幫助推論錯誤的原因。例如,程式僅在Windows Vista環境才會出錯,在Windows XP卻沒有問題,那麼便可以往Windows Vista獨有的組態或特性去推論。

像是DirectShow應用程式想要播放mms的串流,會利用到一個名為NetShow的source filter,此filter在Windows XP上是內建,但在Windows Vista上沒有提供,因此,利用到此source filter的程式在Windows Vista上便會無法正常運作。當你的播放程式在Windows Vista上播放串流遭遇問題時,便有可能是這個原因所導致的。一旦建立了這個假設,便可以很快在對應的程式碼處設置中斷點,並且檢驗假設是否成立。

專欄作者

熱門新聞

Advertisement