聊聊代碼提交那些事

文/莊表偉

華爲公司內源社區平臺架構師;開源社理事



1997 年畢業於華東師範大學,曾任盛大創新院的高級研究員、印客網的技術總監。多年來一直在編程的第一線,並以 Coding 爲最大的樂趣。著有一本電子書《開源思索集》,在豆瓣、簡書、亞馬遜、多看閱讀等平臺均可以直接下載。目前專注於華爲內源社區的建設。




解題


爲何要聊“代碼提交”這麼小的一件事情?隨着團隊人數越來越多,提交代碼這件事,變得複雜起來。甚至極端一點說,任何複雜度的軟件項目,也無非是要管好兩件事情:需求和代碼。


順着這條線索,我們可以觀察到一種螺旋上升的現象:


  • 需求越來越多、越來越複雜;

  • 代碼越來越多、越來越複雜;

  • 添加更多的人手開發軟件;

  • 更多的人手會犯下更多的錯誤;

  • 選擇制定某種管理規範;

  • 通過工具執行某種規範;

  • 圍繞一組工具的特性,形成某種工具與管理的共生平衡;


需求再次爆炸……


從個體戶到小團隊、從小團隊到大兵團,需求的數量、人員的數量、代碼的數量,都會有百倍、千倍甚至更加驚人的增長。在這種變化的過程中,不僅僅是人數需要增加,相應的工具、以及管理流程,也要發生變化。


本文主要探討:如何管好代碼這件事情,這篇文章不會介紹基礎知識,而僅僅是一種邏輯上的梳理。關於:代碼提交、代碼管理、團隊管理、研發質量管理等等內容。


個體戶的幸福生活


在單槍匹馬乾活的日子裏,很多事情都相當簡單。甚至,在當年我們都不知道什麼叫版本管理。我們在自己的機器上寫代碼,當然也在自己的機器上完成編譯,然後自己試用一下,算是“測試”。


如果是服務器端的開發,我們就直接登錄到服務器上,打開 vim,直接寫代碼,寫完了保存就 OK。


當年我還在寫 PHP 程序的時候,最喜歡的編輯器是 EditPlus,因爲它支持 FTP 鏈接到服務器,直接就能修改服務器上的文件,Ctrl+S 以後,再刷一下瀏覽器,結果就出來了。


這樣的開發習慣,當我成爲 Java 程序員的時候,也影響到了我的技術選型。我最喜歡的 java web server 是 resin。最大的一個原因是:使用 resin 的服務,修改 java 代碼,也不必編譯、然後再重啓服務。


爲何需要版本管理?


當我們的項目,越來越複雜、代碼越來越多時,就開始需要版本管理的工具了。


在事情還不那麼複雜的時候,我們可以將修改代碼的原因,記錄在註釋裏。但是,如果一個文件被反覆修改,那麼將修改理由記錄在提交說明裏,將是一個更好的選擇。


一個原本正確的代碼,被改壞了,需要回退。這個時候我們不能僅僅依靠自己的記憶力,恢復代碼到原來的樣子。


當協同開發的人超過一個,就可能會出現:一個文件,曾經被多個人修改過的情況。這時:找到當時那個幹了壞事的傢伙,就變得非常重要。


所以,我們至少應該能夠有地方記錄:誰,在什麼時候,因爲什麼理由,修改了一個文件(或者修改了一組文件)。


早期的版本管理工具爲何選擇那麼變態的工作模式?


我曾經用過的最早的版本管理工具,叫做 Visual SourceSafe,簡稱 VSS。也許是資歷太淺,VSS 就是我用過的,最難用的版本管理工具了。


爲了保證源代碼的安全,VSS 採用了最爲極端的獨佔工作模式。當我想要修改某個文件的時候,就把這個文件 check out 出來,然後在我修改完成,並再次 check in 之前,任何其他人都無法 check out 這個文件,當然也無法修改這個文件。


因此,當時最常見的辦公室對話是:是誰,又簽出了文件?那個誰誰誰,你快點改啊,我也要改這個文件!


事隔多年以後,我在維基百科上看到:“VSS 雖然是微軟公司的產品,但微軟內部卻很少使用它。” 真是欲哭無淚。


本質上,VSS 是一個將代碼安全的需求,置於團隊研發效率之上的工具。幸好,這個產品已經沒人用了。


爲何需要分支和版本號?


在人數很少的時候,VSS 實際上也能工作得很好。但是,當軟件越來越複雜,需求越來越多的時候,我們只能招聘更多的工程師,並且催促他們儘快上手開始寫代碼。


爲了幫助一羣人,能夠順暢地協作,我們需要創造諸多的概念、流程、工具與協作方法。


1、版本號:實質上對爲一個階段的工作成果命名,按照某種慣例,特別不成熟的成果,我們會命名爲 0.1,甚至 0.01;第一個可以正式發佈的版本,我們會命名爲 1.0。預發佈的版本,我們會稱之爲 1.01-alpha;然後是 1.01-beta;最終我們會發佈一個 1.01-final;


這些做法實際上意味着:版本號具有隱含的質量屬性。這樣的質量屬性,即方便對外公告,也方便內部管理;更進一步,從蒐集 bug 的角度來說,我們也可以較爲準確地將某一個 bug,記錄在特定的版本之下,直到他們在每一個版本之後,被解決掉。


2、分支:實質上是爲了更多的人並行工作,而出現的概念。當然,這一概念,需要版本管理工具的支持。


在某個版本發佈之後,並不意味着這一版本已經完美無缺,所以,我們需要維護一個分支,在其中只添加 bugfix 類的改進,而不會增加新的功能特性。


而另一方面,我們會有一個 master 分支,開發人員可以盡情先把功能特性提交上去,而不必太過顧慮產品的穩定性。


在人數更多的時候,我們會創建更多的分支,比如:一個大項目組,可以分爲 3 個小組,每個小組有一個自己的分支,他們先在自己的分支上工作一段時間。然後再將這一個小組的工作,批量匯入主幹分支。


開源社區的一大創造


在工具改進的過程中,工作流程也在改進,起因還是因爲人容易犯錯誤,在開源社區,這樣的問題尤其突出。如果是在公司裏,大家都是擡頭不見低頭見的同事,當初也是經過足夠的面試,具備基本的能力,才能進來的。但是在開源社區,一個從來沒有見過的 ID 號,想要向我的項目提交代碼,我怎麼可能放心?


因此,在開源社區最初的形態:mailing list 中,就已經形成了一套行之有效的代碼提交規範。


想要向某一個項目提交自己的代碼,首先需要訂閱那個項目的郵件列表,跟裏面的 committer 混個臉熟。提交代碼,其實就是發一封郵件。在郵件裏,要清楚地介紹自己打算幹些啥,簡明扼要,而且最好不要一下子就“搞一個大動作”。一開始,大家都不熟,你上來就想要貢獻一個“幾千行代碼的大特性”,誰都沒法信任你。


最好是從 bugfix 開始,所以那些代碼貢獻,都被稱之爲補丁 (patch)。一個補丁,最好不要太大,幾行(最多幾十行),這樣那些大牛們纔會有心情 review 這些代碼。


因爲,實際上只有他們纔有資格向代碼庫提交代碼,所以:他們才被稱之爲 committer。所以,我查看提交日誌的時候,會發現兩個屬性:author (實際寫這段代碼的人)以及 committer (將代碼提交到代碼庫裏的人)。


而這樣的工作模式,就被稱之爲 code review。隨着開源社區的日益成熟,開源社區的這種工作模式,也開始進入企業,在企業內部貫徹 code review 的工作流,也變成了一種常態。


另一種保障質量的手段:自動化檢查


隨着團隊數量的進一步上升,僅僅依靠人類肉眼審查代碼,想要杜絕各種錯誤,其實是不可能。這時候,工具的作用再一次體現出來了。


如果一個工作,實際上是一種簡單重複勞動,那麼:就可以通過編程,將他自動化完成。例如,自動化編譯、自動化測試。


如果一個工作可以自動化完成,那麼:我們完全可以將這個工作分解得更加細緻。例如:從自動化的驗收測試,到自動化的單元測試。


如果我們可以在一個工作的各個環節都執行檢查,那麼:我們完全可以自動化地檢查所有可能檢查的部分。例如:我們不僅僅可以檢查功能,還可以檢查語法,可以檢查編程規範,檢查是否存在安全隱患等等。


如果我們可以自行一次自動化檢查,那麼:我們完全可以更加頻繁地執行這樣的檢查。例如:開發者的每一次提交,都能夠觸發一次自動化的檢查。


最爲理想的開發流程是:所有的開發工作,都能夠各自獨立進展,互不干擾。最好是每一個特性、每一個 bugfix,都有一個獨立的分支。然後,在自己的分支上,完成一套自動化檢查。再將這個分支合入主幹,再跑一遍全套的自動化檢查。這樣,我們就能知道:這一項工作確實已經完成了,而且沒有破壞任何其他的部分。


當然,事情並沒有那麼簡單,如果是小型項目,那麼每次 CI 的成本,都會非常低。速度也會快到忽略不計。但是,一旦項目變得複雜,代碼庫變得龐大,編譯與測試的時間以小時來計算時,當開發者數量增長,併發提交的人數,超過併發 CI 的服務器數量時,問題就會複雜到專門寫一本書了。


爲何從選擇 SVN 到選擇 Git?


如果 Linux 的開發者,不是成千上萬那麼多,也許 Linus 當初也不會選用 BitKeeper。當然,也不會因此出現各種風波,最後讓大神在一怒之下,10 天時間,自己擼了一個 Git 出來。


相對於 SVN,Git 有一些特別明顯的好處:


分佈式配置庫,支持離線工作,保存各種提交歷史。而且,在不存在中心倉庫的情況下,任何兩個 Git 倉庫之間,可以交換代碼。


更好的分支模型,使得創建一個分支,合併一個分支,快捷高效。當一個團隊的人數越來越多,代碼越來越複雜時,使用 SVN 分支所帶來的煩惱,就會迫使他們最終選擇 Git。


Git 有一個複雜的由 commits 組成的 DAG(有向無環圖),這樣一種數據結構,能夠更加準確的記錄實際開發過程中的各種狀況,尤其是在用到 rebase、cherry-pick、three-way merge 的特性時,會感覺非常自然。


當然,有很多不必面對這些複雜性的軟件項目,會感到 Git 過於複雜,難以理解,而且沒有必要掌握。至於那些的確需要創建諸多分支,的確存在多種代碼合併的情況,的確需要更加頻繁的運行自動化測試的團隊,選擇 Git,就會非常自然。


從 Git 到 Github、Gerrit


當出現了 Git 這樣的工具之後,我們會發現世界並沒有變得更加美好,因爲一個常見現象:過於靈活的工具,會讓人更加容易犯錯。在項目組完全使用 Git 的情況下,依然可以設計出多種多樣的工作流程。


最簡單的一種:每個人有自己的 Git 倉庫,然後各自多加幾個 remote,然後隨意地傳來傳去類似於 SVN 的集中工作流程,大家都向同一個中心倉庫提交代碼集成管理者工作流,貢獻者將代碼推送到各自的公開倉庫,然後給維護者發送郵件,請求拉取自己的更新,最後由維護者手動完成合入司令官與副官工作流,在 Git 官方網站的介紹中,描述到:“一般擁有數百位協作開發者的超大型項目纔會用到這樣的工作方式,例如著名的 Linux 內核項目”。


在 Git 出現(2005 年)之後的幾年裏,陸陸續續出現了一些新的創造,較爲突出的有兩個:Github(2008 年)、Gerrit(2008 年)。這兩個工具,本質上都是對於過於靈活的 Git 工作流程進行限制、簡化以及優化。只不過兩種方案的出發點,大不相同,導致其產品設計與功能特性,也有了諸多區別。


Github 的出發點是大量中小型開源項目的託管服務,而且,爲了形成一個完整的大社區,Github 在社交化方面,投入了大量的精力,也創造了諸多世界第一的奇蹟。


至於 Gerrit 的出發點,這是大型複雜項目的 Code Review 管理。從誕生之出,Gerrit 就是爲了像 Android 這樣的大型項目服務的。通常一個 Gerrit 實例,只服務於一個大項目。對於構造一個社區,形成某種跨項目的開源生態圈,Gerrit 並無興趣。


沒有終點的旅程


很多時候,我們會發現這樣的現象:研發工具的演進與開發模式的演進,往往呈現一種交互影響的關係。複雜的開發模式,會促使新工具的誕生。而新工具的引入,也會促使傳統的開發模式,發生變革。


在本文的前半部分,我們主要探討的是各種需求複雜化之後,如何催生了一代又一代的新工具。而另一方面,當團隊引入一種新工具之後,常常會發生的,並不是 happy ending。


一些問題的確被解決了,但是新的問題又產生了。樂觀的說,我們會發現新工具的各種能力,經過巧妙的組合,產生新的用法,甚至遠遠超過工具設計者的設想。悲觀的說,我們也會發現新工具的各種強大能力,在尚未熟練掌握的人手裏,會產生各種意想不到的災難。


在那些較爲積極進取的團隊中,新工具往往會成爲某種催化劑,他們會迅速上手,然後越玩越好。於是我們最終會發現,一切問題,究其根本,還是人的問題。


人、工具與流程,是項目管理中的三大要素,而其中人是決定因素。對於管理者而言,是否能夠正確的判斷問題出在哪裏,以及通過何種手段去解決,是最爲重要的。我們經常會遇到這樣的管理者,在遇到麻煩時,習慣性的選擇某一種手段:有些領導特別喜歡“制定管理規範”、有些領導特別喜歡“採用先進的工具”、還有些領導特別喜歡“給員工打雞血”。我們並不能簡單地說他們做錯了。但是:只會單一手段的領導,通常更容易犯錯。


總而言之,在軟件開發的過程中,爲目前的團隊選擇合適的工具,並且既不保守、又不冒進的選擇某種新的工具,而且還能夠帶領整個團隊,用好這些工具。的確是極大的挑戰,也可以說是沒有終點的旅程......


(更多華爲資訊請關注華爲開發者社區,華爲自己的對外開放門戶:http://developer.huawei.com/ict/cn/ ,不要問我叫啥,別人都叫我雷鋒)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章