“一個(gè)房子如果窗戶破了,沒(méi)有人去修補(bǔ),隔不久,其它的窗戶也會(huì)莫名其妙地被人打破;一面墻,如果出現(xiàn)一些涂鴉沒(méi)有被清洗掉,很快 的,墻上就布滿了亂七八糟、不堪入目的東西;一個(gè)很干凈的地方,人們不好意思丟垃圾,但是一旦地上有垃圾出現(xiàn)之后,人就會(huì)毫不猶疑地拋,絲毫不覺(jué)羞愧!
我們一直在喊敏捷開發(fā),其實(shí)敏捷開發(fā)的一個(gè)很重要的目的就是消除浪費(fèi),防止破窗效應(yīng)的發(fā)生。事情太難,就讓它簡(jiǎn)單,更簡(jiǎn)單。流程太重,就讓它輕點(diǎn),更輕點(diǎn)。盡量掃清開發(fā)的障 礙,消滅破窗形成的環(huán)境。下面我會(huì)從軟件構(gòu)建的很多方面來(lái)描述如何防止“軟件開發(fā)中的破窗”。
臟代碼
如果代碼不整潔,后來(lái)人就很難看懂,人們往往會(huì)對(duì)難以看懂的代碼失去耐心,不愿意進(jìn)一步了解。如果不能進(jìn)一步了解一部分代碼,也就難以改進(jìn)它,這樣 帶來(lái)的一個(gè)后果可能有兩點(diǎn):1、這段代碼被拋棄,然后重新編寫。2、直接復(fù)制這段代碼在別的地方使用。對(duì)于第一點(diǎn),會(huì)帶來(lái)軟件開發(fā)中的浪費(fèi),而且再次編寫 也不可能就能一部到位的編寫正確,可能會(huì)引入新的bug。對(duì)于第二點(diǎn),大家都知道重復(fù)代碼是設(shè)計(jì)走向腐化的根源之一。
如果我們?cè)诰帉懘a時(shí)能不斷的應(yīng)用一些原則,確保我們的代碼易懂,自描述。在開發(fā)新特性時(shí)還不斷的使用重構(gòu)手段,讓我們的設(shè)計(jì)保持一個(gè)良好的狀態(tài)。 我們就能防止窗戶被繼續(xù)打破。
測(cè)試
沒(méi)有測(cè)試,或者混亂的測(cè)試代碼都是破窗滋生的環(huán)境。
沒(méi)有測(cè)試
沒(méi)有測(cè)試時(shí),當(dāng)我們想對(duì)一塊代碼進(jìn)行重構(gòu),我們就像沒(méi)有帶保險(xiǎn)繩走鋼絲,步履維艱,生怕一下子失去平衡,掉下懸崖。這樣在我們的心中就產(chǎn)生了懼怕重 構(gòu)的陰影,久而久之,我們就不去重構(gòu)。最后帶來(lái)的結(jié)果就跟上面一段說(shuō)的一樣,設(shè)計(jì)不斷的腐化,然后就失去了控制。
如果有了單元測(cè)試,有了驗(yàn)收測(cè)試,當(dāng)我們每做一下重構(gòu)時(shí),我們都可以從測(cè)試快速獲得反饋,每當(dāng)紅條亮起時(shí),我們知道我們破壞了一些已有的功能,我們 停下來(lái)去修復(fù),當(dāng)綠條亮起時(shí),我們知道現(xiàn)在處于安全狀態(tài),可以安心的繼續(xù)重構(gòu)。一切都在我們的掌控之中,我們會(huì)喜歡上重構(gòu)。
混亂的測(cè)試代碼
有很多人覺(jué)得測(cè)試代碼不是交付給用戶的產(chǎn)品代碼,可以區(qū)別對(duì)待,我們不需要花那么多時(shí)間琢磨變量命名,方法命名,我們也不需要關(guān)注重復(fù)的代碼。但 是……
混亂的測(cè)試代碼跟沒(méi)有測(cè)試是一樣的,甚至比沒(méi)有測(cè)試更糟糕。我們以為我們有測(cè)試,但測(cè)試卻給我們虛假的報(bào)告,當(dāng)我們發(fā)現(xiàn)我們的重構(gòu)破壞如此之深時(shí), 已經(jīng)為時(shí)已晚。即使測(cè)試能給出真實(shí)的報(bào)告,但如果測(cè)試代碼混亂,那么添加新的測(cè)試就非常困難,我們就會(huì)越來(lái)越懼怕添加新的測(cè)試。而且隨著產(chǎn)品代碼的演進(jìn), 測(cè)試代碼也需要伴隨著演進(jìn),測(cè)試代碼越混亂,我們就越難以修改測(cè)試,讓它反應(yīng)出現(xiàn)在產(chǎn)品代碼的狀態(tài)。終于到了一天,大家決定拋棄測(cè)試,如是我們又回到了沒(méi) 有測(cè)試作保障的日子。
實(shí)際上,從某種程度上測(cè)試代碼的整潔程度比產(chǎn)品代碼的整潔程度更重要,因?yàn)橛辛撕玫臏y(cè)試我們可以無(wú)憂無(wú)慮的重構(gòu)我們的代碼,即使現(xiàn)在我們的產(chǎn)品代碼 很糟糕也不怕,因?yàn)橛辛藴y(cè)試的保證,我們知道我們可以重構(gòu)過(guò)去,如果我們只有混亂的測(cè)試代碼,我們那一線重構(gòu)的希望都沒(méi)有了。
難以測(cè)試
可測(cè)試性是衡量代碼的一項(xiàng)準(zhǔn)則。既然是準(zhǔn)則一般都很難達(dá)到,如果代碼難以添加測(cè)試,在嘗試幾次之后,我們一般都會(huì)放棄編寫測(cè)試的想法。當(dāng)我們嘗試對(duì) 一段代碼編寫測(cè)試時(shí)發(fā)現(xiàn),這塊代碼鐵板一塊,與太多的其他類耦合,需要傳入很多重型對(duì)象的參數(shù),比如與設(shè)備交互的代碼,與數(shù)據(jù)庫(kù)交互的代碼相耦合,這些重 型對(duì)象很難模擬或插樁。沒(méi)有辦法,在進(jìn)度的壓力下我們只有放棄添加測(cè)試的想法了,那么如上面一樣,代碼就像草原上奔跑的野獸,失去了控制。
編寫可測(cè)試性的代碼是困難的,要將糟糕的代碼改進(jìn)成可測(cè)的代碼尤其困難。但有一個(gè)訣竅,我們可以先編寫測(cè)試,用測(cè)試驅(qū)動(dòng)出我們的產(chǎn)品代碼,這樣一開 始我們就獲得了一個(gè)個(gè)測(cè)試套件,將我們的產(chǎn)品代碼穩(wěn)穩(wěn)的固定在那里,就像走鋼絲時(shí)的保險(xiǎn)繩;不僅如此,我們還獲得了可測(cè)試性的代碼。
測(cè)試運(yùn)行太慢
實(shí)際上測(cè)試運(yùn)行太慢是一種信號(hào),該信號(hào)告訴我們耦合的太緊了。運(yùn)行一個(gè)測(cè)試,需要編譯加載很多模塊。如果運(yùn)行一個(gè)測(cè)試需要20分鐘,你希望頻繁的運(yùn) 行測(cè)試么?如果運(yùn)行一套測(cè)試需要10個(gè)小時(shí),你希望測(cè)試多久運(yùn)行一次?測(cè)試運(yùn)行太慢就是第一個(gè)被打破的窗戶,如果不趕快修補(bǔ),后面會(huì)有更多的窗戶被打破。
測(cè)試運(yùn)行太慢,我們就不會(huì)頻繁的運(yùn)行測(cè)試,測(cè)試也就不能提供立即的反饋,這樣測(cè)試的作用就大打折扣了。
上面主要從代碼實(shí)踐方面來(lái)闡釋編碼中的破窗和如何防止破窗,其實(shí)在軟件開發(fā)的很多方面都存在類似的情況。
源代碼管理
有很多團(tuán)隊(duì)因?yàn)楦鞣N各樣的原因采用了難以使用的源代碼管理工具,或者完全因?yàn)閺S商對(duì)管理層的廣告宣傳,采用了一個(gè)無(wú)比重型,好看但不中用的工具。在 經(jīng)受一兩次工具的折磨之后,團(tuán)隊(duì)成員就會(huì)產(chǎn)生懼怕的心理,盡量的推遲提交代碼。提交代碼需要足夠的頻繁,甚至一次有意義的重命名都可以作為一次提交,這樣 在代碼復(fù)查的時(shí)候光閱讀提交代碼的注釋就能演示出代碼的演化過(guò)程。而且,如果每一次成功都有保存,這樣在犯錯(cuò)的時(shí)候我們有機(jī)會(huì)后悔,我們有機(jī)會(huì)回滾到一個(gè) 成功的狀態(tài)。人的大腦雖然非常聰明,但也非常易于出錯(cuò),特別是在疲勞的時(shí)候,如果我們小步前進(jìn),小步提交,我們就能停在任何地方。你還記不記得那種必須到 某個(gè)時(shí)候才能保存當(dāng)前狀態(tài)的電腦游戲?
有的時(shí)候并不是工具難以使用,而是環(huán)境使然。在分布式的團(tuán)隊(duì)里,有可能網(wǎng)絡(luò)不穩(wěn)定,遠(yuǎn)程源代碼倉(cāng)庫(kù)經(jīng)常不可訪問(wèn),或者在提交代碼時(shí)需要連上VPN, 然后再提交,久而久之也會(huì)讓團(tuán)隊(duì)成員懶于提交代碼。這樣我們就應(yīng)該采用分布式的源代碼管理工具,比如Git。
難以集成
代碼寫完了并不是開發(fā)任務(wù)的結(jié)束。你還記不記得多少次為了集成產(chǎn)品,解決幾個(gè)模塊之間的沖突而加班加點(diǎn)。敏捷強(qiáng)調(diào)及時(shí)的反饋,持續(xù)的交付。如果集成 一次產(chǎn)品需要幾天時(shí)間,我們?nèi)绾巫龅郊皶r(shí)反饋呢?如果集成太困難,大家都會(huì)懼怕集成,就會(huì)盡量的避免集成,但產(chǎn)品最終是要集成的,所以到了最后期限的時(shí) 候,大家都在加班加點(diǎn),但卻不是寫代碼,而是為了集成。
如果集成太困難,我們?yōu)槭裁床怀掷m(xù)的集成呢?所有團(tuán)隊(duì)成員都工作在同樣的分支上。持續(xù)集成服務(wù)器不斷的簽出最新的代碼,運(yùn)行各種各樣的測(cè)試,最后構(gòu) 建出可用的軟件出來(lái)。只要需要,任何時(shí)候我們都可以提供可以工作的軟件。
可視化
可視化是管理中的鐵三角之一。很多管理人員喜歡使用各種各樣絢麗的工具繪制出絢麗的圖表。比如使用Project做出精確到天的人員計(jì)劃,使用 PowerPoint做出產(chǎn)品的宏偉藍(lán)圖。好像將這些做出來(lái),然后發(fā)給大家就有一種這個(gè)項(xiàng)目都在我的控制之內(nèi)的感覺(jué)一樣。其實(shí)不管怎么優(yōu)秀的軟件工具還是 比不上紙和筆。軟件打開需要時(shí)間,隨著軟件更新?lián)Q代,軟件體積越來(lái)越大,打開一個(gè)龐大的Project文件甚至需要一兩分鐘的時(shí)間,而且文檔埋藏在電腦文 件系統(tǒng)的深處。找到文檔,打開,幾分鐘就已經(jīng)過(guò)去了,別看這幾分鐘,久而久之我們就不想再去看這些東西了,我們以為這些東西都裝在了我們的腦中,但實(shí)際卻 沒(méi)有;撕芏嗑帉懙男枨笪臋n,最后成了一紙空文,當(dāng)發(fā)現(xiàn)與需求不符的時(shí)候已經(jīng)晚了。要防止這種事情的發(fā)現(xiàn),我們就不要打破第一扇窗。
雖然到了二十一世紀(jì),豐田公司還是在很多方面采用原始的看板。軟件開發(fā)中也是一樣,拋棄那些精美的軟件吧,將計(jì)劃,進(jìn)度,用戶故事用最簡(jiǎn)單的紙和筆 繪制,然后貼在開發(fā)人員抬頭就可見(jiàn)的墻上。不需要畫的多精美,因?yàn)樵骄谰驮讲幌肴バ薷,但軟件開發(fā)中永恒不變的是變化,我們必須隨需而變。
笨重的流程
有的公司給開發(fā)、測(cè)試、部署規(guī)定了嚴(yán)格的流程。開發(fā)人員想將產(chǎn)品功能部署到測(cè)試環(huán)境都需要與很多相關(guān)人員交互,提交申請(qǐng)單,然后才能由專人將剛剛修 改的一行代碼部署到測(cè)試環(huán)境中,進(jìn)行測(cè)試。首先不說(shuō)這個(gè)過(guò)程中有多少等待,多少浪費(fèi)。光這笨重的流程就讓大家望而卻步,進(jìn)而導(dǎo)致懼怕修改,連好的改進(jìn)都會(huì) 受到抵制。
后記
軟件開發(fā)的方方面面就像一扇扇窗戶,不要打破第一扇窗戶,打破了也要趕快去修補(bǔ),不然軟件就會(huì)隨著窗戶一樣,一扇扇的被打破,慢慢的腐化下去。
日本的豐田公司,在遠(yuǎn)遠(yuǎn)落后于福特和通用之后,采用5S的精益思想【注3】,成為后起之秀,而這5S(整理、整頓、清掃、清潔和素養(yǎng))最終的目的就是 防止破窗效應(yīng)。
注1: 經(jīng)濟(jì)學(xué)上還有一個(gè)破窗謬論,但與此文沒(méi)有關(guān)系。
注2 : 要了解破窗效應(yīng)更多細(xì)節(jié)可以參見(jiàn)wiki:http://en.wikipedia.org/wiki/Broken_windows_theory
注3: 精益思想(Lean)是MIT對(duì)豐田生產(chǎn)方式(TPS)進(jìn)行研究后的產(chǎn)物。關(guān)于豐田5S和精益思想(Lean)可以參看豐田的相關(guān)著作。