2009年9月5日 星期六

驗證第一、想法第二

看到這篇《The Only Truly Failed Project》,其中有兩段話寫得很棒:

Failure is a wonderful teacher. But there’s no need to seek out failure. It will find you.

The only truly failed project is the one where you didn’t learn anything along the way.

最近觀察不少數據和實驗結果,推翻之前許多猜想,不過我沒有像碩士做研究時那般痛苦,反而有些興奮。大概是因為沒有發論文的壓力吧,加上先前讀過費曼的言論,覺得這是理所當然的情況,我們知道得本來就很少,想法通常也是錯的。然而,重點在於如何從錯誤的嘗試中學習?

昨天晚上不斷思索這堆錯誤的嘗試告訴我什麼,仍未有定論。忽然想到愛迪生的話,當他實驗千種材料失敗後,他說,至少我明白這千種材料不能作為燈炮的材料。
於是心裡又更踏實了。但這不表示可以隨意的嘗試,在沒有有效的驗證方式之前,無謂的嘗試無法提供任何訊息。

印象中費曼提過,他不和人討論無法驗證的想法,那是沒有意義的行為。提出猜想後,我會思考如何驗證。直到有辦法進行驗證前,我會先保留想法,改試別的作法。或是將原問題先拆成幾個小問題,讓小問題能夠被驗證。待各個擊破搜集到一些資訊後,再回頭看是否能驗證原本的想法。

能夠驗證的想法,才能從中學習。不然結果出來後,我們無從判斷結果有多接近目標,自然也無法從中學習,進而做修正。舉例來說,如果要做 AI 下棋的搜尋,要怎麼知道目前的策略有效?和人下棋是個辦法,可是沒有無法頻覆進行,也就不能確保方法的可靠程度。另一方面,光看評估盤面函式的輸出值也無用,沒法確認分數高確實是有利的盤面。

換個想法,提兩個策略,互相對戰。至少可以確定每次都贏的策略,是相對來說較好的策略。一個策略可以用 greedy algorithm,另一個用自己想的特別方式。雖然我們預期特別的方法會贏,但若結果相反,也能從中明白新東西:像是 greedy algorithm 比想像中的有效,可從中找到好點子;或是特別的方法不如預期地有效,也就不用再和人下棋,減少後續測試的成本。若擔心兩個方法自動對戰變化有限,可以引用不同棋譜的中盤局面,再引入兩個方法擔任不同角色,觀察好的方法是否在各種相同局面裡,無論擔任那一方,都能下得較好。

再舉解魔術方塊的例子。為了能夠驗證,可以先將解好的盤面一步步弄亂,記下正確的還原步驟和所需步數。準備好多組測試盤面(即最後弄亂的樣子),測試演算法解的效果,步數和預期步數相差多少?那一步發展變得不同?於是有確實的數據可以分析,明白問題出在那。有未知的大進步時(只花了預期的一半步數),也方便觀察出原因(如弄亂盤面時做了不當的重覆操作),不會不小心高估方法的成效。

看起來理所當然的事,沒想到我這麼遲才明白,驗證是如此的重要。而這個觀點卻是從 coding 那邊先萌芽,才接著在研究這邊確實實行。不論是研究還是coding,我認為重要性是: 需求 (動機) > 驗證 > 想法 (解法) > 實作。如《管理是什麼》書裡提到,管理即為客戶創造價值 -- 相當含糊的定義,可是卻非常精闢。不論是研究還是寫程式,也要先確保能滿足某種需求,之後的發展才有意義。接著,在天馬行空地想解法前,先確保有方法驗證方法的好壞、達到目標的程度、有辦法分析結果,之後才能確實地落實想法。於是,即使失敗仍能有所收獲。

2009-09-06 Update

LCamel 一提,發現英文的用詞很有趣,Verification and Validation 裡這麼解釋:

It is sometimes said that validation can be expressed by the query “Are you building the right thing?” and verification by “Are you building the thing right?” “Building the right thing” refers back to the user’s needs, while “building it right” checks that the specifications be correctly implemented by the system.

意即:

  • 滿足需求 = validation = do the right thing.
  • 驗證作法 = verification = do the thing right.

語言真是奇妙啊,用一句話來總結,就是「Are the do you right thing right?」

2009年8月10日 星期一

《別鬧了,費曼先生》閱讀中的雜記

讀《別鬧了,費曼先生》的過程裡,偶而有寫片段感想。這篇是這些短記整理後的記錄。

通用的學習法則

《別鬧了,費曼先生》提到費曼常跨領域學些不同東西,不管是哲學、數學、生物學,費曼都用他學習科學的習慣判別對錯,掌握基本原理。最近做研究也有這種體會。即使沒有背景知識,有良好邏輯,掌握假設和觀察結果,通常能做出正確的判斷。前提是我們能明確判讀那些是客觀事實,那些是主觀判斷,數據不會騙人,可是人會,而且我們自己也可能會騙自己。

舉例來說,看到別人的實驗結果和實驗報告,一定要自己讀一遍數據,不然無法判斷別人說明中那些部份為主觀推測。像是兩個方法有效程度差了千分之一,是否可說有顯著差異?去除人有私心的因素外,我們也沒有想像中的聰明,容易將一些相似的資訊混在一起,不知不覺認定是事實。「誤把有相關性視為有因果關係」是常見的例子,有興趣的人可以看些寫給大眾看的統計書籍。反用「判讀個人觀點和事實」的原則在自己身上也很有效,可以找到自己思路的盲點,不會過於武斷主張自己的看法。

此外,舉例說明是最佳的討論方式。費曼提到他和數學家討論數學原理時,他的判斷方式是心中想像一些實例,對方描述問題時,他就想像心裡那些物體會怎麼變化,於是當對方說某某性質如何,他發覺在他想像世界裡有矛盾時,就能做出辯駁。另一方面,我相信只有當我們能舉例說明時,我們才算真的明白,藉由逼自己舉例,可以思考的更靈活,找出忽略的細節。

別死記名詞

另一方面,費曼也指出許多人記了一堆名詞,卻沒掌握基本精神,他在 MIT 的大學同學如此,愛因斯坦的研究助理也會犯這種錯。費曼一再提到很多學生只會背名詞,不會理解背後的含意,不明白他們在學什麼。這種精神從他小時候就存在,和父親在林中散步時,費曼的父親向他解釋生物行為而不背名字,費曼將自己的成就歸功於父親童年時的教導,不無道理。

費曼在問學生問題時,會舉例討論,而不問教科書式的問題。我在確認別人能力時也是這麼做,不問專有名詞解釋(像是請說明 X 演算法為何),盡量用應用題直接看對方怎麼運用知識,這樣能立即明白對方有沒有理解背後含意。反過來看,不知道某些名詞不代表什麼,就算對方不知道 Dijkstra’s Algorithm,但若能提出相似的解法,反而更高明。只要和對方說明這些知識,他們立即能掌握精神加以運用。反之,知道 Dijkstra’s Algorithm 卻無法活用,知道更多演算法、累積更多經驗,成長也有限。

名詞只是方便溝通的工具,若不能理解背後的含意,光記許多名詞毫無意義。在資訊爆炸的時代,每天產生一堆新的名詞,吸收這堆雜亂的詞彙時,得時時提醒自己,是否明白背後含意。以前我曾落入背誦知識為樂的陷阱,後來發現知道很多皮毛卻無法做進一步運用,才驚覺自己浪費不少時間。舉例來說,現在當紅的 Android,即使我知道 Android 是 Google 發佈的手機平台,也沒任何幫助,只是一堆名詞堆砌。到不如了解為何會有 Android?適合解什麼樣的問題?需要時可視需求對相關知識做進一步研究。 (備註:我並不了解這兩個問題,只是舉例而已。)

並不是說記名詞毫無意義,費曼童年時自己發明一套數學符號,他覺得讀起來比較好理解,很高興地用了一陣子。但後來發現他無法和人溝通,就把這些符號丟了,改用共通符號。與人溝通需要專有名詞,不然難以進入問題核心,講半天還在說「你是說如果有 n 個點,點可以是任意東西,像是城市,或想像成座標也行,這 n 個點之中部份兩點有互相連接,連接的距離長短不一,而你想從某個起點 A 走到終點 B,想問如何走才能花最少的步數嗎?」(即最短路徑問題)。

2009年8月5日 星期三

《別鬧了,費曼先生》讀後感 -- 關於科學的品德

小時候曾讀過一次,當時只覺得費曼這人真有趣。走過一遭研究的生活後,再回來讀這本書,幾乎每部份都令我深思許久。特別是最後一篇《草包族科學》讓我有更深的體會。

這篇是費曼於1974年在加州理工學院的畢業典禮演講,有別於其它故事,單刀直入說明科學研究最重要的課題--科學的品德,也就是對自己誠實,對大眾誠實。費曼舉了個反例說明何謂不誠實:

有個朋友在上電台節目之前跟我聊起來,他是研究宇宙學及天文學的,而他很感困惑,不知該如何談論這些工作的應用。我說:「根本就沒有應用可言。」,他回答說:「沒錯,但如果這麼說,我們這類研究工作就更不受支持了。」我覺得很意外,我想那是一種不誠實。如果你以科學家的姿態出現,那麼你應該向所有非科學家的大眾說明你的工作--如果他們不願意支持你的研究,那是他們的決定。

自己做研究也是一樣,該同時說明支持和不支持的數據和觀點,而非只報告支持的部份。要延伸別人的研究前,要先重製對方的實驗,如此才知道兩者在自己的實驗環境下差多少,而不能為了急著產生報告,省略前人的實驗,直接做自己的部份。

做研究必須客觀,早就同意某種觀點而開始做實驗,和從實驗中找出支持自己觀點的證據,是截然不同的行為。在利益衝突下,我們很容易欺騙自己,得非常非常細心且耐心地排除任何有利於自己發展的想法。很多時候我們難以把持,或是不自覺地騙了自己。舉例來說,看到違反預期 (不利) 的結果,會不斷思考那個環節有誤,嘗試修正「不當變因」;看到符合預期 (有利) 的結果卻會很興奮,忘了要進一步確保沒有「不當變因」影響結果。

藉由費曼生活中的插曲,看到一位真正科學家自然行事的風格,令我受益良多。在看到這麼多令人困惑的情況後,費曼的身教無疑是個強心劑。我想我不會再困惑了,若有那麼一天我打算讀博士,除了找到非作不可的研究外,還要確定我能把持住想法,能完全地誠實才會成行。不然,我還是遠離利害關係,至少在沒有利害關係的影響下,我能較誠實地進行研究。

費曼演講的最後一段如此說道:

因此我只有一個希望:你們能夠找到一個地方,在那裡自由自在的堅持我提到過的品德;而且不會由於要維持你在組織裡的地位,或是迫於經濟壓力,而喪失你的品德。

我誠心祝福,你們能獲得這樣的自由。

So I have just one wish for you—the good luck to be somewhere where you are free to maintain the kind of integrity I have described, and where you do not feel forced by a need to maintain your position in the organization, or financial support, or so on, to lose your integrity. May you have that freedom.

看到這段話,我鼻頭都酸了,祝福自己,也祝福所有對科學有興趣的人。

ps. 英文原文講稿在此

2009年7月25日 星期六

學而不思則罔,思而不學則殆

現在回顧兩年前思考未來出路的文章,覺得孔子說的「學而不思則罔,思而不學則殆。」真是太有道理了。再多規劃,聽再多情報,若沒有親身體驗,只會對紛亂的情報感到困惑。

現在回頭比對當時搜集的情報和現在知道的情報,發現有許多出入,甚至連對自己的認知都有誤。一開始我不知道自己喜歡做研究還是做產品。結果經過四個月全心投入研究後,覺得做研究滿有趣的,和正在業界的學長聊天,覺得自己似乎沒那麼適合當工程師。結果去一家公司實習四個月後,又覺得自己比較喜歡做產品。現在做了半年半研究半產品的工作,又覺得好像都很有趣。到頭來是看目標是否明確有意義、組織是否能給我彈性的空間發展,以及不需要為了衝積效而做無意義的事。那麼,不論是研究還是產品,都能從中找到樂趣。若用負面例子來看,寫不踏實的學術報告,和做難用的產品,都一樣乏味。

需求不明確,情報又不見得正確,怎麼能做出有效的決定?

所以,若碩士畢業生沒有先到業界實習過,不清楚自己的喜好,聽再多情報也難以判斷。另外,若不是和公司內的人直接一對一聊,情報價值也難以判斷。和一家公司內的一個人聊,也只能了解該組詳細情況和公司概況,容易以偏概全。就像好學校有壞實驗室,壞學校會有好實驗室一樣,和自身最相近的案例才有幫助。

類似的例子俯拾皆是,像是高中生選大學,若自身沒有針對有興趣的科系先嘗試一下,和他說明學校科系的特色,也難以定奪。若用系統來解釋,沒有寫過系統程式直接讀作業系統的書,只會覺得抽象得令人不知所云。寫一堆系統程式不看書消化整理知識,需求稍微變更,又不知該如何有效處理。

學技術時我明白孔子這兩句話的意思,但在人生方向的課題裡,我卻到現在才明白:原來我沒搞懂。話說回來,接觸新領域時,總會想省時間偷吃步,不做基礎練習,看來在技術方面,我也沒明白這兩句話的重要性。

2009年7月22日 星期三

學程式語言的樂趣

國三時為了做北市數學科展,實習老師教我使用 Microsoft Word,這是我首次體會到電腦在遊戲之外的樂趣。為了學會怎麼灌 Windows,高一時參加了社團,結果社團學長 Scott 說:「灌 Windows 太簡單了,你來學 C 吧。不過我們這裡只有 Red Hat Linux,就邊學 C 邊學 Linux 吧。」傻傻的我,以為 Linux 裡只有 Vi,就四處翻 Linux 入門書,看完各本書裡 Vi 部份,還寫了篇 Vi 的教學。日漸熟悉 Vi、bash 指令後,覺得玩系統、寫程式真有趣。於是上課無聊就偷看電腦書,下課耍自閉繼續看書,中午放學都跑去社團混,就這樣渡過我的高一。不知不覺,我打下日後學習能力最重要的基礎。

高一一時想不開,同時學 C 和 Perl,用 Perl 寫東西頗有趣的,特別是費盡心思把程式擠到一行作完一件煩人的小工作時,有說不出來的成就感。可是我基礎太弱,無法明白兩者更深層的差異,只能當作兩個不同工具看。兩者同時學的下場,學一陣子仍不熟兩邊的語法。於是我先專心學 C,之後 Perl 也忘光了。那段日子最大的收獲是,不要一次同時學兩個語言。另外,多虧 Scott 心理不正常,用 K&R 的《C 程式語言》當入門書,我直接學會標準語法,和 C 的典型寫法。這個習慣一直伴隨我到現在,每學一個新語言時,我傾向直接找能教我典型寫法的書,省去先花時間學一堆語法,再去蕪存菁地寫出典型風格。

寫了三年的 C,大一接觸到 Java 後,覺得 OOP 很有趣,這麼簡單又優雅地,讓我能同時使用兩個 Stack!雖然一開始寫著滿滿的 C-like Java,程式裡都是 static method,看了些 OOP 的書,開始轉型寫著自以為是 OOP 的怪東西。時至今日,我仍然不明白要怎麼寫出真正的 OOP。至少我發現自己寫得不是 OOP,和大學時相比,算是進步不少。

有些課要求用 C++,我就會用 Java 學到的 OOP 觀念和 C 的經驗來寫。每次要寫 C++ 就重翻一下 C++ 快速入門的書,寫完後又忘光一切。大三做專題時,我和朋友們打算參加一個國外的比賽,由於主要贊助商是 Microsoft,我們決定用 .NET Framework 開發。就這樣,我有個好機會寫 C#。亂翻一些 ASP.NET 的書把殼刻一刻後,我找了《C# Essentials》來看。這本書如其名,薄又都是重點,很快地明白 C# 的特色,讓我覺得 C# 挺漂亮的。不過寫完專題後沒繼續寫 C#,現在什麼也不記得了。而且,當時我的底不夠深,仍無法感受到各語言的特色。

除 C++、C# 兩個插曲外,大學時主要用 Java 和 PHP。用途很簡單,接網站案子用 PHP,其它用 Java。我練習 PHP 的方式是不斷改寫自己的網站,還有接些小案子,可是我覺得 PHP 實在不怎麼美,沒動力學好它,能用就好。

大學中途有試著寫了一點 Python、Scheme,可惜無法持續。一直到碩一一時興起,一方面是厭煩了用 PHP 寫重覆的東西,另一方面想試看看當紅的 Ruby on Rails,就邊查邊寫完成一個小案子。寫得過程裡,覺得 Ruby 很奇妙,於是在寒假花了一週讀完《Programming Ruby》第一部份,這才正式開始我學的第三個語言。

自那之後,我只有寫 Ruby,Java、PHP和其它語言碰也沒碰,這樣直到碩士畢業。沒錯,我的碩士論文也是用 Ruby 寫的,這才深深體會到 Ruby 有多慢有多肥,幸好實驗室的機器很壯,不然我得改用 Java 完成碩論。這段期間,把一個論文裡用到的程式零零總總地加起來,大概有兩千多行,也驚覺 Ruby 幫我省了不少寫程式的時間,相信用 Java 的話,我無法在不到一個月的時間裡寫完自己的演算法、數個比較對象的演算法、前置處理、評估分數等程式。不過卻辛苦交接的學妹,這樣瘋狂用一個不熟語言趕出來的程式碼,原以為不會繼續用的說。

用 Ruby 讓我有了第二次衝擊,就像當年從 C 到 Java 發現可以同時有兩個 Stack 那般驚奇,沒想到 iterator、code block 這麼好用,腦裡想什麼,手就可以直接敲出來,不用寫一寫還要把游標移來移去。而且,Ruby 可以寫出很短又好讀的程式,我從來沒想過程式可以寫到這麼精簡,而寫到這麼精簡後,又更容易理解。

碩士畢業去一家公司實習時,該公司不用 Ruby,我藉機來試看看 Python。於是,對照先前一年多寫 Ruby 的經驗,我明白很多事。有時選擇某個語言,沒有什麼明確的理性依據,單純是個人偏好。高中時我一直想知道 Python 和 Perl 的差異,到底學那個比較划算,如今又多了一個 Ruby 列入抗爭。現在來看,即使我可以找到三者在語言設計、執行速度、社群、函式庫、上手度等各方面的比較,到頭來,選擇何者反而像是信仰。

附帶一提,若要推廣一個程式語言,良好的互動直譯器、快速隨手查的文件、精簡易讀的入門書,三者缺一不可。 Ruby 大概就互動直譯器弱了一些,而 Python 三者都完美無缺,非常容易上手。至於 Java,即使到現在,我仍不知道要推薦別人看什麼書入門。最後附上我對各語言入門方式的看法,入門書書單是針對已學過一種語言的人:

語言 互動式直譯器 隨手查的文件 入門書
C man page 《C 程式語言》
Ruby 設定後的 irb ri 《Programming Ruby》
Python ipython ipython 《Python Essential Reference》
Java CHM格式的 Java doc

( 待續 )

2009年6月13日 星期六

為什麼要寫 unit test?為什麼要先寫測試?

這篇說明寫 unit test的好處,以及為何先寫 unit test 再實作,比實作完再補 unit test 更好。這裡先列出摘要:

  • 測試碼可以簡省手動測試的時間,但有錯時無法告訴我們錯誤的源頭在那。
  • unit test 可以告訴我們錯誤的源頭在那,可是 unit test 有時間成本和維護成本。寫過多 unit test 反而有害。
  • TDD 藉由先寫測試避開 unit test 的成本問題,並帶來其它好處。

為什麼要寫測試碼?

吃燒餅那有不掉芝麻,寫程式自然要驗證程式是否正確。想像平時我們如何驗證程式?大概就是寫一寫,執行看看,看看輸出符不符合預期,換些不同輸入,再看看結果,有錯再改,改完再重跑...。測試碼可以協助我們自動化驗證,省時省力。

也許有人會懷疑寫測試碼不划算,功能常改變,到時測試碼又要重改。這個問題視情況有不同種解法,比方用較「便宜」的方式寫測試,像是準備好輸出入檔,用 shell script 執行,配合 diff 看輸出是否一致。或是用 scripting language (如 Python) 寫簡單測試。而更根本的解法,個人認為是配合 TDD,待後面說明。

手動執行測試,執行個三十年,每次花的時間還是一樣,也許還會因為手酸變慢。但寫測試碼則相反,我們會愈寫愈快,愈寫功能愈好。

為什麼要寫 unit test (單元測試)?

首先介紹不同級別的測試碼,由小到大依序為:

  • unit test:測試對象為單一函式。
  • integration test:測試對象為數個單元函式的複合體。我沒特別區分這個和 unit test [*1],以下兩者一起用 unit test 表示。
  • system test:測試整個系統。
  • acceptance test:客戶驗收用的測試。將使用情節或使用需求轉成 acceptance test,藉以確認產品滿足客戶要求,方便起見,以下以 system test 統稱。

備註 *1:在談論 mock 時,unit test 和 integration test 的差異較明顯,本文不談這個議題,總得讓大家有意願寫測試,再來談如何寫測試才有意義,你說對吧。

以測試的觀點來看,system test 的 test case 夠多,system test 應該能涵蓋到大範圍的產品碼,如此一來也達成我們一開始提的「驗證」目的。從實務觀點來看,寫愈多測試碼,負擔愈多,規格變動時要改的東西也多。更何況 unit test 著眼點更細,變更實作方式也有可能得改 unit test。如此一來,為什麼要寫 unit test

首先,unit test 著眼點比 system test 小,也意味著當測試結果不對時,unit test 可以指出更明確的問題點,而 system test 只能粗略地和我們說輸出不對,接著我們得比對正確和錯誤的輸出,開始推測 bug 在那,並用 debugger 設中斷,或在程式內輸出訊息一步步找出問題源頭。相信大家都能明白除錯的痛苦。而 unit test 可以協助我們釐清那些程式是對的,那些是錯的,將問題範圍縮小。若 unit test 寫得好,幾乎用不到 debugger 和輸出訊息,光看那個 unit test 錯誤,就知道 bug 在那。

除了幫自己省時間外,unit test 也可幫助別人維護和理解程式。維護的人不如自己熟悉,難免不小心改爛程式,unit test 可以減少這類問題,也讓原作者安心給其他人修改。新手要閱讀程式時,unit test 可看成是各函式的使用範例,相信大家都同意讀例子比讀全部程式碼來得容易吧。好的範例有時比註解還有用。

為什麼要先寫測試?

好吧,若讀者大人耐著性子看到這裡,想必已對測試碼已有些心動,也認為寫 unit test 有那麼幾分道理,那先寫測試和後寫測試有什麼差別?若後來寫的程式不管用,需求變更,先寫測試不是搬磚砸自己的腳,自找麻煩嗎?事實上正好相反。

我們得先問自己,為什麼程式會不管用?為什麼需求會變更?去除客戶或主管找麻煩的因素外,一部份原因為思慮不週,寫的功能不夠貼近目標。如同產品一般,沒試用過我們不會明白產品的缺點在那,函式也是一樣。先寫測試碼就是先想像要如何使用即將要寫的函式,在寫測試碼的同時,我們同時也在設計函式。有時會發現難以測試,而想出更簡潔的介面。當我們能輕鬆寫出測試碼時,也意味著目標函式易於使用,之後才方便重用。

再者,先寫測試,相當於先列需求,規範中的輸入為何?預期輸出為何?可能有什麼例外情況?於是,我們接著寫出的程式,一定是有用的。我們不會忽然想說「要不要加個 foo(),之後大概會用到吧。」先寫測試,確保之後寫出的每一行產品碼都是有意義的。同時,我們寫的測試碼也是有意義的,若事後再補測試,不明白加入一個 unit test 有何幫助,有時會多寫不必要的 unit test,如同前面提過,測試碼不是萬靈丹,它也同時是負擔,需求變更時,有時也得一併改測試碼。再者,先寫測試表示有先考慮程式的可測性 (testability),使得程式容易測試,通常會將程式拆得很細,待寫產品碼時容易將寫好的小函式組成目標功能;完成產品碼後再來補測試則困難許多,由於寫產品碼時沒有考慮到寫測試的需求,結果是影響測試碼的品質,甚至變成過於難寫而不補上測試。

除幫助設計、減少寫冗碼的機會外,先寫測試還方便我們之後進行重構。沒有測試碼的重構是很危險的,而沒重構的程式碼也是很危險的 [*2],就像在底層不穩的地基上不斷加蓋偷工減料的高樓一般。先寫測試的好處還有一點,早寫早享受,先寫測試,就能先自動化驗證,省去所有手動驗證。

備註 *2:為什麼要重構?這是一個值得獨立討論的議題,主因為減少隱藏的 bug 和強化程式碼的可讀性,並更容易重用,以及增加新功能。詳見 Martin Fowler 寫的 《重構:改善既有程式的設計》

綜合以上所言,先寫測試花費的時間,可以攤算在設計時間,以及減少寫無用程式、減少手動驗證、縮短除錯的時間,所以先寫測試不見得會增加額外時間,我們只是把投入不划算事上的時間,先拿來寫測試。

特別加贈:何時該寫新的測試碼?

雖然寫測試好處多多,也別卯起勁來狂寫,最後卻覺得投資沒回本,浪費過多時間寫測試。遵照 TDD 的原則,先依規格寫測試,別浪費時間寫也許有用的測試。

那麼,何時才是跳出 TDD 迴圈,另外加寫測試的時機呢?依我個人經驗,我發現以下三個情況,都是寫新測試碼的好時機:

  • 在程式裡輸出訊息找錯誤:與其花時間寫 print、看輸出訊息、再回頭砍掉 print,不如寫個 unit test,之後能持續受惠。
  • 執行 debugger:同上,你還是得花時間設中斷,一步步看訊息,不如寫個 unit test,之後能持續受惠。
  • 不知錯誤在那,該如何進行下一步:這表示一次貪心實作太多功能,把目標縮小,一步步補 unit test 吧。

另外,在解 bug 時,先寫個 unit test 重製問題,再開始除錯。如此一來可確保自己明白成因,之後同樣的 bug 也不會再出現。

超級加贈:若 TDD 這麼好用,為啥它不遍及?

施主,這個問題得問你自己啊,快來加入推廣 TDD 的行列吧!

說正經的,雖然上面將寫 unit testTDD 說得如此美好,寫 unit test 不是簡單的事。若寫得不好,就會有「改變實作,也得改變測試」的副作用,或是測試執行過久,不方便反覆執行,或是寫出無用的測試。如同學習 design pattern 一般,unit test 也有 design pattern,對沒接觸過寫測試碼的人來說,相當於要花「兩倍」時間學習。加上前面提到大家可能有的誤解,使得 TDD 難以落實。

另一個可能是,若你的組織同時有兩批人馬對同一產品進行開發和除錯,程式碼分成兩個 branch:一個加入新功能,一個除錯。重構會造成程式碼難以合併,決策者因而選擇不頻繁重構。並且,這類組織可能有龐大的人力另外進行測試 (可看成是 system / acceptance test),而誤以為不需要 unit test。

然而,長遠來看,第一個理由顯然是偷懶,只是把現在的問題延後到日後再解決。趁早把除錯的時間省下來,投資到 TDD 這積優股吧。第二個情況可能是公司考量,專業分工使得公司容易找人和培訓,我沒在這類公司工作的經驗,不知能如何對應。

詳細的 TDD 風險,可參照 What is the downside to Test Driven Development?。結論是團隊要有熱情學一堆新技術,或是有個經驗老道的開發者帶才適合用 TDD。不過,我個人的觀點是,現實世界的選擇不是非一即零,不是所有程式都能 TDD,若成本過高用 TDD 不划算,那就暫時別花時間學相關技巧,挑軟柿子吃。至少,有用 TDD 的部份就能享受到 TDD 帶來的好處。別因為一些可能想見的問題,而全盤否定使用 TDD

我對 TDD 充滿熱血,請問如何入門?

好吧,這個標題是我寫來自 high 的。若有興趣的話,可以參考 The Bowling Game Kata投影片,由設計保齡球計分程式的小例子,一步步展示 TDD 進行的過程,相信跟著例子走一次必能有所啟發,親身體驗 TDD 如何帶來新設計。若能自己先寫一遍,再來看投影片,感受會更深。或是參閱我之前寫的介紹文:《TDD 推廣:背景知識和簡介》以及《最近用 Python + TDD 心得(與 Java 做對照)》

2009年5月11日 星期一

以有涯逐無涯,殆矣。

近幾個月學習狀況不錯,學習效率好也能持久,但總覺得愈學愈多,愈學愈累,偶爾會有種茫然的挫折感。忽然想到莊子的這句:

吾生也有涯,而知也無涯。以有涯逐無涯,殆矣。

原本我曲解了這段話,還振奮了一下,想說要更認真地追求知識。忽然,我發現莊子的本意反而是點出恰到好處的生活方式,於是我的世界改變了。

體悟到這點後,我決定晚上十點後停止電腦科學相關的學習,改看看動畫、或翻翻閒書,或偶而像這樣寫寫 Blog 也不壞。待之後多存點錢,買了電鋼琴還可以來個「彈琴復長嘯」。不知為何,我覺得自己可以用更自在的方式追求更深的知識,讓生活過得趨於平衡。

忽然,又有兩年半前相似的體悟,到外邊小晃了一圈,我似乎走回正確的路了。即使不知道方向,不知道終點何在,我明白自己正踏在想走的路。

題外話,看到現在學習程式語言和機器學習的進展,不禁懷疑過去為何期望這麼久,卻遲遲沒有行動。才想起以前還有許多想做的事,像是增加電腦科學的廣度認知、寫 3D 遊戲、參加各式比賽、做研究寫論文等。許多事若沒有親身嘗試,只會念念不忘,難以打消念頭。經過前幾年的嘗試,才能像現在般沉澱心靈,專心在最有興趣的幾個點上。