2014年3月2日 星期日

事情的另一面: 測試與除錯技巧

回顧過去數年的經驗,最大的體悟除《從需求出發理解背後技術的思考脈胳》以及《專注於滿足需求而非工具或方法》之外,大概就是沒有所謂的鐵則,許多事情都有另一面,端看你處於什麼情境、從什麼角度看它。

上份工作以 machine learning 為主 (2008 ~ 2011),針對一個特定的應用問題,實驗不同的演算法,看看能否實際運用在產品上。結果不如預期時,會陷入不知該懷疑演算法、演算法參數、還是程式寫錯的窘境。因此,對於程式正確的要求,遠高於其它產品。這樣才能專注地藉由實驗修正理論模型。在這個需求之下,Test Driven Development 就成了相當有用的開發方式。經過數次用 TDD 開發的經驗後,總算能夠達到和不用 TDD 差不多的開發效率。這意味著用了 TDD 也不會拖慢完成第一版的時間,並且程式更易於理解和維護。儼然是 TDD 的完全勝利!

但這個經驗有個代價。代價不是學習 TDD 的時間,畢竟學什麼工具或方法 (如OOP) 都要時間。學習 TDD 對立的結果是: 學不到除錯的技巧。

起初我以為除錯是果,治本的方式自然是避免有因。那麼,TDD 貌似完美的解法,學 TDD 即可。但是開發專案免不了團隊合作,也免不了使用第三方程式。換句話說,錯誤遍地都是,有時甚至是作業系統或編譯器的錯。拙劣的除錯技巧無法適應這個時代。我在和別人一起除錯時才發覺這事。對方能力很好,不過開發習慣不太好,常犯一些「我無法想像的錯誤」。但也因為他的習慣,讓他可以比較快看懂別人寫的亂糟糟的程式,可以比較快想到問題可能出錯的地方。我費了不少力氣才補足這塊,實在是始料未及的事。只能說,該走過的路還是得走,無法省去。

目前的工作以 C++ 為主,為了在大量的原始碼裡除錯,偶而會視需求加強一下 gdb 的使用技巧,還有練習寫 python script 簡化 gdb 操作程序。相對於兩年前,gdb 的操作技巧進步不少。另外也寫了 gj 幫助閱讀程式碼。但是在觀察同事的開發方式後,發覺我有時過於依賴工具的便利性,反而減少全面性的思考。最後還是得有系統地一步步思考、推論,才能有效率地解決問題 (關於這點,之後有適當材料再另寫文章說明「系統化的解決問題」)。換句話說,熟練除錯工具反而無意識地減少我系統性思考的時間,也滅少我系統性思考的經驗。我的意思並非捨棄除錯工具,像 Sherlock 那樣全部都在自己的思維宮殿裡解決 (雖說那樣實在是相當地帥啊!)。凡人如我等,還是需要工具輔助搜集情報和記錄訊息。只是這兩件事是相斥的,愈是熟練除錯工具,愈少思考;愈長思考,自然也愈不依賴除錯工具。

再回頭看 TDD,目前的工作絕大多數情況不適用 TDD。一來不像開發 machine learning 工具那樣,要求近乎100%正確。二來大部份的程式和 GUI 相關,本來就不容易測試。若要達到像以前一樣的開發效率,我得先熟悉 C++,再熟悉 C++ 基本的 unit test 工具,再熟悉和 GUI 相關的測試工具和知識。對照達到後帶來的好處,相當地不划算。不過其中一個和 GUI 無關但和網路高度相關的子專案,我很自然地用 Python + TDD 的方式開發核心部份。日後上線時,也從中獲得明確的回報: (1) 極少的錯誤。以及 (2) 透過單元測試輕鬆地重制線上偶而才會發生的網路錯誤,只更新一次程式碼就修正了問題。可參考《寫出容易測試的程式》了解類似的處境和用到的技巧。當然,我也因此「失去」一些線上除錯的經驗。

除了測試與除錯的心得外,軟體設計模式和軟體開發準則,也讓我經歷了幾次「打破鐵則」的心路歷程。之後再另寫文章補充。

回顧這些事,讓我明白個人經驗的侷限,而減少過度歸納和推衍的習慣。看別人的論述時,會多想想自己和對方的情境,從中得出目前我能用到的部份。不會過於尋找或遵從「聖杯」。白話來講,就是比較務實吧。