天问

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

在最近参与的 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和 5会成为开发过程中的瓶颈,花上大量时间合入、合入之后解决各种本地构建的问题。 我们通常会低估这些工作的难度,在项目计划上预留很少的时间来做这些事情

其次,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 开发了大量各种工具,这些工具的切换都是成本。在上面本次的优化项目中,没有涉及这块,但正是交付项目需要考虑这些问题。

 作者简介 
陈灵

华为无线网络产品线五级软件专家,在大型软件架构设计、性能优化方面有丰富的成功经验,多次荣获公司/产品线优秀软件架构实践表彰。

华为的明星博主,原创软件技术博客超过一百篇,愈十万字,促进了软件领域各种原理、方法、实践应用知识在公司内的普及。

 C++ 专家,著有《C++ Programming Guidelines for Embeded System》、《C++ 在嵌入式系统应用的五大问题及解决方案》等大部头。

公司早期 git 与社交编程的实践者和布道者之一,同时开创了公司的技术面试环节依据 github 记录考察能力的先河。

博客地址:http://blog.yoqi.me/?p=909

扫我捐助哦
喜欢 0

这篇文章还没有评论

发表评论