許多同學經常把一個程序直接貼給我說:
老師,我的程序出錯了,幫我看看吧。
對於這些同學的請求,我往往只能委婉的拒絕:
調試要靠自己哦~ 我幫你指出程序的錯誤,對於你自己一點幫助都沒有。你今天先繼續努力debug,明天要是還不能解決,我就幫你看看。
雖然大部分的人最終都能靠自己找到錯誤,但是學會好的調試技巧,確實能夠起到事半功倍的刷題效果。
於是我總結了四種我自己常用的在刷題過程中的調試技巧,希望可以幫助到大家:
技巧1: 打印中間結果
依賴於IDE的斷點調試,是十分浪費時間的一種調試方法。而且在面試中,你是基本沒有斷點調試的機會的(因為很多公司是白板寫題,不會提供給你IDE)。
事實上在實際的工作中,你也很少能夠有機會去進行斷點調試,比如你進行的是 Web 開發,你只能想方設法的在代碼中打印一些 Log,然後根據 Log 去分析出錯的原因。你平時用 IDE 寫代碼,就十分容易養成這種斷點調試的“壞習慣”。一個更好的方式,是使用打印中間結果的方式。以 LintCode 上的 Clone Graph 為例子:
LintCode 支持自己出測試數據進行測試的模式,點到“Testcase” 這個 tab之後,輸入測試數據,點 Run Code。你的程序中的輸出也會被顯示在 Output 中。我們看到 代碼中 第22行到27行,是在打印一箇中間結果,這個中間結果就是在使用了 bfs 算法之後,從一個點出發,找到的所有的圖中其他節點。這樣我們就可以迅速驗證,代碼出錯到底是因為 BFS 寫錯了,還是因為其他的部分寫錯了。
使用這種調試方法的另外一個好處是,你很自然的希望你的程序可以“分階段”輸出一些中間結果,這種分階段處理的方式,也是我們在課上經常強調的,把大問題變成小問題,然後逐個擊破的編程思想。養成這種編程習慣之後,可以非常有效的幫助你在寫程序的時候,就避免掉錯誤。
技巧2: 一行一行改成參考程序
在 九章的官網 中有 LC 完整的參考程序庫,其中 Java 程序的一部分代碼是我自己親自編寫的,具備較高的質量和參考價值。很多題目也給了多種不同的解法。比如 Binary Tree Level Order Traversal 這一題,給出了三種 BFS 的參考程序和 1個 DFS的參考程序。通常來說很多題目,你不僅僅需要掌握其中一種方法,而是需要掌握這個題所有的解決辦法。
有不少同學經常會來問我:
老師,我的代碼和參考程序一樣啊,為什麼還是不對?
這種時候,我通常建議他們,將自己的代碼,一行一行的改成參考程序。這樣,當他們發現哪一行代碼不一樣的時候,就自然定位到了錯誤。
舉一個例子,就以 Binary Tree Level Order Traversal 為例,寬度優先搜索算法(BFS)是面試必須掌握的一種算法。來看看下面這個有錯誤的代碼(請花3分鐘閱讀一下,並找出錯誤所在):
從程序思想上來看,好像並沒有什麼錯。很難直接發現有什麼問題。與參考程序逐行對比之後會發現,錯誤發生在第14行。
參考程序中,寫法是:
和上面的程序寫法是:
這兩種寫法存在巨大區別,原因是在循環體內部,我們會不斷往 queue 中放東西,這導致了queue.size() 的值是不斷變化的。而沒有起到我們只想循環當前這一層所有點的功能。
通過對這個程序逐行對比,我們能夠馬上學會如下的知識點:
- for循環中間的判斷語句,在循環體的每一次循環中都會被重複執行,而不是在一開始就確定好執行多少次。
- 為了做到BFS的分層遍歷,需要先把 size 取出來,再 for循環這個size,這是BFS分層遍歷中最關鍵的一句話。
技巧3: 給小熊講程序
這個技巧,是我曾經參加高中生算法競賽的時候,一位算法競賽國家隊的前輩教我的。
當時我的算法水平可能已經還不錯了,但是代碼質量很差,很容易寫出有bug的代碼。這位前輩告訴我,可以放一隻 “小熊” 在你的電腦旁邊,一旦程序出錯了。就對著這隻小熊講你的整個代碼是怎麼解決問題的。
因為是給小熊講,所以你可以把它當作什麼都不懂,於是需要更加仔細的去講述你代碼中每個細節,所以你需要一行一行的講,甚至連為什麼你要用 ArrayList 而不用 int[] 都要講得清清楚楚。講著講著,你就有可能突然發現你的錯誤所在了。
這個技巧的意義在於,你在寫代碼的時候,腦子裡可能想著一個事情,但是打出來的代碼,可能是另外一回事兒。或者你腦子裡想著有3個條件需要判斷,但是打的時候,漏掉了一個。當你把代碼重新講一遍的時候,事實上是在重新整理你的邏輯,查漏補缺。這樣很容易就能夠發現你的錯誤。
技巧4: 必殺,重寫一遍
大部分的bug,其實都是 typo。比如:
- 大於符號和小於符號打反了
- <= 寫成了 <
- 本來要寫一句話幹什麼來著但是後來寫著寫著忘記了
- if 後面忘了加 else
- for 循環後面忘了加括號,導致代碼跑到了循環體之外
- 變量名打錯:for(int j=0;i
諸如此類,不勝繁舉。重新寫一遍代碼, 往往都能 fix 這些問題。
尾聲
調試是在你出現 BUG 的時候,才需要做的事情。大家平時應該儘量去練習的是不要寫出有BUG的代碼。
這的確很難,但是你應該以此為訓練目標,在你在 LintCode 上點擊 “Submit” 之前,儘量的做到已經檢查過自己的代碼,不要過分依賴於 Online Judge幫你判斷你的代碼是否有錯,更加不要依賴於本地 IDE 的斷點調試功能。在 LintCode 這個界面(登陸後可見),可以看到自己提交的指標:
這個指標非常的重要,他是判斷你的代碼是否 Bug Free 的重要參考數據。如果你最近一個月內的這個數據在 3 以內,那麼說明你的代碼具備非常高的質量,不太容易出bug。如果在3-4之間,說明,還可以有所提高。如果在4以上,說明,你的代碼質量比較糟糕了。
九章算法 ,國內&硅谷一線工程師在線直播授課,已經幫助30000+人成功拿到心儀offer。