2009年3月15日 星期日

軟體開發技巧的待讀書單

經過這陣子的評估,終於列出了夢幻般 (?) 的讀書清單。我的做法是先在網上看到有人推薦,到書局翻一陣子,再回來查 Amazon 的評論,最後決定是否有必要看。附帶一提,待這書單決定後又查了一下,結果發現每本書都有得到 Jolt Award,一瞬間好像以為 Jolt Award 不值錢了 :-)。

以下依暫定的閱讀順序依序說明,除前兩本外,後三本都有中文版:

1. Test Driven Development by Examples

Amazon 評論 4 顆星,由 Kent Beck 所著。全書只有 200+ 頁,用大量的例子,一步一步說明怎麼進行 TDD,相當易讀。這裡有別人寫的詳細書評

2. xUnit Test Patterns: Refactoring Test Code

Amazon 評論 4.5 顆星,不過只有七筆評論,樣本略嫌不足。 在書店試翻的感想是:好書但不易讀,而且實在是太厚了。ThoughtWorks 專出這種書嗎?

3. Refactoring to Patterns

Amazon 評論 4 顆星,但評論兩極化落在 3 和 5,最中肯的評論為: "Good ideas, but needs refactoring",值得一讀,但不好消化。讀過 Martin Fowler 的 Refactoring 並有一段實戰經驗後的最佳書藉。本書中文版《重構-向範式前進》,且是侯捷合譯的 。

4. Head First - Head First Object-Oriented Analysis and Design

Amazon 評論 4 顆星,評論裡指出本書適合初學者,另外最多人同意的評論 (Decent Introduction to OOA&D) 指出:本書不夠簡潔,並有不少小錯,若有第二版才值得推薦。 

5. Head First - Design Pattern

Amazon 評論 4.5 顆星,且是壓倒性的一堆 5 顆星。Head First 的書以易讀出名,但通常也寫得很厚,需要花不少時間消化 。 對照過於精簡典雅的 Design Pattern Bible Book,這本書親切不少。附帶一提,在書店還有看到 Head First - Algebra,真是太有趣了!可惜是教國中代數,不是教線性代數。

依目前心得來說,有四件事要學:

  1. 寫出良好的測試程式 - book 2
  2. 寫出良好的 OOP- book 4, 5
  3. 提昇重構技巧 - book 3
  4. TDD (其實就是上述三者的綜合體) - book 1

前往軟體開發聖殿之路是很遠的,希望一個月至少能解決一本,並持續地應用到實戰中,半年後就出師啦!

備註

這裡補充我曾看過且大力推薦的書藉:

2009年3月10日 星期二

TDD 推廣:結語和參考資料

前篇,這篇說明我和組員近兩週開發過程中的心得,並附上幾篇不錯的參考資料。

結語

目前我和組員只有做到半套的 TDD,有時有照三步驟的順序進行,有時是先寫程式碼,再補測試碼,或兩者交替寫。目前組員有感受到測試程式的好處,例如對程式有信心、避免因修改而破壞既有功能。組員也漸漸感受到重構的必要性。但對於先寫測試碼這點,仍有疑慮而難以進行。至少,我們盡量遵守 TDD 的結果,在寫另一個 class 時,會先補好測試碼,保持程式碼的品質。附帶一提,配合 IDE 的功能,先寫測試碼可以輕鬆地建出產品用程式的框架。像 Eclipse 可以自動產生不存在的 class、method、field,所以我常先寫測試,再用 Eclipse 建出這些東西的殼,之後再填入內容,寫起來相當快。

開發過程也不盡是順利。藉由頻繁的 code review 組員的程式碼,還有回顧自己的程式碼,我發現許多問題。像是單一測試函式太大、測試碼和程式關聯性過高、測試時間過長,若沒有第一時間更正,後果不堪設想,也會使得組員對 TDD 有不當的負面認知。這方面的相關知識可以搜尋 TDD antipattern,或 JUnit antipattern

此外,由於組員少了前備知識,進行開發過程裡會看到許多 code smell,常常在重構一段程式前,必須先重構另一段程式,而重構另一段程式前,又得先補對應的測試程式或重構測試程式。這才體會到良好品質的程式碼得來不易,需要全組成員一同堅持和維護,大家的相關知識也需要一同逐步提昇,才能達到良好的軟體開發流程。雖然初期摸索很花時間,相信日後會有相當高的回報。

說了這麼多,其實仍抵不過自己親身嘗試,強力建議親身找個小專案,用 TDD 的三個步驟寫個千行程式,相信程式設計思維會有很大的轉換。

參考資料

我到 slideshare 找了一陣子,看到三份不錯的教學,但各有些不足,沒有一份完全符合我預期的入門文件。若有時間的話,自己再來作份投影片。

相關閱讀

TDD 推廣:相關工具

前篇,這篇說明我近兩週開發過程中選擇工具的心得。

開發環境

我採用 Eclipse,配合 Eclipse 的 refactoring tool,大幅加快重構的速度。人都有惰性,好工具讓我們更願意重構,而且降低重構過程除錯的風險。對照之前我自己寫 C++ 和 Python 的經驗,現在我更常做 rename variable/method、change method signature 之類的操作。

Unit Test

經過評估後,我決定用 JUnix 4.x。JUnit 4.x 改用 annotation 的方式表示 test method,可以去除必須繼承 TestCase 的限制。如此一來 test method 命名彈性更大,而且也可以直接在 class 裡加 test method 測 private method,解決因 private 不方便測試的困擾。不過我不確定這種作法是否正確,仍需多點經驗判斷。其它 JUnix 4.x 的好處可以參照《An early look at JUnit 4》

關於 unit test 的技巧, ThoughtWorks 有出相關的書:《xUnit Test Patterns: Refactoring Test Cod》。有機會再去書店翻看看。

Mock lib

關於 mock 的知識可以參見 Martin Fowler 的《Mocks Aren’t Stubs》,解釋得鞭闢入理,除介紹 mock 外,更進一步指出用與不用 mock,其實測試思維是截然不同的(確認物件行為或確認物件狀態),有機會的話再對此另寫心得。文中的範例程式有點過時(畢竟是舊文章),現在 mock lib 的用法更方便一些。Java 的 mock lib 有許多選擇,最後我選用 EasyMockZ。除了好用外,主要是因為 Python 的 Mox 是從 EasyMock 來的,這樣寫 Python 時可以花較少心力學 Mox。

2012-08-05 更新: 我現在改用Mockito了,更為簡單易用 。

Database unit testing

若只是用到簡單的 database 操作,可以用 EasyMock 處理,。測試前準備好 mock connection、mock statment、mock resutSet 即可。一方面是初學 mock 的緣故,另一方面也是 database 相關操作太瑣碎,我花了一整個下午才用 EasyMock 完成測試程式並寫好對應的功能。事後修改挺複雜的,比方說我程式裡補了 close(),mock statement/connection 也要記得加。感覺不是理想的作法,這方面經驗尚淺,需要多點實戰。

若要測得更深更全面一些,測試對 DBMS 本身的操作( insert / delete / update)的話,用 DBUnit 會方便許多。DBUnit 的基本精神是幫測試程式準備好已知狀態的資料庫,以及比對兩個資料庫是否相等。它的核心功能是將資料庫內容和 XML 檔案互轉。所以,先建好一個資料庫,透過 DBUnit 將整個資料庫存成一個 XML 檔,日後隨時都能用該 XML 檔還原回原樣。

對測試程式而言,執行速度是非常重要的,執行速度快才能反覆地執行多次,才方便反覆開發、重構以及整合測試。所以我改用 SQLite 做為測試用的資料庫,真正的產品再用 MySQL 之類的 DBMS。SQLite 的好處是支援 SQL92 的語法,整個資料庫只要一個檔案即可。如此一來只需要讀寫本機檔案,省去網路連線的負擔,也方便建置測試環境。

由於使用 DBUnit 前得先準備好資料庫才行,我得先從既有的 MySQL database 建出同樣 schema 的 SQLite database,才能用 DBUnit 做後續的操作。原本打算寫個程式讀出 MySQL database schema 再建出 SQLite database schema。但兩者建 table 語法差太多,無法直接套用。最後我是利用 DBUnit 產生的 XML 檔,寫程式從 XML 檔中讀出 table 和 column 名稱,再建出簡單的 SQL 指令來建出 SQLite database。如此一來,每次自動測試前我先用 DBUnit 將 SQLite database 填入備好的資料,就能快速地重覆在同樣的狀態下測試資料庫操作的程式碼。待有更多相關經驗後,再針對此議題寫篇心得。

相關閱讀

TDD 推廣:影響和個案心得

前篇,這篇談 TDD 帶來的改變,以及我自己的個案心得。

TDD 的影響

只要用 TDD 重寫以前寫過的小專案,就會發現最後的產出,和原本寫的程式不同。舉例來說,會發現少了很多 setter function,因為大多情況只要用 constructor 放值即可。更進一步,會發現少寫了許多功能,而這些功能其實是原本不需用到的。

TDD 同時也幫助設計出易用的介面,透過寫測試程式的過程,可以釐清程式的使用方式。當發現程式不好測試時,通常意味著程式設計不良。測試程式也可以當作文件使用,提供使用程式的範例程式碼。

除此之外,在 TDD 的過程中,開發的速度很順暢,很少會落入長時間的除錯裡,因為程式被強迫切成許多小單位,並且每一單位都有被測試。一但出錯馬上會發現,而且知道錯在那裡。不像以往得用 debugger 追個老半天,最後發現 bug 在很遠的一小段程式碼裡,有時還是簡單的打錯字。在除錯的過程,藉由自動化測試的幫助,修改和執行可以快速地反覆交替進行,減少除錯以外的精神損耗(如手動準備執行需要的資料、設置和操作 debugger )。即使寫測試程式花的時間和除錯一樣多(我相信會是較少),至少以 TDD 的方式進行,心情會比較愉快,不會卡在一個地方太久。

TDD 的個案心得

以我最近的實作案例來說,我一開始知道會用到數個算數函數,而且這些函數日後需要抽換為不同的算法,方便我比較何種組合效果最好。舉例來說,我需要實作排序演算法 Sort(),由於資料分佈特性的改變,我可能需要實作各種演算法如 quick sort、heap sort、insertion sort、radix sort 等,並讓這些演算法的輸入輸出介面一致,方便視情況抽換不同的演算法。在這個案例裡,我需要寫多個這類型的演算法,把它們整合成一個較大的程式。

若照我原本的習慣,大概就全部套 strategy pattern,於是我可能會先寫數個 interface 或是寫個 abstract class 再套多型。至少會花半天完成這些工作,並且增加一堆「未來可能會用到」的 class。但這回我忍住了,我遵從 TDD 的三個步驟,我先統統用 static method 寫,因為這是最簡單的實作。結果寫個兩天後,我發現這幾個算數函數,其實只有一個真的需要套 strategy pattern,其它用 static method 就夠了。也許那一個最複雜的函數也不需用到 strategy pattern,總之,由於目前仍無需求,我全部都保留原樣。於是我將時間花在刀口上,優先完成必要的事。並且,我的 class space 沒有被一堆「也許有用」的 interface 和 class 汙染,避免 over-design,提高整體的可讀性。

相信大家看到這都會大喊,全部都等用到才寫,日後難道不會付出更大的代價做變動?改程式碼的代價很高,所以應該多費些心力設計好才對吧?但別忘了,「改程式碼的代價很高」是一般的情況,在有充沛的測試程式做為後盾的情況下,改程式的代價其實沒那麼高。況且,程式碼隨時都有重構,程式應該是保持在容易修改的情況,改程式的代價又更低了。

至於初期的設計要精確到什麼程度才開始寫程式,仍需經驗拿捏。我目前的作法是先有個大概的整體設計,再開始用 TDD 的方式實作各個 class,並完成細部的設計。

相關閱讀

2009年3月9日 星期一

TDD 推廣:背景知識和簡介

之前我曾獨自一人用 TDD (Test Driven Development,中譯為測試驅動開發) 的方式,分別用 Python 和 C++ 各寫了一千多行的小程式,感覺滿好的。最近剛好有機會寫一個新專案,就趁這機會開始第三次的 TDD 練習。和前兩次不同的是,這次要和一位組員合作,以 Java 開發。看來正是測試 TDD 威能的最好時機。

由於我的組員缺乏寫測試程式的經驗,剛好可以用來評估是否能在組內推廣 TDD。雖然我之前有過兩次 TDD 開發經驗,但也只是自己邊看文件邊摸索,經驗仍嫌不足。這次進行 TDD 讓我多花了不少心力查相關資料。但是為了擺脫軟體開發後期的泥沼,現在先下點苦工是值得的。這裡記錄一下過程中的心得。

背景知識

了解 TDD 前,得先知道重構 ( Refactoring ) 和單元測試 ( Unit Testing )。個人大力推薦 Martin Fowler 寫的《重構:改善既有程式的設計 》,可以了解何謂好的程式碼、如何找出該改善的程式碼以及如何改善它。書中列出多種簡單的操作流程,相當淺顯易懂。附帶一提,Martin Fowler 的個人站有許多經典好文,值得細細咀嚼。

TDD 簡介

TDD 是個知難行易的道理。知難,是因為我們很難相信它,到不是因為 TDD 概念很複雜,只要三句話就可以交待完 TDD:

  1. 先寫簡單的單元測試,並執行它。
  2. 用最簡單的方法實作需要的功能,讓程式能通過測試。
  3. 重構程式,並確保重構後的程式仍能通過測試。

實務上,這三條規則蘊含了經年累月的經驗法則。若沒親身體驗過,再怎麼解釋它們帶來的好處,也很難令人信服。這三條規則是環環相扣的,由於有先寫測試,才能安心地重構;由於有重構,才能方便地擴充功能;由於專注於最簡單的實作,省掉 over-design 的時間,才能用更快的速度完成該作的事,並有多餘時間寫測試程式和重構。藉由測試程式,程式設計師可以提高對程式碼的信心;透過重構,程式碼易於修改和重用。注意這裡強調「最簡單」的實作,這正是 TDD 精神所在,簡單的實作易於進行,複雜的考量如程式碼是否能重用、是否易於擴充,都留待第三步再做。專心正是將事做得又快又好的祕訣。

天下沒有白吃的午餐,要做到如上所述的理想世界,必須學許多寫測試程式的技巧。如同平常寫程式有輔助工具、函式庫、設計模式等,測試程式亦有對應的東西要學。舉例來說,產品用的程式需要重構,測試用的程式也需要重構。關鍵在於「treating their tests like first-class citizens. 」反之若將測試程式視為可有可無的附帶程式,只會陷入惡性循環,改變產品用程式的細節得改變測試用程式,反而變成得改兩倍的程式,以及在兩份程式中除錯。結果變得更糟,並誤認為測試程式是累贅。我剛用 TDD 時就因為沒把測試程式寫成許多小巧的函式,或是沒有寫成正確的檢查方式,造成產品用的程式是對的,卻常在測試程式裡除錯,造成無謂的損耗。

相關閱讀

2009年3月8日 星期日

讓 Python 的 unittestgui 可以載入 test suite

由於 pyUnit 附的 unittestgui 怎麼試都沒成功,我改用這個 unittestgui.py:GUI test runner using wxPython。相關說明請見之前的文章。美中不足的是,它不方便載入 test suite,所以我修改了一下這份程式。

程式下載和用法

點此下載修改後的程式碼。使用的範例程式碼如下:

1
2
3
4
5
6
7
8
9
# all_test_suite.py
import unittest
import catsinbag.card_factory_test
import catsinbag.cash_test
 
def suite():
    suites = []
    suites.append(unittest.TestLoader().loadTestsFromTestCase(some_module.SomeTestCase))
    return unittest.TestSuite(suites)

執行 unittestgui.py 後,按 Open 選擇 all_test_suite.py 即可載入。

目前這個工具仍有些問題,像是原始碼改過後,得重開檔案載入後才會生效,所以無法一邊改程式碼一邊按 Run Test。還有錯誤訊息顯示得不夠清楚,而且還是顯示在 console 上,不是顯示在 GUI 上。

修改過程心得

參照 unittest官方文件,最方便載入多個 test cases 和 test suites 的方法,是採用下面這段 code:

1
2
3
suite1 = unittest.TestLoader().loadTestsFromTestCase(SomeTestCase1)
suite2 = unittest.TestLoader().loadTestsFromTestCase(SomeTestCase2)
alltests = unittest.TestSuite([suite1, suite2])

若自己用 console 執行 unittest,只要把 alltests 交給 TextTextRunner 即可,例如:

1
unittest.TextTestRunner(verbosity=2).run(alltests)

但是,一個熱愛寫 testing 程式的程式設計師,除了想要有更高品質的程式碼外,內心更渴望看到 Green Bar啊!所以非得用 GUI 版的 unittest 不可。而 unittestgui.py 可以載入 test case、test suite,卻沒辦法載入 module 內產生的 test suite。所以只好直接改 unittestgui.py 的程式碼。關鍵在於 OnMenuOpen() 如何取得 test suite。原本的程式碼如下:

1
2
3
4
5
6
7
modname, modtype = os.path.splitext(os.path.basename(self.filename))
if modtype.lower() == '.py':
    moduleToTest = imp.load_source(modname, self.filename, file(self.filename))
elif modtype.lower() in {'.pyc':0, '.pyo':0}:
    moduleToTest = imp.load_compiled(modname, self.filename, file(self.filename, 'rb'))
#print moduleToTest, dir(moduleToTest)
self.suite = unittest.defaultTestLoader.loadTestsFromModule(moduleToTest)

修改後的程式碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
modname, modtype = os.path.splitext(os.path.basename(self.filename))
if modtype.lower() == '.py':
    moduleToTest = imp.load_source(modname, self.filename, file(self.filename))
elif modtype.lower() in {'.pyc':0, '.pyo':0}:
    moduleToTest = imp.load_compiled(modname, self.filename, file(self.filename, 'rb'))
#print moduleToTest, dir(moduleToTest)
if hasattr(moduleToTest, 'suite'):  # use the prepared suite if it exists.
    if callable(moduleToTest.suite):
        self.suite = moduleToTest.suite()
    else:
        self.suite = moduleToTest.suite
else:
    self.suite = unittest.defaultTestLoader.loadTestsFromModule(moduleToTest)

利用已讀到的 module: moduleToTest,增加判斷它是否有提供 function suite() 或是 attribute suite,若有的話,直接使用準備好的 test suite,而非從 TestCase 或 TestSuite 中產生。

最後附上執行後的精美圖片,辛苦了這麼久,就是為了看到這 Green Bar 啊!

unittestgui_with_suite

裝了 ScribeFire Blog Editor

由於 Windows Live Editor 對 WordPress 的支援不夠完整,這篇改用 Scribefire 試看看。

Scribefire 也有很炫的預覽功能,而且多了可以將網頁的標題和連結取出加入編輯內容內,挺方便的。Category 部份到是有支援一半,可點選要加入那些目錄,但沒有顯示出原本目錄的階層關係。唯一的缺點是不能加入<!–more–>。或許之後可以改用 ScribeFire 看看。

Update

ScribeFire 也加了部份討厭的 tag,像是 <br> 和 <div>,這樣日後要回頭手動編輯時,文章內容挺醜的。

裝了 Windows Live Writer

剛才不小心關掉網頁而遺失了正在寫的文章,一怒之下就裝了 Windows Live Writer。現在這篇就是用它來試寫。

初步用起來還挺方便的,不過每打一個字畫面就閃一下,感覺挺怪的。還有不能插入 WordPress 專用的一些 tag,得自己切到 Source mode 輸入(例如<!—more—>)。 Preview mode 看起來挺屌的,可以直接看到文章送出後,在自己的 Blog theme 下看起來是什麼樣子。

Update

Windows Live Writer 竟然加了多餘的 tag,像是 <p>,而且不能用<!—more—>,發文章時找不到選 category 的地方,只有看到加 tag。改來試用別套好了。

2009年3月7日 星期六

甲土豆症候群

這個可怕的病得從小黃(匿名)去一趟系計中後說起。

有天小黃買了新硬碟,卻發現自己的電腦無法使用,只好去系計中借些工具處理一下。結果當時系計中的部份人士正瘋狂地看《甲土豆系列》影片,小黃不小心中標了,遂把影片也帶回家裡推廣。

不久後小羊(匿名)也中招了,兩人還常常你一句我一句地接裡面的台詞,這時小駱(匿名)才發覺事態不妙。但小駱經不起誘惑,仍將它介紹給小豬(匿名)、小德(匿名)、小王(還是匿名)等人,結果不幸地,小王也中標了。

在此記錄此種症狀的感染過程:

  • 通常看完第一遍時,是一臉茫然不知所措。
  • 看第二遍時,開始有噗嗤一聲。
  • 看第三遍時,開始斷斷續續地笑。
  • 看第四遍時,開始莫明地狂笑,但要說為何好笑,卻怎麼也說不上來。
  • 看第五遍時,忍不住跟著念起劇中台詞,而且沒有播放時還會不停地覆誦劇中台詞,遇到其他發病者時,會自然地你一句我一句地接完全部台詞。

為證明我不是無端唬爛,以下為小王發病過程的個案記錄:

  • 這是啥鬼….XD “靠北我就說前面那一句給你…”
  • 後勁真的蠻強的…
  • 甲土豆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  • 甲土豆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  • 甲土豆!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

若還是不信邪的話,請直接觀看《甲土豆》一片:

另外讓小黃和小羊中毒最深則是這篇《歐巴馬》(內有髒話,不喜者勿點):

我自己是覺得《聯合國》也不錯啦(內有髒話,不喜者勿點):

僅以此篇,為這些患者留個記錄。若看完上面兩篇還無感覺的話,恭喜你,你目前對《甲土豆》免役,別再多看幾遍,快快離開《甲土豆》的世界吧。

備註

我忘了在那看到別人介紹,這系列原本的影片是教青少年正確的災難處理方式,像是「失火了怎麼逃生」,「有人在冰湖上跌倒了,要怎麼救?」,對照改版後的台詞,真是太 XD 啦,讓我想到台詞翻譯的重要性。而說到台詞翻譯的重要性,不得不提這篇《史上最爛翻譯: 港版「風之谷」完整考證》,相信自此之後,大家會對譯者們致上最高的敬意。

路漫漫其修遠兮,吾將上下而求索

忽然發現很久沒更新「格言綿句」了,來補上去年十月末的感觸。

原文寫於 2008-10-21,下文為稍微修改後的版本。

在家和老爸老媽一起看《喬家大院》,這部戲還滿好看的。當喬致庸決定做票號 (相當於現今的銀行) 時,他的得力助手孫茂才大力勸阻,而喬致庸引用《離騷》回了這段話:

亦余心之所善兮,雖九死其猶未悔。
路漫漫其修遠兮,吾將上下而求索。

真是感動到心坎裡了!雖然我還不知道自己要追求什麼,也許過陣子會重想起學生時代的熱情。

備註

離騷全文見這裡,這輩子大概一次也不會仔細讀完吧。文學氣息這種東西,小時候沒好好培養,長大後就是那幅德性啦。要學的東西只會愈來愈多,學不完的東西也只會愈來愈多。