目錄
摘要
問(wèn)題的提出
并行版本控制——多人協(xié)作開(kāi)發(fā)的有效保障
代碼提交和同步
編碼過(guò)程中的溝通紐帶——commit mail
日常測(cè)試——每日構(gòu)建
有效的版本控制——代碼分支、版本標(biāo)記
特性凍結(jié)與代碼凍結(jié)
交付工程
總結(jié)
參考文獻(xiàn)
作者簡(jiǎn)介
本文以cvs為例,介紹了軟件工程中,編碼過(guò)程中對(duì)于版本控制的運(yùn)用的一些技巧。在最后部分,還介紹了軟件工程最后的“交付工程”。
編碼過(guò)程是軟件工程的重要一環(huán)。這一部分工作的好壞直接關(guān)系到軟件產(chǎn)品的質(zhì)量。高效率的多人協(xié)作開(kāi)發(fā),依賴于團(tuán)隊(duì)精神、設(shè)計(jì)師對(duì)于軟件架構(gòu)的整體把握、好的并行版本控制技術(shù),以及制度化的每日構(gòu)建和最后階段的交付工程。
今年六月,我有幸在一家開(kāi)發(fā)安全軟件的公司觀摩了他們的每日構(gòu)建和交付工程中的活動(dòng)。他們對(duì)于并行版本控制、每日構(gòu)建技術(shù)熟練而深入的應(yīng)用給我留下了非常深刻的印象。在此,我愿與讀者一同分享我自己的學(xué)習(xí)體會(huì),這其中的某些部分得益于在那家公司的實(shí)地觀摩,另一些則來(lái)自于我自己參加的實(shí)際軟件工程項(xiàng)目的體會(huì)。
毫無(wú)疑問(wèn)地,一個(gè)軟件工程項(xiàng)目最有價(jià)值的部分還是在它的設(shè)計(jì)階段。良好的設(shè)計(jì)能夠讓實(shí)現(xiàn)環(huán)節(jié)變得更有效率,從而極大地提高勞動(dòng)生產(chǎn)率;而好的編碼規(guī)范,則是協(xié)同開(kāi)發(fā)的重要基石。限于篇幅,對(duì)于前述兩項(xiàng)內(nèi)容本文將不會(huì)過(guò)多設(shè)計(jì),我將著重介紹軟件工程中編碼與測(cè)試環(huán)節(jié)的一些經(jīng)驗(yàn),這些經(jīng)驗(yàn)對(duì)于已經(jīng)擁有優(yōu)秀的軟件設(shè)計(jì)師和編程、測(cè)試人員,而苦于由于連調(diào)、最終測(cè)試導(dǎo)致發(fā)布頻頻延期的開(kāi)發(fā)團(tuán)隊(duì)來(lái)說(shuō)是非常有益的。
設(shè)想一個(gè)有4名編程人員的小型開(kāi)發(fā)團(tuán)隊(duì)(以下簡(jiǎn)稱“TJRP開(kāi)發(fā)組”),Tom、Jason、Robert和Pat分別負(fù)責(zé)4個(gè)模塊,按照傳統(tǒng)的軟件開(kāi)發(fā)模式,開(kāi)發(fā)將經(jīng)歷編程-連調(diào)-測(cè)試-發(fā)布4個(gè)階段。
如果最初的設(shè)計(jì)正確,并且,四個(gè)開(kāi)發(fā)人員都是Guru級(jí)的程序員而且配合默契,那么這個(gè)模式將會(huì)運(yùn)轉(zhuǎn)良好。然而遺憾的是,Pat剛剛參加工作不久,對(duì)于設(shè)計(jì)師撰寫(xiě)的設(shè)計(jì)文檔的理解不夠透徹,而Robert則自作主張地對(duì)設(shè)計(jì)進(jìn)行了一些修正,更糟糕的是,項(xiàng)目組會(huì)議的時(shí)候,這些問(wèn)題沒(méi)有及時(shí)地被暴露出來(lái),導(dǎo)致Pat和Robert代碼在設(shè)計(jì)上的“分歧”越來(lái)越大。結(jié)果,進(jìn)入連調(diào)的階段,Pat和Robert發(fā)生了激烈的爭(zhēng)執(zhí),在吵得不可開(kāi)交之后,項(xiàng)目經(jīng)理終于讓4個(gè)人坐到了一起來(lái)解決問(wèn)題,最后,連調(diào)階段整整多花了一倍的時(shí)間。
但倒霉的事情還沒(méi)有結(jié)束。Jason在測(cè)試中發(fā)現(xiàn),原本正常的代碼的行為被改變了,并且,他驚訝地發(fā)現(xiàn)代碼被某個(gè)“別人”改過(guò),在翻箱倒柜地找出某份正常版本的副本之后,他又發(fā)現(xiàn),“別人”修改中的某些地方是必要的。代碼合并和重新測(cè)試使得測(cè)試階段足足花掉了原先預(yù)想3倍的時(shí)間。
可憐的Tom運(yùn)氣更差,作為主要的代碼復(fù)審人員,他不得不閱讀所有的代碼。Robert和Pat的爭(zhēng)吵導(dǎo)致了大量的代碼變動(dòng),他不得不重新審核代碼,而Jason的代碼合并引發(fā)的新問(wèn)題又讓他不得不分神去幫助Jason進(jìn)行調(diào)整。
最后的結(jié)果是,軟件開(kāi)發(fā)的成本是預(yù)期的2.4倍,發(fā)布時(shí)間也拖后了不少。我并不是在開(kāi)玩笑,上面所講的是一個(gè)發(fā)生在那家安全軟件公司的真實(shí)故事。他們的技術(shù)經(jīng)理介紹,在施行了規(guī)范的開(kāi)發(fā)制度,以及啟用并行版本控制系統(tǒng)之后,他們認(rèn)為開(kāi)發(fā)達(dá)到了一個(gè)全新的水平。并行版本控制系統(tǒng)本身并沒(méi)有產(chǎn)生任何代碼,但由于使用這樣的系統(tǒng),開(kāi)發(fā)的效率被大大地提高了。
所謂版本控制其實(shí)并不是什么復(fù)雜的概念。對(duì)于開(kāi)發(fā)活動(dòng)的絕大多數(shù)參與者來(lái)說(shuō),版本控制系統(tǒng)在某種意義上能夠幫助他們做好開(kāi)發(fā)過(guò)程中的記錄工作,并且,通過(guò)保存文件在不同時(shí)期的版本,交付工程師和代碼復(fù)審員能夠很容易地縮小搜索問(wèn)題代碼的范圍,而程序員則可以通過(guò)這樣的系統(tǒng)更好地并行協(xié)作。一般來(lái)說(shuō),源代碼的版本控制系統(tǒng)能夠?qū)崿F(xiàn)以下一些最基本的功能:
- 保存任意一個(gè)源代碼文件的不同版本
- 記錄修改者、修改原因
- 當(dāng)兩個(gè)用戶同時(shí)修改一個(gè)文件時(shí),盡可能地自動(dòng)合并修改;在不能合并時(shí),給出提示
- 比較不同版本之間,或與本地副本之間的差異
- 獲取最新版本的全部源代碼供測(cè)試,并允許回退到所保存的源代碼的任意版本
- 創(chuàng)建代碼分支,便于軟件發(fā)布和后期維護(hù)(后面將會(huì)提到);新的代碼可以合并到這些分支中。
- 對(duì)不同的源代碼給出標(biāo)記,方便日后審查
- 訪問(wèn)控制:阻止未經(jīng)授權(quán)的修改和查閱
我們知道,技術(shù)不是解決一切問(wèn)題的靈丹妙藥,但是誰(shuí)也不會(huì)否認(rèn)大規(guī)模的機(jī)械化生產(chǎn)的效率高于人拉肩扛的手工業(yè)作坊,一旦運(yùn)用得當(dāng),技術(shù)將極大地改善我們的工作和生活。我們可以看到,上面的功能有效地解決了TJRP開(kāi)發(fā)組所面臨的絕大部分問(wèn)題,例如:
- 由于能夠同時(shí)修改代碼,并獲取對(duì)方的修改,Pat和Robert能夠有效地、盡早地進(jìn)行溝通
- 促進(jìn)開(kāi)發(fā)者之間的交流,每一個(gè)修改都必須給出原因,并記錄提交者
- 測(cè)試和連調(diào)可以盡早開(kāi)始,避免模塊之間的不兼容在最后階段被暴露出來(lái)而阻礙發(fā)布
- 不同開(kāi)發(fā)者的修改能夠及時(shí)合并,并避免由于版本不一致導(dǎo)致的沖突
- 代碼復(fù)審可以針對(duì)某一代碼分支進(jìn)行,從而,允許一些開(kāi)發(fā)者持續(xù)地開(kāi)發(fā)下一個(gè)版本,而穩(wěn)定的代碼則可以交付給用戶
更進(jìn)一步,以管理者的角度,還有了一些額外的好處,如:
- 每日構(gòu)建和測(cè)試能夠讓項(xiàng)目經(jīng)理更好地把握工程的進(jìn)度
- 誰(shuí)作了多少工作,誰(shuí)工作的更出色,可以在版本控制系統(tǒng)中清晰地體現(xiàn)
- 分工明確,通過(guò)訪問(wèn)控制,可以避免不了解整個(gè)代碼體系的開(kāi)發(fā)人員偶然的錯(cuò)誤修改導(dǎo)致的全盤(pán)崩潰
- 更重要地,版本控制系統(tǒng)中將保持大量的開(kāi)發(fā)經(jīng)驗(yàn),這對(duì)于一個(gè)開(kāi)發(fā)團(tuán)隊(duì)來(lái)說(shuō)是一筆無(wú)價(jià)的財(cái)富
我們可以看到,上述改進(jìn)集中地體現(xiàn)了一個(gè)重要的思想,即:
及時(shí)溝通以預(yù)防問(wèn)題的出現(xiàn);盡早發(fā)現(xiàn)、盡早解決問(wèn)題;明確獎(jiǎng)懲制度,激發(fā)開(kāi)發(fā)人員的積極性。 |
下面我們將以非常常見(jiàn)的版本控制系統(tǒng)——cvs[1]為例,介紹并行版本系統(tǒng)一些基本的使用原理。
每當(dāng)我們開(kāi)始一個(gè)新的修改之前,首先要做的是從代碼庫(kù)中提取出一份最新的副本(通過(guò)update操作完成);在本地修改、粗調(diào)之后,則應(yīng)盡快將代碼提交回代碼庫(kù)(通過(guò)commit操作完成)。
基本的update和commit操作流程如下圖所示:
 圖1. cvs update和commit
這兩項(xiàng)操作也解決了日常開(kāi)發(fā)大約80%的問(wèn)題。絕大多數(shù)情況下,這部分的工作是相當(dāng)簡(jiǎn)單的,除非出現(xiàn)兩個(gè)開(kāi)發(fā)者同時(shí)修改同一個(gè)文件的情況,例如,兩個(gè)開(kāi)發(fā)者同時(shí)地修改了同一個(gè)文件的同一個(gè)版本,這種情形稱為沖突:

圖2. cvs并行開(kāi)發(fā)中的沖突
可能開(kāi)發(fā)者B的動(dòng)作比較快,或者,修改的東西比較簡(jiǎn)單,于是他首先提交。在A、B從代碼庫(kù)中提取代碼時(shí),最新版本是1.1,于是,B提交的版本被版本控制系統(tǒng)命名為1.2。
但不久,A想要提交代碼,版本控制系統(tǒng)將拒絕他的提交,因?yàn)樗男薷幕诖a的1.1版,而目前的最新版本已經(jīng)是1.2了。cvs提供了自動(dòng)合并功能,允許在兩個(gè)人修改的不是同一行代碼的前提下,自動(dòng)合并本地修改和最新的代碼,當(dāng)然,如果趕上兩個(gè)人同時(shí)修改同一行代碼的情況,cvs也會(huì)非常“聰明”地把兩個(gè)“英雄所見(jiàn)略同”的地方合并。
但如果兩個(gè)人恰好都修改了同一行代碼,而且改的不一樣怎么辦?cvs會(huì)告訴后一個(gè)提交的開(kāi)發(fā)者發(fā)生了這樣的情況,并且要求他解決問(wèn)題。代碼中存在的差異將以<<<和>>>標(biāo)記出來(lái),以方便進(jìn)行修改。
簡(jiǎn)單說(shuō)來(lái),當(dāng)發(fā)生沖突時(shí),我們通常約定由后一個(gè)提交者解決沖突——當(dāng)然,他可以選擇忽略這些沖突,但這些操作都會(huì)被記錄,更何況,統(tǒng)計(jì)顯示,同時(shí)將一行代碼修改為兩種不同的樣子這樣的情況在實(shí)際開(kāi)發(fā)中很少出現(xiàn)。于是,修改流程繼續(xù),如下圖:

圖3. 開(kāi)發(fā)者A解決沖突,并提交
極端情況下,可能出現(xiàn)多個(gè)開(kāi)發(fā)者同時(shí)修改同一個(gè)文件的問(wèn)題。這一問(wèn)題基本上可以按照上述的方法解決。當(dāng)然,為了避免發(fā)生這樣的情形,在設(shè)計(jì)的時(shí)候就應(yīng)該讓每個(gè)人工作的代碼盡可能地不重疊。
下面是非?;镜腸vs update/commit操作規(guī)范:
簡(jiǎn)單的cvs操作約定
- 修改文件之前首先update。這意味著修改時(shí)的版本盡可能新,一旦發(fā)生沖突,解決它的工作量會(huì)比較小。
- 及時(shí)commit。本地代碼與代碼庫(kù)中的代碼差異越小,別人合并的難度也就越小(他們有比較大的概率能夠拿到新的版本)
- 將不同的功能單元修改分開(kāi)commit。一方面,這樣做能夠盡早地commit,減少別人合并的難度;另一方面,由于cvs提供了回退到先前版本的能力,一旦由于某項(xiàng)功能修改造成問(wèn)題,也很容易將那次修改的內(nèi)容,而不是整個(gè)修改回退到正常的代碼。
- 同一功能涉及的所有代碼一次commit。不希望將涉及同一功能修改的代碼分開(kāi)commit,因?yàn)檫@會(huì)給日后的追蹤帶來(lái)麻煩。
- 先調(diào)試后提交。這將減少別人不會(huì)因?yàn)橥搅酥虚g結(jié)果引發(fā)問(wèn)題,甚至發(fā)生提交沖突的可能。
- 寫(xiě)清commit log(提交日志)。cvs中允許保存commit log,在這里可以寫(xiě)為什么進(jìn)行代碼的修改,以及進(jìn)行了什么樣的修改,清楚的commit log能夠幫助其他開(kāi)發(fā)者在不仔細(xì)閱讀代碼的情況下了解修改的內(nèi)容,從而極大地提高開(kāi)發(fā)效率;另一方面,這些日志對(duì)于開(kāi)發(fā)者自己,以及整個(gè)開(kāi)發(fā)團(tuán)隊(duì),都是非常寶貴的財(cái)富。
|
同步代碼(update)和提交代碼(commit)占到了cvs日常操作的80%以上。從上面的介紹我們可以看出,僅僅依靠這兩項(xiàng)非常簡(jiǎn)單的功能,cvs就能極大地改善開(kāi)發(fā)流程,并提高軟件工程的可控性。簡(jiǎn)單地說(shuō):
- 全體開(kāi)發(fā)者使用同一個(gè)中央代碼庫(kù),從而消除了由于來(lái)回復(fù)制文件導(dǎo)致的不一致。
- 發(fā)生沖突時(shí),后提交的開(kāi)發(fā)者必須解決它。這名開(kāi)發(fā)者能夠知道是誰(shuí)引入了沖突,他可以自己解決沖突,也可以與引入沖突的開(kāi)發(fā)者商量如何解決沖突。
- 測(cè)試可以貫穿編碼過(guò)程的始終,任何時(shí)候引入的新問(wèn)題都能夠被及時(shí)追蹤、快速定位,從而更有效地解決。
- 由于存在中央代碼庫(kù),代碼審核人員和每日構(gòu)建人員能夠及時(shí)地了解代碼是否存在問(wèn)題,并幫助項(xiàng)目經(jīng)理保證開(kāi)發(fā)進(jìn)度。
- 有助于幫助開(kāi)發(fā)人員養(yǎng)成嚴(yán)謹(jǐn)?shù)墓ぷ髁?xí)慣——規(guī)則要求他們將盡可能地提交正確的代碼,并且每個(gè)修改必須寫(xiě)commit log進(jìn)行說(shuō)明。
- 有助于建立更公平的工作質(zhì)量評(píng)估機(jī)制。 cvs能夠記錄每個(gè)人完成的實(shí)際工作量,包括他們因?yàn)樾拚龁?wèn)題等等所作的勞動(dòng),以及他們完成代碼的質(zhì)量情況。這樣,管理者能夠?yàn)閮?yōu)秀的開(kāi)發(fā)人員提供更好的工作機(jī)會(huì)、報(bào)酬,等等,這對(duì)于鼓舞整個(gè)團(tuán)隊(duì)的士氣、提高開(kāi)發(fā)人員的工作積極性都是非常有益的。
- 促進(jìn)開(kāi)發(fā)人員之間的交流。盡管cvs本身無(wú)法代替交流,但commit log,以及cvs系統(tǒng)獲取任意版本之間差異的能力,能夠幫助開(kāi)發(fā)人員了解對(duì)方的想法,并促進(jìn)他們共同提高。
- 降低程序開(kāi)發(fā)人員的門(mén)檻。由于提供了許多非常方便的協(xié)同開(kāi)發(fā)手段, 使用cvs能夠減少協(xié)同開(kāi)發(fā)所需要的磨合期,同時(shí),不同層次的開(kāi)發(fā)者之間由于能夠進(jìn)行經(jīng)常性的溝通,從而把編碼過(guò)程從技能型工作向熟練性工作又推進(jìn)了一步。這意味著高層次的開(kāi)發(fā)人員能夠去進(jìn)行更能發(fā)揮他們特長(zhǎng)的工作,而新手則可以很快地融入到日常的開(kāi)發(fā)活動(dòng)中來(lái),從而提高勞動(dòng)生產(chǎn)率,降低開(kāi)發(fā)成本。
cvs是一項(xiàng)開(kāi)放性很強(qiáng)的工具,它可以被非常容易地訂制。一般來(lái)說(shuō),cvs服務(wù)器會(huì)架設(shè)在一臺(tái)Unix主機(jī)上(我們推薦使用FreeBSD),通過(guò)使用腳本語(yǔ)言(例如,Perl),cvs能夠完成一些額外的功能。
commit mail是commit log在郵件系統(tǒng)上的延伸。下面是一封典型的commit mail,它來(lái)自FreeBSD開(kāi)發(fā)團(tuán)隊(duì):
phk 2003/10/21 23:32:20 PDT
FreeBSD src repository
Modified files: sys/geom geom_io.c Log: Forgotten commit: If a provider has zero sectorsize, it is an indication of lack of media.
Tripped up: peter
Revision Changes Path 1.50 +3 -6 src/sys/geom/geom_io.c |
我們看到,上面的這封commit mail中提到了開(kāi)發(fā)者(phk)、提交時(shí)間(太平洋時(shí)間2003年10月21日23:32:20)、涉及的代碼庫(kù)名字(FreeBSD src repository)、修改過(guò)的文件(sys/geom的geom_io.c)以及commit log。最后,commit log還提到了提交后文件的最新版本(1.50),修改規(guī)模(+3 -6)以及代碼的實(shí)際路徑。
實(shí)現(xiàn)上面的功能并不復(fù)雜,實(shí)際上,您只需要下載一套經(jīng)過(guò)定制的FreeBSD cvs代碼庫(kù)(壓縮包不超過(guò)40KB),并作少量的調(diào)整,就能夠直接使用這些功能(我們將在不久以后發(fā)布這些內(nèi)容)。進(jìn)行這些訂制甚至不需要基本的Perl和C/C++常識(shí)就能夠完成——當(dāng)然,我想這樣的常識(shí)對(duì)于軟件開(kāi)發(fā)人員來(lái)說(shuō),并不算是很高的要求。
commit mail可以通過(guò)郵件列表發(fā)給全體開(kāi)發(fā)者。許多大的軟件公司,以及開(kāi)放源代碼團(tuán)體,都采用這樣的方式來(lái)協(xié)調(diào)開(kāi)發(fā)活動(dòng)。
傳統(tǒng)的軟件工程中,測(cè)試發(fā)生在連調(diào)之后。這么做的理論依據(jù)是,測(cè)試依賴于一份一致的、至少能夠正常編譯并啟動(dòng)的代碼。而這個(gè)條件在連調(diào)之前是無(wú)法滿足的。
然而在有了版本控制系統(tǒng)(如, cvs)之后,連調(diào)變成了開(kāi)發(fā)中的日常行為。代碼幾乎在每一時(shí)刻都處于高度的一致?tīng)顟B(tài),甚至在許多時(shí)候,代碼會(huì)處于可用狀態(tài),從而為測(cè)試創(chuàng)造非常有利的條件。
許多大型開(kāi)發(fā)團(tuán)隊(duì)會(huì)使用一臺(tái)甚至多臺(tái)被稱作TinderBox的機(jī)器來(lái)完成每日構(gòu)建和測(cè)試。簡(jiǎn)單的每日構(gòu)建流程如下:
- 測(cè)試工程師,或代碼復(fù)審員從代碼庫(kù)中提取一份代碼的快照
- 相關(guān)人員在TinderBox上進(jìn)行編譯
- 編譯的任何錯(cuò)誤被追蹤,測(cè)試工程師或代碼復(fù)審員將回退代碼到上一次能夠成功編譯的點(diǎn),并與此后進(jìn)行代碼提交的其他開(kāi)發(fā)者進(jìn)行聯(lián)系,解決問(wèn)題
- 測(cè)試工程師對(duì)于代碼進(jìn)行測(cè)試
其中,第一、二步是可以通過(guò)腳本定時(shí)、自動(dòng)完成的,不需要人工干預(yù)。第三步中的編譯錯(cuò)誤在軟件開(kāi)發(fā)中偶爾會(huì)發(fā)生(這可能來(lái)自于沖突合并時(shí)引發(fā)的問(wèn)題,但由于開(kāi)發(fā)人員的本地測(cè)試,這種文體不會(huì)是經(jīng)常性的),習(xí)慣上,這些錯(cuò)誤會(huì)由代碼復(fù)審員去追蹤和處理,并交給相關(guān)的開(kāi)發(fā)人員解決。
測(cè)試工程師可以將編譯好的版本交付給一個(gè)測(cè)試組,甚至用戶去進(jìn)行測(cè)試。測(cè)試工程師可能隨時(shí)發(fā)布軟件的“快照”版本。
實(shí)際上在許多大公司中,每日構(gòu)建是非常“家常便飯”的事情。我們看到的Internet Explorer版本,如6.0.2600或者類(lèi)似6.0 Build 2600中的2600的意思就是這份代碼之前已經(jīng)經(jīng)歷了2600次每日構(gòu)建操作(當(dāng)然,中間肯定進(jìn)行過(guò)不少修改,而且,也不排除這個(gè)2600是故意湊整得到的,但總之,他們進(jìn)行了相當(dāng)多的日常構(gòu)建工作)。
在軟件開(kāi)發(fā)的后期,由于發(fā)布的迫在眉睫,每日構(gòu)建很可能會(huì)演化為持續(xù)構(gòu)建,即,每次commit觸發(fā)一次構(gòu)建操作。所有問(wèn)題立即得到反饋。
為了支持每日構(gòu)建或持續(xù)構(gòu)建,比較理想的方法是采用Makefile完成構(gòu)建操作。對(duì)于Unix系統(tǒng),make工具通常是pmake(BSD Make)或gmake(GNU Make);對(duì)于Visual C++,則是nmake。大的開(kāi)發(fā)團(tuán)體通常使用一組腳本來(lái)完成所有的make操作,而對(duì)于中小型項(xiàng)目,手工地使用類(lèi)似VC++這樣的IDE本身的構(gòu)建功能也是可以接受的。
基本的每日構(gòu)建規(guī)范如下:
基本的每日構(gòu)建和測(cè)試注意事項(xiàng)
- 避免在存在開(kāi)發(fā)人員大規(guī)模地進(jìn)行commit時(shí)提取快照。此時(shí)提取的中間結(jié)果很可能有問(wèn)題,并導(dǎo)致每日構(gòu)建從頭做起。通常,測(cè)試工程師和代碼復(fù)審員在多數(shù)開(kāi)發(fā)人員下班的時(shí)候開(kāi)始每日構(gòu)建,因此,每日構(gòu)建有時(shí)也被稱作每晚構(gòu)建(Nightly Build)。
- 標(biāo)記(tag)每日構(gòu)建中能夠正確編譯的版本。這將減少第二天每日構(gòu)建中的麻煩。
- 及時(shí)通知造成問(wèn)題的開(kāi)發(fā)人員,即,所涉及代碼的提交者。某些公司甚至要求員工開(kāi)著手機(jī)和尋呼機(jī),以保證工期。
|
作為commit mail的有效補(bǔ)充,許多項(xiàng)目開(kāi)發(fā)組會(huì)建立郵件列表來(lái)傳遞一些相關(guān)的信息。測(cè)試日?qǐng)?bào)通常會(huì)發(fā)給整個(gè)開(kāi)發(fā)團(tuán)隊(duì)的參加人員,此外,保留一個(gè)出現(xiàn)過(guò)的問(wèn)題記錄,對(duì)于測(cè)試環(huán)節(jié)也會(huì)有相當(dāng)大的好處——這些問(wèn)題在隨后被反復(fù)測(cè)試,以保證最終的RELEASE不出現(xiàn)這些問(wèn)題。
每日構(gòu)建并不是可有可無(wú)的工作,作為日常測(cè)試的重要手段,每日構(gòu)建能夠有效地幫助管理者了解工程進(jìn)度,幫助開(kāi)發(fā)者盡早發(fā)現(xiàn)問(wèn)題,同時(shí),也會(huì)促進(jìn)開(kāi)發(fā)組中的交流。
三個(gè)文件的“最新版本”分別是1.5, 1.3, 1.4,但最新版本不一定是我們需要的。在這種情況下,版本控制系統(tǒng)提供了一個(gè)非常重要的機(jī)制——版本標(biāo)記。例如,我們目前已經(jīng)確認(rèn)三個(gè)文件的1.4, 1.3, 1.4組合在一起能夠正常運(yùn)行,于是我們?cè)谌齻€(gè)文件的這些版本上標(biāo)注標(biāo)記TAG_1,如下圖:

圖4. TAG_1標(biāo)記被打到三個(gè)文件的不同版本上
需要說(shuō)明的是,標(biāo)記是可以被移動(dòng)的。這意味著一旦發(fā)現(xiàn)標(biāo)記打錯(cuò)了,可以把標(biāo)記移動(dòng)到別的位置。但在實(shí)踐中,標(biāo)記往往同另一個(gè)非常重要的版本控制機(jī)制——代碼分支一起使用。在詳細(xì)討論標(biāo)記(tag)的重要意義之前,我們先來(lái)看看代碼分支時(shí)什么:

圖5. 比較復(fù)雜的情形,一個(gè)正在開(kāi) 發(fā)的項(xiàng)目中的某個(gè)文件,已經(jīng)完成了 2.0和2.1的發(fā)布; 其中,BP是指劃分 分支的切分點(diǎn)(Branchpoint)
所謂代碼分支是版本控制中的一個(gè)非常關(guān)鍵的概念。當(dāng)開(kāi)發(fā)到某個(gè)階段的時(shí)候,可以交付一個(gè)版本,而主要的開(kāi)發(fā)者則把精力投入到最新版本的開(kāi)發(fā)中。第一個(gè)交付分支(2.0)中的一些問(wèn)題,以及引入的新功能隨后在RELENG_2分支中被修正,公司決定發(fā)布2.1版本;此后,2.x中的問(wèn)題繼續(xù)在RELENG_2中被修正,而一些安全更新,則被合并到2.1-RELEASE中(RELENG_2_1)。
圖5展示的是一個(gè)文件上的版本分支。實(shí)際的軟件工程項(xiàng)目的源代碼會(huì)由大量文件組成,盡管在本質(zhì)上分支是針對(duì)每一個(gè)文件說(shuō)的,但在被標(biāo)注了同一分支名稱的文件,就像版本標(biāo)記一樣,能夠表達(dá)一組特定版本文件的集合。
cvs的版本分支功能有一個(gè)很大的缺陷,即,大量文件的切分點(diǎn)(Branchpoint, 即某一個(gè)分支最初的版本號(hào))在cvs中很難被指定(cvs支持按某一分支、某一特定時(shí)間、某一特定版本來(lái)提取文件,但通常不同的文件的版本號(hào)并不統(tǒng)一,特別是在大型項(xiàng)目中,肯定有某些文件因?yàn)楸惶峤坏拇螖?shù)很多,而版本號(hào)很“高”的情況)。為了消除這個(gè)缺陷,在實(shí)踐上,我們采用版本標(biāo)記與分支結(jié)合的方法,即,在劃分新的分支之后,在這一分支的這些文件的版本上增加一個(gè)版本標(biāo)記。
例如:對(duì)于軟件的2.0版,在劃分時(shí),將切分出RELENG_2(2.x),RELENG_2_0(2.0)兩個(gè)分支,而這時(shí)的文件的版本,同時(shí)被打上一個(gè)RELENG_2_0_0_BP的標(biāo)記。這樣一來(lái),在以后比較版本時(shí),我們可以使用RELENG_2_0_0_BP來(lái)指定這個(gè)版本。當(dāng)不同的分支又增加了許多修改之后,這個(gè)標(biāo)記將極大地減輕代碼復(fù)審員的工作量。
注意,代碼分支并不僅限于版本上的用法。事實(shí)上,基于同一代碼基礎(chǔ)的多個(gè)不同的軟件也可以采用代碼分支的方法進(jìn)行開(kāi)發(fā)。而最終,這些代碼還可以合并為一個(gè)。
您可能已經(jīng)注意到最左邊的一組版本序列:1.1, 1.2, 1.3, 1.4, 1.5. 1.6。在cvs中,這一序列被稱為“主分支(MAIN Branch)”。盡管并非必須,但習(xí)慣上,主分支通常是活躍的開(kāi)發(fā)分支。在這個(gè)分支中,人們不斷地引入最新的特性,當(dāng)然,不可避免地,這也可能引發(fā)一些問(wèn)題,而這些引入主分支的問(wèn)題在隨后將被追蹤、修訂。經(jīng)過(guò)一段時(shí)間之后,被“沉淀”下來(lái)的代碼可以進(jìn)入另一個(gè)叫做“穩(wěn)定分支”的代碼系。
這樣的開(kāi)發(fā)模式通常被稱作“多頭并進(jìn)”模式,這樣的模式在許多開(kāi)放源代碼的軟件開(kāi)發(fā)中非常常見(jiàn),例如,Linux的單、雙號(hào)版本、FreeBSD的-STABLE和-CURRENT[2],等等。在一般的商業(yè)軟件開(kāi)發(fā)中,這種模式也相當(dāng)常見(jiàn),特別是在大公司的開(kāi)發(fā)中。擁有多頭并進(jìn)這一能力對(duì)于大型軟件的開(kāi)發(fā)尤為重要,因?yàn)榇笮蛙浖芸赡馨喈?dāng)多的模塊,通過(guò)版本控制,問(wèn)題能夠很容易地被整個(gè)開(kāi)發(fā)團(tuán)隊(duì)追蹤。
多頭并進(jìn)的開(kāi)發(fā)環(huán)境中,開(kāi)發(fā)人員可以在粗略熟悉了某個(gè)分支的代碼體系的情況下參與開(kāi)發(fā)或維護(hù),這意味著,即使某個(gè)代碼分支的維護(hù)人員突然離去,其他人也不用擔(dān)心通盤(pán)閱讀不同分支的代碼可能造成的理解困難,換言之,對(duì)于新的維護(hù)人員的要求被降低,從而,軟件的開(kāi)發(fā)和維護(hù)過(guò)程能夠更為有序地進(jìn)行。
據(jù)我所知,F(xiàn)reeBSD的軟件開(kāi)發(fā)過(guò)程極大地得益于多頭并進(jìn)的開(kāi)發(fā)模式。下面簡(jiǎn)單地介紹一下FreeBSD所采用的軟件開(kāi)發(fā)模式:
案例:FreeBSD開(kāi)發(fā)模式中對(duì)于多頭并進(jìn)的應(yīng)用
FreeBSD包括了兩個(gè)主要的開(kāi)發(fā)分支:4-STABLE和5-CURRENT,以及若干安全分支。其中,4-STABLE(RELENG_4分支)代表的是FreeBSD 4.x系列的開(kāi)發(fā),其關(guān)注的焦點(diǎn)是系統(tǒng)的穩(wěn)定性和性能;5-CURRENT(HEAD分支)代表的是FreeBSD 5.x系列的開(kāi)發(fā),它關(guān)注的焦點(diǎn)是盡可能多地引入最新的操作系統(tǒng)特性,全新的設(shè)計(jì)思想,等等。除此之外,還有一些被稱作安全分支的分支,它們分別代表FreeBSD 2-STABLE, 3-STABLE, 4.6-RELEASE, 4.7-RELEASE, 4.8-RELEASE以及即將推出的4.9-RELEASE等等,但這些分支完全不引入任何新的特性,只有安全更新能夠被加入到這些分支中。
FreeBSD的“安全分支”是一個(gè)非常重要的概念,在FreeBSD的開(kāi)發(fā)中,這些分支基本上只由一個(gè)包括了少量開(kāi)發(fā)者(目前只有兩人)的,被稱為“FreeBSD安全官”的團(tuán)隊(duì)維護(hù)。對(duì)于很多用戶來(lái)說(shuō),他們并不在乎操作系統(tǒng)是否擁有新的特性——他們不愿意嘗試新版本的軟件,因?yàn)楝F(xiàn)有的系統(tǒng)工作的非常好。這些用戶使用“安全分支”的FreeBSD操作系統(tǒng),因?yàn)樗軌蛱峁┍匾陌踩?,而操作系統(tǒng)特性并不會(huì)因此發(fā)生變化(無(wú)論這種變化是否能夠改進(jìn)性能,或提供一些眩目的功能,甚至支持新的硬件,因?yàn)橛脩舻南到y(tǒng)已經(jīng)放在那里了)。
CURRENT分支走的是另外一個(gè)極端。所有的新特性,一旦被特定的工作人員(committer)測(cè)試通過(guò)(大的變化需要核心團(tuán)隊(duì),即core team的批準(zhǔn),但這種情況并不是很多),就允許被引入CURRENT分支。盡管CURRENT分支在絕大多數(shù)時(shí)間都能夠被正確地編譯,但引入新特性有時(shí)會(huì)不可避免地帶來(lái)一些問(wèn)題,例如硬件適應(yīng)性問(wèn)題。
在這兩個(gè)極端之間,有一條中間路線,即STABLE分支。在CURRENT分支中提交的代碼通常會(huì)被指定一個(gè)MFC(Merge from -CURRENT)時(shí)間,在這個(gè)時(shí)間之后,如果沒(méi)有人提交關(guān)于代碼的問(wèn)題,則這些代碼會(huì)被引入STABLE分支。
這樣,STABLE分支的代碼幾乎都是經(jīng)過(guò)相當(dāng)長(zhǎng)時(shí)間測(cè)試的代碼,對(duì)于大多數(shù)用戶來(lái)說(shuō),STABLE分支是一個(gè)很好的選擇。
一般來(lái)說(shuō),F(xiàn)reeBSD中的代碼會(huì)經(jīng)歷下面的歷程:
- 代碼被引入CURRENT分支
- 相關(guān)開(kāi)發(fā)者獲得來(lái)自用戶的反饋; 在確認(rèn)基本沒(méi)有問(wèn)題的情況下,代碼被引入STABLE分支
- 大多數(shù)最終用戶使用STABLE分支的代碼來(lái)支持他們的計(jì)算機(jī)
我們可以看到,上面的開(kāi)發(fā)模式同時(shí)照顧到了開(kāi)發(fā)者和用戶群體的利益。一方面,活躍的開(kāi)發(fā)不會(huì)因?yàn)橛绊懙搅舜罅康钠胀ㄓ脩舳獾街肛?zé);另一方面,在開(kāi)發(fā)分支(CURRENT)的代碼經(jīng)過(guò)一段時(shí)間被引入STABLE,最終用戶能夠得到那些新的操作系統(tǒng)功能。 |
事實(shí)上,上述開(kāi)發(fā)模式已經(jīng)被證明是相當(dāng)成功的。由于開(kāi)發(fā)過(guò)程中每一天都有相當(dāng)多的人對(duì)新的CURRENT和STABLE分支的代碼進(jìn)行測(cè)試,因此,在最近的幾年中,F(xiàn)reeBSD的開(kāi)發(fā)一直呈現(xiàn)著良好的態(tài)勢(shì)。
許多參與過(guò)大型項(xiàng)目開(kāi)發(fā)的讀者可能都經(jīng)歷,至少是聽(tīng)說(shuō)過(guò)特性凍結(jié)和代碼凍結(jié)這樣一個(gè)概念。所謂“特性凍結(jié)”實(shí)際上是一個(gè)開(kāi)發(fā)者之間的約定,在這個(gè)階段中,不再允許添加新的功能。
特性凍結(jié)(Feature Freeze)通常在一個(gè)開(kāi)發(fā)分支(Development Branch)躍變?yōu)榻桓斗种?Release Branch)的時(shí)候開(kāi)始。之所以需要特性凍結(jié),是因?yàn)樵黾有碌奶匦院苡锌赡芤胄碌膯?wèn)題,而這將給代碼復(fù)審帶來(lái)沉重的負(fù)擔(dān),甚至導(dǎo)致一次不成功的最后交付。
當(dāng)然,對(duì)于那些已經(jīng)明確地定義了特性表的小型軟件工程項(xiàng)目(例如,傳統(tǒng)的瀑布開(kāi)發(fā)模型)來(lái)說(shuō),特性凍結(jié)沒(méi)有什么意義,因?yàn)樵谶@些工程中,詳細(xì)設(shè)計(jì)完全是在編寫(xiě)代碼之前進(jìn)行的,這意味著,代碼將要寫(xiě)成什么樣子已經(jīng)在詳細(xì)設(shè)計(jì)中明確地定義。但在實(shí)際的項(xiàng)目中,詳細(xì)設(shè)計(jì)往往會(huì)包括兩類(lèi)不同的類(lèi)型要求——一部分是“必須實(shí)現(xiàn)的特性(Must have feature)”,另一部份則是“希望實(shí)現(xiàn)的特性(Desired Feature)”。在交付之前,所有“希望實(shí)現(xiàn)的特性”都會(huì)在特性凍結(jié)時(shí)被明確成“實(shí)現(xiàn)特性”和“不實(shí)現(xiàn)特性”。
我們注意到,這種情況下,某些特性被延遲到接近交付的時(shí)候才被明確成“必須實(shí)現(xiàn)”,而另一些“希望實(shí)現(xiàn)的功能”則被作為“不實(shí)現(xiàn)”,從而轉(zhuǎn)化為我們先前熟悉的樣子,即詳細(xì)設(shè)計(jì)文檔中明確地定義了軟件中的所有特性。
這樣做的結(jié)果是軟件工程項(xiàng)目具有更大的靈活性。由客戶需求產(chǎn)生的功能設(shè)計(jì),很顯然地,應(yīng)該列為“必須實(shí)現(xiàn)的特性”,而那些開(kāi)發(fā)團(tuán)隊(duì)提出的能夠提高軟件整體可擴(kuò)展性、可伸縮性或其他性能的特性,則應(yīng)列為“希望實(shí)現(xiàn)的特性”。在特性凍結(jié)之后,整個(gè)開(kāi)發(fā)團(tuán)隊(duì)將專注于那些“實(shí)現(xiàn)特性”(盡管這些特性可能還沒(méi)有被正式的實(shí)現(xiàn))更加穩(wěn)定,從而將生產(chǎn)出更高質(zhì)量的軟件。
根據(jù)我個(gè)人的經(jīng)驗(yàn),特性凍結(jié)應(yīng)該發(fā)生在預(yù)期編碼時(shí)間已經(jīng)用去大約2/3的時(shí)候。這時(shí),項(xiàng)目經(jīng)理應(yīng)該組織開(kāi)發(fā)人員舉行一次會(huì)議討論特性凍結(jié),而在特性凍結(jié)之后,在軟件正式交付之前,任何開(kāi)發(fā)人員都不應(yīng)該再去考慮那些被列為“不實(shí)現(xiàn)特性”的功能。
代碼凍結(jié)是一個(gè)與特性凍結(jié)類(lèi)似的概念,在這個(gè)階段,只允許對(duì)被凍結(jié)代碼分支中的錯(cuò)誤進(jìn)行修正,而不允許任何其他的、涉及功能的修改。實(shí)踐上,這個(gè)過(guò)程中,只有交付工程師(通常是一個(gè)或多個(gè)對(duì)于整個(gè)系統(tǒng)架構(gòu)非常了解的、有豐富開(kāi)發(fā)經(jīng)驗(yàn)的代碼復(fù)審員)被授予審查和批準(zhǔn)代碼提交的權(quán)力,任何代碼修改,只要沒(méi)有經(jīng)過(guò)交付工程師的批準(zhǔn),就不能被提交到代碼庫(kù)中。
代碼凍結(jié)的時(shí)間一般不需要太長(zhǎng)。對(duì)于中等規(guī)模的項(xiàng)目,這一過(guò)程通常會(huì)持續(xù)一至兩周,對(duì)于大型項(xiàng)目,這一過(guò)程則有可能持續(xù)一個(gè)月甚至更長(zhǎng)的時(shí)間。在這個(gè)階段,交付工程師主要負(fù)責(zé)代碼復(fù)審,而測(cè)試工程師則有責(zé)任及時(shí)反饋集中的測(cè)試中暴露出的問(wèn)題,并與相關(guān)的開(kāi)發(fā)人員聯(lián)系、解決這些問(wèn)題。
技術(shù)上,代碼凍結(jié)可以通過(guò)修改cvs中的配置來(lái)實(shí)現(xiàn)。不過(guò),更好的辦法是通過(guò)制度來(lái)保證代碼凍結(jié)。
交付工程的好壞在某種意義上,是直接關(guān)系到用戶利益的部分。前面已經(jīng)說(shuō)了相當(dāng)多的關(guān)于軟件開(kāi)發(fā)過(guò)程中通過(guò)版本控制技術(shù)來(lái)提高開(kāi)發(fā)效率的技巧和方法,這里我將繼續(xù)說(shuō)一說(shuō)交付工程。
前面提到了每日構(gòu)建中在代碼上適當(dāng)?shù)卮蛏习姹緲?biāo)記,以及在劃分版本分支時(shí)在切分點(diǎn)上增加版本標(biāo)記,這些對(duì)于交付工程都具有非常重要的意義。交付工程,在軟件工程項(xiàng)目中是一個(gè)融合了代碼復(fù)審和集中測(cè)試的重要階段。
交付工程的一般程序
- 在項(xiàng)目進(jìn)行到某個(gè)特定的階段,交付工程師同其他主要的項(xiàng)目管理者商議決定宣布代碼凍結(jié)和交付工程開(kāi)始。
- 交付工程師對(duì)代碼進(jìn)行集中的復(fù)審,而測(cè)試工程師則組織進(jìn)行大規(guī)模的集中測(cè)試。
- 交付工程過(guò)程中,交付工程師每隔一段時(shí)間發(fā)布一個(gè)“交付候選版本(Release Candidate)”,測(cè)試工程師跟隨安裝并測(cè)試這些交付候選版本。
- 此間,測(cè)試工程師隨時(shí)向交付工程師反饋問(wèn)題,交付工程師將問(wèn)題分類(lèi)、整理,并約見(jiàn)相應(yīng)的開(kāi)發(fā)人員,予以解決。
- 最終,某個(gè)“交付候選版本”被最終指定為交付版本(Release)。代碼凍結(jié)結(jié)束,軟件被交付給客戶。
|
一些讀者可能已經(jīng)注意到,交付工程的絕大多數(shù)任務(wù)事實(shí)上已經(jīng)被融入了我前面所描述的開(kāi)發(fā)過(guò)程——在編碼階段的整個(gè)過(guò)程中,代碼復(fù)審、問(wèn)題反饋和測(cè)試一直是持續(xù)地進(jìn)行的,只是,在“交付工程”階段,代碼復(fù)審和測(cè)試被提升到了一個(gè)更為核心的地位,在這一階段,開(kāi)發(fā)的重要任務(wù)是查錯(cuò)和排錯(cuò),而不再是將軟件的功能推向一個(gè)嶄新的水平。
前面我們已經(jīng)介紹了通過(guò)引入版本控制系統(tǒng)改善軟件工程中編碼和測(cè)試階段過(guò)程的一些方法。這些方法來(lái)自于我本人所參與的,以及通過(guò)一些其他途徑了解到的實(shí)際項(xiàng)目的開(kāi)發(fā)經(jīng)驗(yàn)。文中介紹的內(nèi)容以cvs (Concurrent Version System)為主,這是因?yàn)閏vs比較容易得到(它本身是開(kāi)放源代碼軟件),并且,比較成熟。另外,由于關(guān)于cvs本身的細(xì)節(jié)并沒(méi)有涉及很深,因此,讀者也很容易將這些經(jīng)驗(yàn)套用在其他版本控制系統(tǒng),如Bit Keeper, Perforce, Clear Case等等之上。
引入版本控制、每日構(gòu)建之后,項(xiàng)目管理人員和開(kāi)發(fā)者可以明顯地感受到以下改善:
- 問(wèn)題能夠盡早暴露,并被解決。測(cè)試過(guò)程被滲透到開(kāi)發(fā)過(guò)程中,有利于及早發(fā)現(xiàn)問(wèn)題。日常的每日構(gòu)建和測(cè)試意味著更多的測(cè)試者可以盡早地參加到測(cè)試工作中,并與開(kāi)發(fā)團(tuán)隊(duì)并肩作戰(zhàn),及時(shí)地為他們提供第一手的資料;而最終的交付工程中進(jìn)行的測(cè)試,只是驗(yàn)證軟件的質(zhì)量確實(shí)達(dá)到了設(shè)計(jì)要求,而不再肩負(fù)發(fā)現(xiàn)問(wèn)題的重大責(zé)任,這意味著很難出現(xiàn)最后階段發(fā)現(xiàn)嚴(yán)重問(wèn)題而導(dǎo)致工期大大加長(zhǎng)的尷尬局面。
- 每個(gè)人做了多少工作在版本控制系統(tǒng)中能夠非常有效地體現(xiàn),這對(duì)于更客觀地評(píng)價(jià)一位開(kāi)發(fā)者的能力,明確獎(jiǎng)懲和提高員工積極性,都有非常積極的意義。
- 管理者能夠隨時(shí)觀察工程的進(jìn)度情況,并且,每日構(gòu)建的到的版本也可以交付給客戶,從而改善軟件開(kāi)發(fā)團(tuán)隊(duì)同客戶之間的交流。
- 版本控制系統(tǒng)記錄的軟件工程的進(jìn)展,對(duì)于今后的開(kāi)發(fā)將是一筆寶貴的財(cái)富。
最后,讓我們用圖片的形式,重新描述一下上面的那些過(guò)程:

圖6. 日常開(kāi)發(fā)過(guò)程

圖7. 交付工程
這樣,我們已經(jīng)粗淺地討論了關(guān)于并行版本控制技術(shù)如何改變軟件開(kāi)發(fā)過(guò)程的一些大致的方法,以及其中的一些先進(jìn)思想。在文中我盡可能地回避了類(lèi)似cvs系統(tǒng)的操作具體細(xì)節(jié),并且,避開(kāi)了一些cvs特有的功能?;旧希疚闹刑峁┑姆椒?,特別是其中的一些思想,能夠適應(yīng)任何一種版本控制系統(tǒng),因此,這些方法具有相當(dāng)大的普遍意義。
[1] CVS - 并行版本系統(tǒng) http://www. [2] FreeBSD 4.4交付工程, Murray Stokely [3] CVS使用手冊(cè)- CVS WinCVS CVSWeb CVSTrac,車(chē)東 [4] 4.3BSD交付工程, Marshall Kirk McKusick, Michael J. Karels, and Keith Bostic
作者簡(jiǎn)介
李鑫,北京工業(yè)大學(xué)計(jì)算機(jī)學(xué)院2000級(jí)學(xué)生,目前擔(dān)任計(jì)算機(jī)學(xué)院學(xué)生科協(xié)主席、放飛技術(shù)網(wǎng)技術(shù)總監(jiān)。您可以通過(guò)電子郵件 ( delphij@ 與他聯(lián)系 )。
版權(quán)聲明
本文版權(quán)歸原作者和放飛技術(shù)網(wǎng)共同所有,轉(zhuǎn)載本文必須同我們聯(lián)系并獲得同意(書(shū)面或PGP簽署的許可)。 |