瞭解一些技巧助你減小代碼查錯時間。web
-- Maria Mckinley安全
在週五的下午三點鐘(爲何是這個時間?由於事情總會在週五下午三點鐘發生),你收到一條通知,客戶發現你的軟件出現一個錯誤。在有了初步的懷疑後,你聯繫運維,查看你的軟件日誌以瞭解發生了什麼,由於你記得收到過日誌已經搬家了的通知。網絡
結果這些日誌被轉移到了你獲取不到的地方,但它們正在導入到一個網頁應用中——因此到時候你能夠用這個漂亮的應用來檢索日誌,可是,這個應用如今還沒完成。這個應用預計會在幾天內完成。我知道,你以爲這徹底不切實際。然而並非,日誌或者日誌消息彷佛常常在錯誤的時間消失不見。在咱們開始查錯前,一個忠告:常常檢查你的日誌以確保它們還在你認爲它們應該在的地方,並記錄你認爲它們應該記的東西。當你不注意的時候,這些東西每每會發生使人驚訝的變化。框架
好的,你找到了日誌或者嘗試了呼叫運維人員,而客戶確實發現了一個錯誤。甚至你可能認爲你已經知道錯誤在哪兒。運維
你當即打開你認爲可能有問題的文件並開始查錯。函數
閱讀代碼,你甚至可能會想到該閱讀哪些部分。可是在開始搞亂你的代碼前,請重現致使錯誤的調用並把它變成一個測試。這將是一個集成測試,由於你可能還有其餘疑問,目前你還不能準確地知道問題在哪兒。工具
確保這個測試結果是失敗的。這很重要,由於有時你的測試不能重現失敗的調用,尤爲是你使用了能夠混淆測試的 web 或者其餘框架。不少東西可能被存儲在變量中,但遺憾的是,只經過觀察測試,你在測試裏調用的東西並不老是明顯可見的。當我嘗試着重現這個失敗的調用時,我並非說我要建立一個能夠經過的測試,可是,好吧,我確實是建立了一個測試,但我不認爲這特別不尋常。測試
從本身的錯誤中吸收教訓。搜索引擎
如今,你有了一個失敗的測試,或者多是一個帶有錯誤的測試,那麼是時候解決問題了。可是在你開幹以前,讓咱們先檢查下調用棧,由於這樣能夠更輕鬆地解決問題。編碼
調用棧包括你已經啓動但還沒有完成地全部任務。所以,好比你正在烤蛋糕並準備往麪糊里加麪粉,那你的調用棧將是:
你已經開始作蛋糕,開始打麪糊,而你如今正在加麪粉。往鍋底抹油不在這個列表中,由於你已經完成了,而作糖霜不在這個列表上由於你還沒開始作。
若是你對調用棧不清楚,我強烈建議你使用 Python Tutor ,它能幫你在執行代碼時觀察調用棧。
如今,若是你的 Python 程序出現了錯誤, Python 解釋器會幫你打印出當前調用棧。這意味着不管那一時刻程序在作什麼,很明顯錯誤發生在調用棧的底部。
在棧底你不只能看到發生了哪一個錯誤,並且一般能夠在調用棧的最後一行發現問題。若是棧底對你沒有幫助,而你的代碼尚未通過代碼分析,那麼使用代碼分析是很是有用的。我推薦 pylint 或者 flake8。一般狀況下,它會指出我一直忽略的錯誤的地方。
若是錯誤看起來很迷惑,你下一步行動多是用 Google 搜索它。若是你搜索的內容不包含你的代碼的相關信息,如變量名、文件等,那你將得到更好的搜索結果。若是你使用的是 Python 3(你應該使用它),那麼搜索內容包含 Python 3 是有幫助的,不然 Python 2 的解決方案每每會佔據大多數。
好久之前,開發者須要在沒有搜索引擎的幫助下解決問題。那是一段黑暗時光。充分利用你可使用的全部工具。
不幸的是,有時候問題發生在更早階段,但只有在調用棧底部執行的地方纔顯現出來。就像當蛋糕沒有膨脹時,忘記加發酵粉的事才被發現。
那就該檢查整個調用棧。問題更可能在你的代碼而不是 Python 標準庫或者第三方包,因此先檢查調用棧內你的代碼。另外,在你的代碼中放置斷點一般會更容易檢查代碼。在調用棧的代碼中放置斷點,而後看看周圍是否如你預期。
「可是,瑪麗,」我聽到你說,「若是我有一個調用棧,那這些都是有幫助的,但我只有一個失敗的測試。我該從哪裏開始?」
pdb,一個 Python 調試器。
找到你代碼裏會被這個調用命中的地方。你應該可以找到至少一個這樣的地方。在那裏打上一個 pdb 的斷點。
爲何不使用 print 語句呢?我曾經依賴於 print 語句。有時候,它們仍然很方便。但當我開始處理複雜的代碼庫,尤爲是有網絡調用的代碼庫,print 語句就變得太慢了。我最終在各類地方都加上了 print 語句,但我無法追蹤它們的位置和緣由,並且變得更復雜了。可是主要使用 pdb 還有一個更重要的緣由。假設你添加一條 print 語句去發現錯誤問題,並且 print 語句必須早於錯誤出現的地方。可是,看看你放 print 語句的函數,你不知道你的代碼是怎麼執行到那個位置的。查看代碼是尋找調用路徑的好方法,但看你之前寫的代碼是恐怖的。是的,我會用 grep 處理個人代碼庫以尋找調用函數的地方,但這會變得乏味,並且搜索一個通用函數時並不能縮小搜索範圍。pdb 就變得很是有用。
你遵循個人建議,打上 pdb 斷點並運行你的測試。然而測試再次失敗,可是沒有任何一個斷點被命中。留着你的斷點,並運行測試套件中一個同這個失敗的測試很是類似的測試。若是你有個不錯的測試套件,你應該可以找到一個這樣的測試。它會命中了你認爲你的失敗測試應該命中的代碼。運行這個測試,而後當它運行到你的斷點,按下 w 並檢查調用棧。若是你不知道如何查看由於其餘調用而變得混亂的調用棧,那麼在調用棧的中間找到屬於你的代碼,並在堆棧中該代碼的上一行放置一個斷點。再試一次新的測試。若是仍然沒命中斷點,那麼繼續,向上追蹤調用棧並找出你的調用在哪裏脫軌了。若是你一直沒有命中斷點,最後到了追蹤的頂部,那麼恭喜你,你發現了問題:你的應用程序名稱拼寫錯了。
沒有經驗,小白,一點都沒有經驗。
若是你仍以爲迷惑,在你稍微改變了一些的地方嘗試新的測試。你能讓新的測試跑起來麼?有什麼是不一樣的呢?有什麼是相同的呢?嘗試改變一下別的東西。當你有了你的測試,以及可能也還有其它的測試,那就能夠開始安全地修改代碼了,肯定是否能夠縮小問題範圍。記得從一個新提交開始解決問題,以便於能夠輕鬆地撤銷無效地更改。(這就是版本控制,若是你沒有使用過版本控制,這將會改變你的生活。好吧,可能它只是讓編碼更容易。查閱「 版本控制可視指南 」,以瞭解更多。)
儘管如此,當它再也不感受起來像一個有趣的挑戰或者遊戲而開始變得使人沮喪時,你最好的舉措是脫離這個問題。休息一下。我強烈建議你去散步並嘗試考慮別的事情。
當你回來了,若是你沒有忽然受到啓發,那就把你關於這個問題所知的每個點信息寫下來。這應該包括:
有時這裏有不少信息,但相信我,從零碎中挖掘信息是很煩人。因此儘可能簡潔,可是要完整。
我常常發現寫下全部信息可以啓迪我想到還沒嘗試過的東西。固然,有時候我在點擊求助郵件(或表單)的提交按鈕後馬上意識到問題是是什麼。不管如何,當你在寫下全部東西仍一無所得時,那就試試向他人發郵件求助。首先是你的同事或者其餘參與你的項目的人,而後是該項目的郵件列表。不要懼怕向人求助。大多數人都是友善和樂於助人的,我發如今 Python 社區裏尤爲如此。