Daniel Teng:
Justin Searls正在比較底特律派TDD和倫敦派TDD,這是第一集搓译,用的是成為“Discovery Testing”的倫敦派玩法悲柱。題目還是Gameof Life
https://www.youtube.com/watch?v=aeX5OXO-w30
Joseph Yao:
為了方便大家看視頻,我已經(jīng)把全部四集(已完結(jié))傳到了土豆上面侥衬,你懂的诗祸。整個(gè)四集的時(shí)間大概在兩個(gè)半小時(shí)。
Part 1: http://www.tudou.com/listplay/dlhRQuC3a4g/nsSXLo3CE-4.html
Part 2: http://www.tudou.com/listplay/dlhRQuC3a4g/c5SOegUcqmc.html
Part 3: http://www.tudou.com/listplay/dlhRQuC3a4g/S0fs3Z7uMHQ.html
Part 4: http://www.tudou.com/listplay/dlhRQuC3a4g/JN6GRH2Gl4A.html
Justin 有關(guān) Discovery Test 的 Blog http://blog.testdouble.com/posts/2015-09-10-how-i-use-test-doubles.html
Justin 的實(shí)現(xiàn)代碼在這里 https://github.com/searls/game-of-life-java-example
——————————— 建議你先看完視頻再看我下面的評(píng)論———————————
我在往來(lái)上海轴总,北京和福州的飛機(jī)上面看完了所有四集直颅。Justin 的英文說(shuō)的比較快,有時(shí)挺含糊的怀樟,有些地方?jīng)]太聽(tīng)懂功偿。好在有代碼演示,應(yīng)該使我能夠理解他的做法和理念了往堡。第一集開(kāi)始講的是他對(duì) Detroit School TDD 和 London School TDD 的理解械荷,這部分我很認(rèn)同,總結(jié)的很到位虑灰。然后他提出了自己根據(jù) London School TDD 發(fā)展出來(lái)的 Discovery Test 方法吨瞎,瞬間讓我很期待后面的三集。不過(guò)可惜的是穆咐,看完之后的感覺(jué)離我的預(yù)期差距較大颤诀,我無(wú)法認(rèn)同 Discovery Test 這種做法字旭。
先來(lái)簡(jiǎn)單介紹一下 London School TDD。這種方法源自于倫敦的極限編程社區(qū)崖叫,Steve Freeman 和 Nat Pryce 的著作 “Growing Object-Oriented Software Guided by Tests” (簡(jiǎn)稱為 GOOS)是對(duì)這種開(kāi)發(fā)方法的經(jīng)典總結(jié)遗淳。Steve 曾經(jīng)和我開(kāi)玩笑說(shuō),這本書(shū)是他們社區(qū)爭(zhēng)論十年的結(jié)果心傀。我覺(jué)得屈暗,相比 Detroit School TDD(由 TDD 之父 Kent Beck 提出),London School TDD(或者說(shuō) GOOS)是在其基礎(chǔ)上的發(fā)揚(yáng)光大脂男,而不是看似“對(duì)立”的另一種 TDD养叛。GOOS 方法使得 TDD 和 ATDD/BDD 可以結(jié)合在一起,更好的從業(yè)務(wù)和設(shè)計(jì)上疆液,以測(cè)試的形式一铅,來(lái)驅(qū)動(dòng)整個(gè)開(kāi)發(fā)過(guò)程。關(guān)于 GOOS 方法的細(xì)節(jié)堕油,我之前和伍斌有過(guò)一些討論潘飘,可以看這里 https://groups.google.com/forum/#!searchin/agileshanghai/DDD/agileshanghai/pbojfS61sBE/e-VPbUIfAgAJ
總體來(lái)說(shuō),我認(rèn)為 Discovery Test 并沒(méi)有很好的繼承 London School TDD 的做法掉缺,甚至產(chǎn)生了背離卜录。另一方面,整個(gè)過(guò)程有明顯的過(guò)度設(shè)計(jì)眶明。具體來(lái)說(shuō):
整個(gè)過(guò)程中 Justin 并沒(méi)有寫(xiě)驗(yàn)收測(cè)試艰毒,以至于我無(wú)法理解他要實(shí)現(xiàn)的需求是什么。比如搜囱,他在討論設(shè)計(jì)的時(shí)候提到 KeepsTime 和 TimeLimit 這樣的概念丑瞧,我就不明白到底是什么需求導(dǎo)致了這樣的設(shè)計(jì)。說(shuō)到底蜀肘,如果沒(méi)有明確的需求绊汹,我完全可以認(rèn)為 Game Of Life (GOF)和時(shí)間的關(guān)系很小。比如扮宠,GOF 可以無(wú)限制的把 Cell 變換下去西乖,每隔 0.1 秒變一次就可以了。
GOOS 方法中的驗(yàn)收測(cè)試坛增,等同實(shí)例化需求中的例子获雕,是對(duì)業(yè)務(wù)需求的最小拆分并擁有用戶價(jià)值,讓開(kāi)發(fā)過(guò)程始終有一個(gè)業(yè)務(wù)聚焦點(diǎn)收捣〗彀福夸張點(diǎn)說(shuō),程序員在寫(xiě)代碼和測(cè)試的每一秒都應(yīng)該思考的一個(gè)問(wèn)題罢艾,就是“我如何讓驗(yàn)收測(cè)試盡快通過(guò)楣颠,盡早交付業(yè)務(wù)價(jià)值”嫁乘。
Justin 甚至都沒(méi)有提到驗(yàn)收測(cè)試這件事,直接導(dǎo)致他在后面三集的開(kāi)發(fā)中球碉,對(duì)設(shè)計(jì)進(jìn)行不少“YY”(我在下面會(huì)提到)。他在第一集的最后給出過(guò)別人實(shí)現(xiàn)的帶界面的 GOF仓蛆,但是他自己的代碼到最后也沒(méi)有實(shí)現(xiàn)那種效果睁冬。
如果是我來(lái)開(kāi)發(fā)的話,我會(huì)先寫(xiě)一個(gè)驗(yàn)收測(cè)試看疙,比如豆拨,在網(wǎng)頁(yè)上面顯示一個(gè) 1*1 的矩陣,里面只有一個(gè)代表 dead cell 的點(diǎn)(白色)能庆,經(jīng)過(guò) 0.1 秒之后施禾,還是顯示這樣的矩陣。雖說(shuō)把矩陣顯示出來(lái)并不是開(kāi)發(fā)的風(fēng)險(xiǎn)所在(Justin 也這么說(shuō))搁胆,但是我在實(shí)際工作中遇到過(guò)太多以為沒(méi)有風(fēng)險(xiǎn)的地方最后卻發(fā)生了風(fēng)險(xiǎn)的事情弥搞。寫(xiě)一個(gè)簡(jiǎn)單的驗(yàn)收測(cè)試,或者至少手動(dòng)驗(yàn)收一下渠旁,可以很好的避免程序員喜歡“YY”的毛病攀例,減少開(kāi)發(fā)風(fēng)險(xiǎn)。
由于沒(méi)有驗(yàn)收測(cè)試的緣故顾腊,Justin 在他的 Design Session 里面提到了很多概念粤铭,并且在之后的演示中把這些概念一個(gè)不差的變成了代碼。這里面有明顯的過(guò)渡設(shè)計(jì)杂靶,具體表現(xiàn)如下:
KeepsTime 和 TimeLimit 為什么需要梆惯?至少這兩類到最后也沒(méi)有實(shí)際的代碼。
為什么有了 World吗垮,還要一個(gè) MutableWorld垛吗?我感覺(jué)有 World 這個(gè)類就可以了。Justin 的解釋是 GenerateSeedWorld 產(chǎn)生的 World 有可能對(duì) at(Coordinates) 的實(shí)現(xiàn)很不相同抱既,但是我覺(jué)得他在沒(méi)有證實(shí)這一點(diǎn)之前就引入 MutableWorld 就太早了
總的來(lái)說(shuō)职烧,我覺(jué)得 Coordinates, Point, Contents, Outcome 都可以被 Cell 代替
為什么需要 Contents?我覺(jué)得 Contents 可以被 Coordinates/Point 這個(gè)類代替防泵,同時(shí)給他增加一個(gè) neighbours 方法就可以了(其實(shí)這個(gè)類應(yīng)該改名叫 Cell 更合適)蚀之。到最后,Contents 里面都沒(méi)有代碼
為什么需要 Outcome捷泞?我覺(jué)得 ReplacesCell 的 replace 方法直接返回一個(gè)下一狀態(tài)的 Point (或者說(shuō) Cell)就可以了足删。到最后, Outcome 里面只有 Contents 和 neighbours 這兩個(gè)數(shù)據(jù)
DeterminesNextContents 和 GathersNeighbors 的代碼應(yīng)該被融合進(jìn) ReplacesCell锁右,這些本來(lái)就是 ReplacesCell 應(yīng)該做的事情失受。GathersNeighbors 沒(méi)有代碼實(shí)現(xiàn)讶泰,這個(gè)類不就是在做 Cell.neighbours 的事情嗎?
因?yàn)檫@些過(guò)度設(shè)計(jì)拂到,導(dǎo)致 Justin 的代碼中有很多 Middle Man 的代碼臭味
整個(gè)四集中痪署,Justin 很少花時(shí)間重構(gòu)代碼,我覺(jué)得并不合理兄旬。說(shuō)實(shí)話狼犯,測(cè)試代碼中有不少重復(fù)是可以去掉的。他在第一集里面提到過(guò) GOOS 方法要比 Detroit School TDD 做重構(gòu)的時(shí)間要少一點(diǎn)领铐,這點(diǎn)我認(rèn)同悯森。不過(guò),GOOS 雖然重構(gòu)少一點(diǎn)绪撵,但絕對(duì)不是沒(méi)有瓢姻,而且關(guān)鍵時(shí)刻還是會(huì)推翻現(xiàn)有設(shè)計(jì)的。大家看了 GOOS 這本書(shū)就會(huì)明白音诈。
有人認(rèn)為 GOOS 方法強(qiáng)調(diào)依賴倒置原則(DIP)幻碱,所以稱其為 Mockist TDD。我覺(jué)得這是一種誤解改艇,其實(shí) GOOS 首先強(qiáng)調(diào)的是單一職責(zé)原則(SRP)收班。因?yàn)樵?GOOS 中,每當(dāng)你寫(xiě)一個(gè)類的UT時(shí)谒兄,就應(yīng)該思考他的職責(zé)是什么摔桦,那么那些不是他的職責(zé)范圍的代碼就自然變成了依賴(在UT中被隔離)。Justin 其實(shí)也是這么做的承疲,只不過(guò)因?yàn)闆](méi)有一個(gè)體現(xiàn)需求的驗(yàn)收測(cè)試邻耕,也沒(méi)有從最小的需求入手,所以導(dǎo)致了過(guò)度設(shè)計(jì)燕鸽。
不知不覺(jué)寫(xiě)了這么多兄世,我也是醉了。最后預(yù)告一下啊研,經(jīng)過(guò)這一年在開(kāi)發(fā)和 coaching 中對(duì) GOOS 的應(yīng)用御滩,我發(fā)現(xiàn)他對(duì)提升代碼質(zhì)量和開(kāi)發(fā)團(tuán)隊(duì)設(shè)計(jì)編碼能力非常有幫助。我計(jì)劃在明年更大范圍的推廣 GOOS党远,并嘗試做些社區(qū)活動(dòng)或者課程來(lái)推廣他削解。
謝謝,
zhanggang:
多謝Joseph和Daniel沟娱,一口氣看完了其中3個(gè)視頻氛驮, 第一集真的很好,把London School TDD的優(yōu)勢(shì)列的很清楚济似,看了之后覺(jué)得思路又清晰了不少矫废。當(dāng)然作為L(zhǎng)ondon School/Top-down TDD的支持者和實(shí)踐者盏缤,我是覺(jué)得其實(shí)這是唯一正確的TDD方式, 并沒(méi)有所謂的缺點(diǎn):)從交付的角度蓖扑,Top-down TDD把設(shè)計(jì)的順序和實(shí)現(xiàn)的順序?qū)崿F(xiàn)了完美的統(tǒng)一唉铜,這個(gè)是非常重要的,非常懷疑過(guò)去首先自頂向下設(shè)計(jì)律杠,然后自底向上的構(gòu)建是在沒(méi)有Mock這種基礎(chǔ)設(shè)施的情況下的無(wú)奈之舉打毛。從精益的拉動(dòng)角度,其實(shí)被Mock掉的接口事實(shí)上扮演了對(duì)下游實(shí)現(xiàn)的訂單的角色俩功,從而消除了實(shí)現(xiàn)的盲目性,通過(guò)直接聯(lián)系到交付價(jià)值的真實(shí)反饋碰声,對(duì)下游的實(shí)現(xiàn)提供了真實(shí)的需求定義诡蜓。深深地覺(jué)得Justin選了Game Of Life作為例子實(shí)在失策,GOL算法比較重胰挑,設(shè)計(jì)不需要太強(qiáng)蔓罚,是演示簡(jiǎn)單TDD的好例子,但用來(lái)做Top-down TDD沒(méi)啥優(yōu)勢(shì)瞻颂,反而容易過(guò)度設(shè)計(jì)豺谈。 Top-down的方式在那種層次比較多,職責(zé)分配比較復(fù)雜的情況下才能顯出特別的價(jià)值來(lái)贡这。