高效程式設計

無名_四葉草發表於2020-04-05

轉自:http://www.kuqin.com/pragmatic/20081103/25470.html

1. 使用工具幫你找 Bug,而不是人工找.

  工具包括用單元測試,assert語句,程式碼測試容器. 人工指用 print 和 debugger 一行一行跟蹤. 我們知道,程式設計中絕大部分時間是耗費在除 bug 上. 不同的人有不同的 debug 的方法. 我個人比較喜歡“極限程式設計(XP)” 學派的主義,也就是說,程式碼未動,測試先行.

  單元測試中的紅棒綠棒(熟悉 JUnit 的讀者知道我在說什麼)一出現,哪裡出了問題就一目瞭然. 單元測試的另外一個好處在於增加寫程式的自信. 以前沒用單元測試之前,每天晚上改程式碼改到很晚的時候腦子常常不靈活,把程式碼改錯,然後第二天來還要重頭弄. 有了單元測試之後每天晚上保證測試全部過掉,這樣心理踏實,睡覺也香,早晨也不忙,吃飯也棒.

  一般的語言都有 assert,但是很少有人用. 其實 assert 是一個非常好的DEBUG 工具,C 的 assert 能夠把哪一個檔案哪一行出了錯都告訴你. 不過我一般會自己寫一個這樣的 assert 巨集:

  #define ASSERT(value,msg) if (!(value)) {fprinft(stderr,"At file %s,line %d: message: %s ",__FILE__,__LINE__,msg); exit(-1);}

  這樣的 ASSERT 可以帶一個資訊出來,比起原來只告訴你哪個檔案哪一行更加有價值.

  第三個是用容器幫你找 Bug. 這一點以 C/C++ 程式最為突出,因為編譯之後直接就是可執行程式碼,執行時的資訊不像 Java 和 Python 這樣有 VM 的語言容易得到. 這時候,我推薦 valgrind. 這個工具能夠把 C/C++ 程式放到一個容器中執行,記下每一個記憶體訪問. 被這樣的容器 debug 一下,基本上指標指飛了 (Segmentation Fault) 的情況幾乎就沒有了. 想像一下是用 GDB 追蹤非法指標和記憶體洩露方便,還是用容器告訴你哪一個指標非法,哪一個記憶體沒釋放方便 :)

  2. 選用自動化工具構建

  用 gcc 或者簡單的 IDE 來編譯和執行程式在程式設計初期是很快速的,可是越到後來,會越臃腫. 在編譯的時候,不同的引數,不同的目標,在 IDE/gcc 裡面每次都要設定. 而且一般的 IDE 也不能做到自動解決依賴等高階方法. 因此,最好的方法是用 Ant 或者 Makefile 管理專案. 這方面教程很多,而且我估計程式設計的個個都知道. 不管專案大小,注意頻繁使用就是了.

  自動化測試也有很多工具,特別是 GUI 和命令列測試的自動化,工具鏈都很完整. 大公司裡的程式設計師走這方面的流程都比較規範(我在西門子實習過),但是小一點的公司中,或者個人搞小專案的時候,就不一定想得起來了(大部分我見到的程式設計師就手工來測試). 手工測試看上去快,但是要是積累的次數多了就比較浪費時間了. 其實自動化測試工具的學習成本很低的,事半功倍.

  3. 買本小書做參考,而不是用 Google.

  這是大實話. 我大三開始學 Python 的時候,語言特性並不熟悉,手頭也沒有書,因此常常連取個隨機數都要上 Google 查一下庫. 我發現,不管網路多快,自己搜尋技術多牛,還是沒有手頭一本書方便. 後來列印了一個7頁的標準庫的 cheatsheet,程式設計立即行雲流水. 我在實習的時候也觀察到,大部分時候程式設計師不可能記住一個框架所有的API,所以他們要不等 IDE 幾秒鐘做程式碼補全,要不一邊翻文件一邊做. 或許MSDN 這些本地文件系統比查書快吧,但是用 Google 和網路搜尋絕對比書慢. 現在因為工作原因,常常要學一些新的語言,我做的第一件事情,就是把他的庫介面的網頁全部列印了下來.

  4. 用指令碼語言開發原型

  人月神話的作者 Brooks 說: 準備把第一版扔掉,因為第一版必然要被扔掉. 這是大實話和真理. 既然第一版要被扔掉,我們們就讓第一版扔掉得越早越好. 說白了就是,原型要快速的被開發.

  所謂的快速原型開發,大致有兩個捷徑,第一是隻做核心的功能,輸入輸出都是構造好的簡單的例子. 第二是隻做最簡單的情況,對於效能和健壯性什麼的都不太考慮. 這兩點,恰好是指令碼語言最擅長的. 指令碼語言擅長於用精簡的幾行構造出複雜的功能,並且語法很鬆散,潛在假設程式是正確的.

  即使在程式碼編寫階段,一些功能的實現,也是要先寫個簡單的,再慢慢打磨成複雜的. 指令碼語言此時依然有用. 比如我在用 Java 的時候,常常不確定一個函式返回的物件究竟某個屬性是什麼樣的值. 這時候我就會用 Java 的 bsh 指令碼寫一行列印,而不會寫一個複雜的 out.println 再編譯再執行再把那行刪除掉. 當然,這幾年很流行動態語言,原型和產品之間的差距已經變得很小了.

  5. 必要的時候,程式要使用清晰的,自我解釋的文字檔案作為日誌輸出.

  不知道各位除錯程式的時候是不是和我一樣,看到不確定的和要跟蹤的變數就直接插入一行 print. 我以前一直這樣做,但是頻繁的插入這樣的列印會使得螢幕的輸出很亂,不知道哪行是什麼意思. 一個更加好的辦法是寫一個日誌函式,可以分也可以不分優先順序,總之保證 Debug 的時候的輸出以一種統一的,可管理的方式出現. 這樣,在最後釋出穩定版本的時候,只需要簡單的幾行命令就可以從程式碼中剔除所有的日誌列印行.

  如果必然要輸出日誌,最好要分配一個單獨的命令列引數,用來控制程式究竟輸出不輸出日誌,輸出哪些日誌. 一開始看上去這個是費時費力,越到後來日誌越多的時候,就體會到方便之處: 有時候你只想要某一類日誌,可是其他的記錄偏偏來搗亂. 多加一個引數可以使得程式更加靈活,根本不需要去修改程式碼或者條件編譯就能得到不同級別的程式日誌.

  日誌和程式的輸出結果一定要清晰且能自我解釋,否則不如沒有日誌. 我切身經歷是這樣的: 幾個月前,我一個程式跑了大約一天,最後輸出了很大的日誌和結果. 但是很不幸的是,結果裡只有數字,沒有任何說明. 我自己都忘了每一行是什麼意思. 而且更加麻煩的是程式的輸出藏在重重判斷和迴圈之內,使得根本沒有辦法分析這一行輸出對應的輸入是什麼. 於是,最終只能再次浪費一天的時間讓程式再跑一次. 經過這次教訓,我的程式日誌和結果中插入了不少讓人可讀的內容. 這樣,即使程式丟失了,結果還是能夠被人解讀的.

  更多的關於資料和程式結果要能自我解釋的精彩論述,可參見 More Programming Pearls 第四章.

  6. 使用命令列小工具操控分析你的結果和程式碼,而不是用自己的眼睛和手.

  我發現,人有一個固有的習慣,就是喜歡自己去”人工”,而不喜歡用工具. 因為人工讓人感覺工作更加刻苦,更加快,更加有控制感. 比如說吧,上面我說的測試,我就不只一次見到為了測一個互動式的命令列,一個程式設計師寧願老是每次打相同的三個命令,而不願意用一個簡單的 expect. 再比如說,面對長長的日誌檔案,我見到很多人都是用文字編輯器直接開啟,用滑鼠滾輪一行一行的往下翻,而不是使用 grep. 包括看網頁,很多人從來不用查詢功能,而是一行一行的往下瞄. 包括打遊戲也是,好的UI指令碼(不是外掛)一大把,可是玩 WoW 的人很少用,都喜歡自己重複點滑鼠.

  別看上面說的這些好像程式設計師沒有,其實我們常常陷入這個誤區. 舉個簡單的例子,一個 python 程式裡面有十幾個 print 函式,我們想把這些列印全部滅掉,一般人會開啟檔案慢慢瞄,稍微高階一點的用查詢,找到了,用快捷鍵刪掉整行. 其實最好的方法根本都不要編輯器,應該用 grep -v. 或者 sed,但是這樣的方法極少會有人用的. 我也是強迫自己無窮多次之後,才漸漸的用這套快速的方法.

  7. 程式能跑就是萬歲. 除非萬不得已,儘量不要在效能上優化你的程式碼

  Knuth 名言: Premature optimization is the root of all evil. (提前優化是萬惡之源). 一般我們寫程式碼的時候,不知不覺的就會覺得,哎呀,這樣寫效率不高,我要構造一個資料結構啥啥. 隨機訪問一定要雜湊表,排序一定上快排,查詢一定要二分,強連通分量一定要用 Tarjan 演算法,動規一定比窮舉好等等,這些競賽的時候極限情況下正確的論斷其實在實際環境中並不重要,因為做程式設計的一開始關鍵是能跑,而不是跑得快. 往往這麼以優化,程式很難 debug,倒是還要去翻演算法導論和TAoCP 看人家的二分怎麼寫的等等.

  在程式能跑的情況下,優化也要特別小心. 我曾經有一個程式,大約有 90% 的運算是查表,只有 1% 的是乘法,另外是一些判斷和把插到的結果插入到一個集合中. 我的查表是用的最土的 list.index. 按照正常的想法,應該把這個優化成雜湊表. 而實際上我用 profile 工具一看,才知道,原來是插入到一個集合的操作費時間,因為每次都需要 extend,涉及到很多記憶體分配的操作. 我做過非常多的 profile 測試,沒有一次不出乎我預料的. 程式執行時間總是在自己不認為浪費的地方被浪費掉. 因此,就算萬不得已優化,也務必要先做一下 profiling. 我喜歡 python 的地方就在於,他的 profiling 只需要一行語句就完成了,而且結果具體乾淨. 其他的語言,至今沒見到這麼簡單的 profiling 工具.

  另外: 用兩個或者大於兩個顯示器. 不要用或者少用滑鼠.


相關文章