如何在項目中利用 git 提高工作效率

文/陳靈


華爲無線網絡產品線五級軟件專家,在大型軟件架構設計、性能優化方面有豐富的成功經驗,多次榮獲公司/產品線優秀軟件架構實踐表彰。

華爲的明星博主,原創軟件技術博客超過一百篇,愈十萬字,促進了軟件領域各種原理、方法、實踐應用知識在公司內的普及。

 C++ 專家,著有《C++ Programming Guidelines for Embeded System》、《C++ 在嵌入式系統應用的五大問題及解決方案》等大部頭。

公司早期 git 與社交編程的實踐者和佈道者之一,同時開創了公司的技術面試環節依據 github 記錄考察能力的先河。


在最近參與的 OSS 優化項目中,我們應用 git 來作爲優化代碼版本管理、團隊協作的工具。

在這個優化項目中,我們:

  • 前後創建了大約 20 個優化分支;

  • 累計完成了超過 350 次提交;

  • 累計完成了 41 次分支合併。

與此相對比的,在很多產品的一個 R 版本開發過程中,甚至不會有開發分支。得益於 git 以及 gitlab 提供的代碼託管,上面這些工作(分支建立、分支合併)變得非常簡單,極大的提高了整個工作效率。


爲什麼要使用 git 而不是 SVN?


如果僅僅把 git 當成一個替代 SVN 的工具,我認爲不需要從這裏找答案,現在有很多 git 教材,都會或多或少的把 git 和傳統的集中式的版本管理工具,比如 CVS、SVN 進行一番對比,給出答案。

在基於問題——改進思維模式中,我們可以發現,期望從工具優劣的角度說服使用 git 而不是 SVN 是始終站不住腳的,因爲 git 同樣存在一堆自己的缺點,比如:太靈活、學習成本遠高於 SVN 等。

所以要使用 git,我們必需要看到工具背後極力所倡導的一種團隊開發的模式和理念,基於對這種模式和理念的探索,我們再談 git,纔是一件比較順理成章的事情。

從性能優化這件事情本身來看,通過分析歷史版本優化數據,我們可以看到:

  • 我們實際修改優化 100 個優化點,能夠確認產生明顯收益的,可能不到 50%,也就是說對於優化來說,如何管理好優化點的修改記錄,這是最大的挑戰。這個述求總結下來就是方便的回溯;

  • 在一個子系統、全系統優化項目中,優化啓動、優化實施、優化效果確認整個閉環週期相對較長,通常的模式都是單獨走自己的優化分支進行優化的實施和確認,實施完畢之後再進行反合。在這個過程中又在結合問題 1,就會發現,如果只有一個 SVN,多人協作是非常困難的事情。

協作模式


1、基於 SVN 的協作模式

我們先看基於 SVN 的協作模式,基本上如下:


(點擊圖片,查看大圖)

我們現在對分支管理比較嚴格,要求一個主幹。這個要求對於產品的發佈版本沒有問題, 但作爲開發過程或者迭代要求來說,是不適應生產力發展的要求的。在上面這種模式下面,如果多個開發人員之間要共享協作,基本都是採取文件共享這種最低級的方式,採取人工對比合入的方式進行操作(圖中步驟 2)。

在很多時候, 步驟 2、3 和 5、6 會成爲開發過程中的瓶頸,花上大量時間合入、合入之後解決各種本地構建的問題。 我們通常會低估這些工作的難度,在項目計劃上預留很少的時間來做這些事情。

其次,SVN 上分支同步、管理比較麻煩,如果同一項目在自己本地超過 3 個分支,並且要在不同分支之間切換、修改、合併是一件比較困難的事情。

2、基於 git 的協作模式

如果使用 git,協作模式類似如下:

  • Step 1,用戶在本地創建新的分支;


  • Step 2,用戶將分支 push 到一個公共服務器,以便其他用戶獲取該分支;

  • Step 3,第二個用戶從遠程服務器獲取分支;

  • Step 4,用戶 1、用戶 2 分別在各自本地開發,在各自本地分支提交,並將提交 push 到遠程服務器;


(注:User 2 實際需要從 remote 上將 User 1 的修改 pull 到自己本地後才能 push,圖上簡化了這個過程。)

  • Step 5,用戶 1 或用戶 2 在遠程服務器上創建合併請求,合併分支。


可以看到:

  • 所有的人,包括服務器,都擁有完整的版本,包括分支,這樣省卻了 SVN 那樣互相在本地共享版本那樣低級的操作;

  • 其次,git 可以自動合併分支,這個過程省卻了大量的機械性的人工操作。

項目中的應用


1、分支策略

在優化實施前,通過對優化項目本身的分析,確定三個大的方向,即:

  •  內外置函數優化

  • 解碼模塊性能優化

  • 自動轉換腳本爲 C++ 代碼

在實施過程中,我們分別爲這三塊建立不同的分支,通過不同的分支來管理不同類別的優化。

分支合併策略:

三個分支分別進行各自的優化,開展各自的效果確認,最終僅僅將那些確認有效的修改統一合併到一個 test 合併進行集成測試:


(點擊圖片,查看大圖)

git 可以自動完成合並,因此可以在不同的分支上進行開發,不同分支最終的代碼合併通過一條簡單的 git merge 命令就可以完成,極大的提高效率。

2、分支上的分支:小粒度優化分支

如前所述,性能優化一個特點並不是所有優化修改都會最終合入版本,這中間還有一個效果確認的過程。假如某個優化修改效果不明顯不最終合入,那麼如何方便的管理修改、回退修改就比較重要。

在傳統基於 SVN 的模式中,通常的做法是,所有的優化修改都在一個分支上進行操作,即使效果不明顯,這些修改仍然在 SVN 上。到了最終合入的時候,再通過優化前後修改逐一對比,確認是否合入。

在 git 上,我們強調特性分支這個概念,git 可以幫助我們合併分支,也可以幫助我們回退提交,那麼可以非常方便幫助我們達成我們預先的期望。

比如,在前面確定的三個優化方向中,內外置函數優化中實際上包括若干項優化,在實施的過程中,我們又進一步對該優化進行了更小粒度的拆分,建立了多個分支:

  •  check_imsi_opt

  •  lookupgrid_umts_chr_opt

  • splicestr_opt

  • fhx_ext_func_opt

  • getloctime_opt

每個小粒度分支包含一組較小粒度的、完整的修改。


(點擊圖片,查看大圖)

通過這種方式,確認某個優化不需要合入,我們可以:

  • 回退 ext_func_opt 分支提交,重新 merge 需要合入修改的分支;

  • 或者從 ext_func_opt 分支建立新分支,merge 需要合入的分支。

3、分支上的分支:特性開發

DSL 轉換爲 C++ 這個優化是一個很大的功能特性,我們花了兩個多個月才完成這樣一個功能特性。這個優化本質上是一個特性開發過程。我們在將 DSL 轉換爲 C++ 代碼時,同樣大量應用了特性分支這種模式,以提高協作開發效率。這個功能特性包含這樣一些小功能分解:

  • DSL 基本語法轉換爲 C++ 語法

  • 爲轉換後的 C++ 方式的 Counter 提供 runtime 接口

  • 適配到多個版本的 Counter 的 DSL 同時轉換爲 C++

  •  需要支持所有的 Counter 的 DSL 腳本轉換爲 C++

  • 需要支持動態加載 C++ 方式的 Counter

  •  需要對原 call-table 進行優化以支持 runtime

上述功能特性建立 A 圖,如下:


(點擊圖片,查看大圖)

在實際分支建立的時候,我們以特性 1 的分支爲主線進行開發。一旦滿足新的功能特性的開發條件,我們隨即從該主線建立一個新分支開始新的特性開發,一旦特性開發、驗證完畢,隨即將功能特性合併回主線。


比如,在上面這個簡化的分支圖中我們可以看到,特性 5 早於特性 3 建立分支,而特性 3 由於工作量小,又早於特性 5 開發完畢合入主線。

通過這樣一種模式,幾個特性之間互不影響,特性之間也能協作開發而不限於某個特性只有一個人完成。


Benefit


回到最初的兩個問題上來:

  • 如何管理好衆多的優化點?

將優化歸類,爲歸類建立分支,在分支上再利用特性分支實現更小粒度的修改,利用 git 的 merge 功能實現分支之間的合入。

  • 如何在優化過程中實現協作?

提前識別依賴,規劃好特性分支,將涉及多人協作的分支 push 到服務器上。多個員工通過在不同的分支之間切換,在分支上提交,修改驗證完畢之後合併回主線。

除此之外,我們能夠在效率上感受到明顯的收益,包括:

1、分支建立的效率

git 倉庫是一個本地庫,再不考慮分支規劃這些管理上的投入外,分支建立可以看成是一個零成本的動作——任何人都可以在他自己本地建立分支,而在 SVN 上,只能由管理員建立分支,有很多溝通、協調管理上的成本。

比如,在 DSL 轉換爲 C++ 的過程中,我們發現採用此方案生成的 Counter 的二進制目標文件佔用較大的磁盤空間,這時,我們臨時決定開展一項針對性的優化:二進制目標體積的優化。我們意識到這個事情是一個獨立的、不與其他特性開發產生依賴的、需要大量驗證的事情。那麼爲此建立一個新的開發分支是最合適不過的事情。分支建立這個事情直接在本地進行操作即可。


(點擊圖片,查看大圖)

2、分支切換的效率

如前所述,我們累計建立了超過 20 個分支,這 20 個分支共享一臺編譯服務器的同一個工作目錄。也就是說,在編譯器服務器上,一個時刻只有一份分支存在。需要構建某個分支的軟件版本,利用 git 的分支切換命令直接切換過去。

git 分支切換效率遠比 SVN 的分支檢出效率高,很多時候,切換分支不會發生任何文件的檢出。這裏就不在贅述。

3、分支合併的效率

使用 SVN 的代碼,在分支合併時,通常是這麼幾個步驟:

  • 把兩個分支都檢出到本地

  • 使用對比工具(比如 beyond compare)對比兩個分支

  • 針對對比出來的差異,逐一手動合入

  • 提交上庫

對於一些比較大型的產品來說,上述步驟 1、3 非常耗時,也非常痛苦。如果仍然採用這種模式,我們多達 40 次的分支合併簡直是給自己挖坑跳。在 git 上,1、2、3 步驟由工具自動完成步驟 4,唯一需要人工參與的工作就是衝突解決。

當然,所有的效率提升,是建立在比較熟練的掌握 git 的基礎上,否則緩慢的學習曲線會極大的打擊使用 git 的熱情。

4、其他收益

除了開發效率的提高之外,利用 gitlab 提供的代碼託管功能,我們還可以:

  • 利用 pull-request 模式對提交代碼控制合入質量

  • 利用在線檢視功能檢視代碼

  • 利用 Issue 列表記錄

這些都是 GitLab 上所提供的功能,已經有很多介紹,在此就不在贅述。


如何更好的用好 git?


1、git tools

git 與 SVN 在操作上有很多不同,雖然同樣有提交 (commit) 的概念,但我們經常還需要涉及到分支的操作(建立、合併、刪除),提前熟悉好 git 相比我們在過程中不斷摸索更好。

熟悉並掌握好 git,包括一個良好的 git 客戶端工具應用。我們嘗試過 tortoise git、git extensions、eclipse egit、git flow,但從最後的效果來看, 我認爲最好的 git 工具還是 git bash 這個命令行工具:

  • 首先,git bash 簡單、快速,通過不斷的命令使用,能夠讓我們不斷的深入掌握 git,更好的理解 git,這是任何一個提供 GUI 的 git 客戶端工具做不到的;

  • 其次,不同的 git GUI 客戶端工具對一些操作概念的封裝是不同的。比如,tortoise git 上提供了 revert 命令,而 eclipse egit 上則沒有 revert 命令,git extensions 也沒有 revert 命令。因此,基於某個特定的 git gui 客戶端工具來學習和掌握 git 存在一定的侷限性,因爲客戶端工具一旦發生變化,重新學習的成本很高。從這個角度來說,更傾向於直接使用 git bash 通過命令行的方式來使用 git;

  • 再次,git bash 命令行工具也是 linux 下使用 git 的最主要的方式,對於同時擁有 linux 作爲開發環境的人員來看,命令行工具是唯一的使用 git 的方式。

2、git command

  • git rebase

我們使用 SVN 多人協作開發過程中,假設 A、B 均基於節點 R1 進行互不耦合的開發,那麼 A 先提交,B 後提交,不會存在什麼問題。即使 A、B 修改同一個文件,只要修改內容不衝突,B 在提交時更新 A 的修改到本地,SVN 客戶端工具都可以自動幫助我們完成合並。

但是在 git 上,A、B 均是首先提交到本地,A 如果先 push 到公共服務器上,B 此後再 push 則必須首先更 A 的 push 更新下來,這樣一來,在 A 的本地就會產生一個 merge 的動作,這個 merge 的動作體現版本樹上。如果這種操作很多,整個版本樹就非常亂。

比如下圖體現的提交就比較混亂:


(點擊圖片,查看大圖)

而實際上的提交應該通過 git rebase 命令對本地提交進行重整然後再 push 到服務器上,如下:


(點擊圖片,查看大圖)

因此參與項目等額人員需要熟悉 git,有針對性的培訓。

  • git cherrypick

git cherrypick 提供了一種很好的方式用於將 A 分支上的某個修改同步到 B 分支上(不是合併),這對於 git 中多個分支並存開發的情況下顯得非常有用。由於之前並不瞭解這個命令,在分支合併過程中做了一些無用功,比如:

分支反向同步:


(點擊圖片,查看大圖)

建立了額外的分支(比如 master-bug-fix ):

如果熟悉 git cherrypick 命令,那麼在過程中能夠避免這樣一些無用功,提高效率。 

  • git commit -- amend

git 的提交首先是提交到本地,這樣一來我們並不侷限於以往 SVN 的提交方式——即必須要完成本地構建才能提交,相反,我們可以隨時提交。

這種方式的好處就是即使保存本地分支的工作進度便於我們在分支中切換。壞處就是從版本樹上來看,可能就存在一些非原子的提交或者包含錯誤的提交,這些提交如果一直在自己本地則沒有什麼問題,但如果將這些提交版本合併到其他分支,歷史樹就非常長。

實際上 git 提供了修訂提交的命令來修改上一次提交,在項目初期大家對這個命令並不熟悉,歷史提交記錄看起來非常醜。

3、分支的及時收編

前述,我們爲兩大優化方向分別建立了兩個分支 (decode_opt) 和  (code_translation),兩個分支最終會合併到 test 分支上。分支合併後,此時應該及時將兩個分支收編,此後:

  • 對於遺留未完成的特性,這些開發我們可以基於合併後的 test 分支重新建立新的 code_translation 分支。這樣做,可以避免後續將 code_translation 再次 merge 到 test 時,可能出現的衝突;

  • 對於 test 分支上的 bug,可以直接在這個分支上進行提交,而不需要回到原始的decode_opt 或  code_translation 分支修改然後再 merge 到 test 上兩個步驟。

及時收編的好處在於:

  • 減少分支數,查看提交歷史時,減少分支歷史中同時出現多條分支線,便於查看;

比如下圖中,code_translation 並沒有及時收編到 test 分支,從歷史記錄上來看,就會看到兩條很長的分支生命週期線,不利於查看歷史記錄。


(點擊圖片,查看大圖)

  •  避免將來合併再次產生衝突。

4、分支檢出

用習慣了 SVN 之後,要檢出不同的分支,很多人仍然習慣性的在磁盤上創建不同的目錄來檢出不同的分支。這種做法實際上比較土、效率比較低的方法。在 git 中,只要在同一個項目(更準確是倉庫)中,我們可以使用同一個磁盤目錄在不同的目錄中進行切換(分支切換效率這個問題就不此討論了,效率高的出奇)。

一旦掌握了這種方式,在特性開發過程中,在不同的分支中切換、開發、提交,你會發現效率提升非常明顯。當然,這是一種習慣的轉換,短時間可能思維難以轉變過來。


Summary


  •  利用 git 來作爲開發階段的版本管理工具,改變了協作模式,極大的提高了協作效率,促進了生產力的提高;

  • git 學習成本比較高,不能簡單的把 SVN 那套操作簡單的映射過來,在項目初期必須要有效率可能會降低的心理準備。同時需要引入專門的培訓和實戰練習,縮短學習週期;

  • 在產品開發中應用中,還需要從集成交付的角度,打通相關環節,特別是 CI 。目前基於 SVN 開發了大量各種工具,這些工具的切換都是成本。在上面本次的優化項目中,沒有涉及這塊,但正是交付項目需要考慮這些問題。


如何參與?
本次活動共有2種註冊方式:
其一,在華爲雲計算開發者官網(http://developer.huawei.com/ict/cn/site-cloud)註冊成爲雲計算的用戶;
其二,在華爲雲計算開發者官網(http://developer.huawei.com/ict/cn/site-cloud)點擊登錄,通過CSDN賬號授權方式進行註冊。
注意,兩種註冊完成後,均需要在個人中心完善個人資料,尤其電話和郵箱兩種聯繫方式,關係到後期領取獎品和華爲開發者生態資訊獲取。




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