学敏捷,先用 Git

引言

​  时至今日 Git 已经成为了事实上最流行的版本控制系统,很难想象一个不会使用 Git 的程序员,第一天上班是怎么度过的。

​  现在网上分析 Git 之所以流行的文章,都是关于它如何从技术实现上解决了其他 VCS(Version Control System,版本控制系统) 的缺点。不过,Git 的诞生并不是从如何设计一个先进的 VCS 开始,而是从最大化整个组织的效率出发,从构建一个可信任网络入手,最终目的是使整个团队更好地沟通协作、更快地交付成果,Linus Torvalds 是站在这个宏观角度下来设计 Git 的。

敏捷与 Git

​  信任、团队、协作、交付、效率……这些词放在一起,是不是让你想起了我们的老朋友——“敏捷”。让我们回顾一下敏捷的几个要点:

  1. 敏捷是⼀种心态:在复杂多变的情况中持续探索,拥抱变化和未知的开放学习者心态。
  2. 从产出来看,敏捷能频密地交付可⻅的、可⽤的成果,并持续地去优化成果。
  3. 从团队组织来看,敏捷能激发大家主动地、持续地学习和改进。
  4. 敏捷能够轻松、低成本地响应变化、不确定性以及机会。

​  虽然 Linus 并没有直接提到敏捷二字,没有全盘采纳敏捷的所有方法,但在其设计 Git 的理念上,却多少体现了敏捷的思想,其终极目的是一脉相通的。不管有意无意,Git 的设计恰恰满足了乌卡时代下的研发环境与交付要求。

乌卡时代,即 VUCA ,这个时代下有 4 个显著特征:易变性(Volatitily)、不确定性(Uncertainty)、复杂性(Complexity)、模糊性(Ambiguity)。

​  正如所有提升研发效率的工具与方法,它们是否打着敏捷的旗号,是否冠以敏捷,其本身是无所谓的,只要自己的项目或者团队能从敏捷的理念获得帮助就足矣。

Git 的设计理念

​  让我们把时针拨回到 2005 年,看看 Linus Torvalds 当时面临着怎样的处境。

​  彼时 Linus 和 BitKeeper(当时 Linux 使用的 VCS)已经决定分道扬镳,而全世界的人还在给他发邮件,附件里直接带上关于 Linux 新的压缩包和补丁。其实在维护内核的头十年,社区一直是这样来做版本控制的。而这样的形式不仅让 Linus 本人忙不过来,也让整个组织运转效率持续低下。于是在发布了 Linux 2.6.12-rc2 之后,Linus 决定在做出一个替代 BitKeeper 的源码控制系统之前,暂时不再碰 Linux。

​  当时,最流行的 VCS 当属 SVN,它的口号是“做最好的 CVS(Concurrent Versions System,并发版本系统)”。而在 Linus 看来,CVS 的理念在软件工程中行不通,是注定失败的,遑论在其之下孵化的 SVN。因为 SVN 的设计团队并不想在版本控制方法论上有新突破,他们只想修补 CVS。所以 SVN 也继承了不平等的思想,版本库总有一些部分比其他部分更重要。

​  事实上,Linus 坦言自己从来就不是一个重视源码管理的人。Linus 甚至把 Git 的成功,归功于没有受到 CVS 设计理念的“毒害”。他并不是直接从技术上设计一个更优秀的 VCS ,而是在用心观察,以 SVN 为代表的版本控制下的项目和团队,其研发效率是如何被拖慢、人与人之间的联系是如何被割裂的。

​  众所周知,SVN 最重要的特色是权限控制,而要实现严格的权限控制,就必须使用集中式的设计,意思是版本库是集中存放在中央服务器的。很多项目或者公司为了保证主代码库的稳定和安全,并不会将提交代码的权限下放给所有人,这本身已经隐含了一个假设——所有的人都是蠢蛋。而从“保护”主代码库的角度出发,管理员需要首先把“不那么蠢”的人挑出来,设计一个少数派的“特权阶级”,而从人为划分阶级开始,自然会不可避免地——往轻了说带入信任隔离,往重了说带入政治斗争。但我们想想,即使有了“不那么蠢”的人组成的特权阶级,由他们来控制代码,就能保证主代码库一定不出错吗?这么多的经验教训告诉我们,严格控制权限的实际意义,并没有想象中的那么大。

​  集中式设计的另一个缺点,就是网络基建服务的投入和运维成本。由于干活前要从中央服务器拉取最新的版本,干完活后要把成果推送到中央服务器,当参与开发的人数多了后,特别是当今远程办公和异地协同的场景越来越平常,此时要做好集中式下的网络基建建设,要考虑的问题可不少。遇到网速不好甚至断网时,就算想敏捷也没力气使了。

​  而这些问题的解决办法,就是分布式。

可信任网络与自组织团队

Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.

激励团队成员建设项目。提供所需的环境与支持,并信任他们能够完成工作。

The best architectures, requirements, and designs emerge from self-organising teams.

最好的架构、需求和设计出⾃于自组织的团队。

​  上面是敏捷十二原则中的两条。第一条的关键字是“信任”。我们也常说,敏捷开发从信任团队开始。从底层来看,像这样有多个节点可以相互连接、去中心化的、遵循约定好的协议进行信息交换的团队,就组成了一个可信任网络。

​  在 Linus 看来,想要最大化整个组织的效率,就要从构建一个可信任网络开始。去中心化之后,隐含的意思是,独立两个节点之间可以不用假设要互相信任,从而不用纠结于代码的提交权限要下放给谁的问题,而是一视同仁,所有人都有了代码提交权限。重点在于,作为一个网络开始运转后,从第一个节点(人)开始,他不用默认相信这个网络里的所有人,而是选择他知道自己可以信任的第二个节点的代码,从而合并代码完成合作。第二个人也是如此,他选择自己信任的第三个节点的代码,第三个人选择自己信任的第四、第五个节点的代码……以此类推,信任的链条开始传播出去,每个人都有自己信任的上下游,虽然每个人都可以提交代码,都有自己的分支,分支数量数不胜数,但此时大家并不用关心整个仓库里每个分支具体发生了什么,而是只用选择对自己有用的、自己信任的分支继续进行开发。这样不用假设信任的前提,通过自发选择传播组成的链条,最终反而形成了最可靠的可信任网络,Linus 认为这才是实现安全系统的唯一方法。

​  可信任网络是团队开发所需的环境与支持,但要实现终极目标,还得要第二条原则里提到的,团队是自组织的。怎样做到自组织?我们有几个角度来考察,比如,从个体上看,要让每个人相信自己能在工作中充分发挥和实现价值;从团队上看,要建立透明的、畅所欲言的沟通渠道;从文化上看,要确保团队遵循平等的准则。这些规则并不是空中楼阁,制定好了就自然而然能够执行的,而是需要底层工具的支撑,Git 就很好地承担了地基的职责。比如,从个体上看,自由独立的分支空间能鼓励每个人写想写的代码;从团队上看,基于 Git 方便的分支对比,Code Review 是绝佳的沟通渠道;从文化上看,分布式的仓库让团队里每个人都可以进行平等的学习交流。

​  敏捷没有固定的流程,但有了可信任网络与自组织团队,我们可以在敏捷上走得更远。

为什么一定是分布式

持续工作

​  分布式意味着每个人手上电脑的里都有着完整的代码库,最显而易见的改变就是我们可以脱离网络工作了。敏捷所追求的是“持续”状态,今天所面临的工作环境复杂多变,为了及时响应需求,离线工作所代表的并不只是能在没网的飞机上提交代码,而是一种“持续工作”的能力,特别是当成百上千个工程师同时参与进项目开发时,如果采用集中式系统,保证大家与中央服务器的网络畅通是很困难的,此时赋予所有人“持续工作”能力的意义就格外重要了。

​  持续工作并不是加班的代名词,它代表的是不再被网络、中央服务器等外在条件所局限,是能自由开发、深度工作的能力。

协作

​  紧随持续工作而来的是分布式设计的第二个深意,就是让每个人拥有更自由的分支空间。在集中式设计里,分支空间是全局唯一且“神圣”的,只有被选中的大师敢在这唯一的空间里对分支进行操作,而且要知道在 CVS 里对分支做合并是要提早一星期做规划准备的,大公司里一个典型的场景是“高级开发”要和”普通开发“来一场上下级之间的商讨会。

​  而分布式的设计,能让每个人的提交代码、分支操作都在属于自己的空间里进行,这样工程师就不用被中央仓库所束缚,他们知道自己的操作不会影响到别人,每个人都可以创建几十上百个属于自己的分支,有些分支是用作功能试验的,有些分支是用作服务维护的,无论分支的作用是什么,无论是好是坏,它们都在你自己的空间里。另外在 Git 里,对分支的操作也更加方便、更加快捷。

​  “分支”这个词语顾名思义,生来就应该是这样的,大家的分支不会相互影响,又可以选择更好的相互嫁接。这也为开发与开发之间平等、相互信任的协作关系奠定了基础。

解放工程师产能

《科学管理原理》被德鲁克誉为“20 世纪最伟大的发明”,因为分工产生了流水线,因为流水线才有了工业化,因为工业化才有了以机器取代人力的工业革命。把福特推上机器时代奠基人地位,并创造了“福特之工业奇迹”的,就是泰勒科学管理原理在工业化流水线上的成功运用。

​  分工产生流水线,流水线成就效率。把研发团队想象成一个现代化工厂,也是一样的道理。想象一个具体的场景:这个中小规模的研发团队由 3 个小组组成,分别是开发小组、测试小组和发布小组。

​  在集中式的设计里,由于对分支的各种处理很痛苦,并且在严格权限控制前提下,即使是几行代码的改动,都会有一套严苛流程要按部就班地走,如果在分支推送了不完美的代码可是会影响所有人的,因为这里的分支空间是全局的。作为一个普通开发的想法是——处理分支的代价太高了,什么试验性功能就别开分支写了,等我把代码一口气写完了再提交吧!然而我们都知道,永远不要相信一个人说他一天内能写完一个新功能。造成的结果就是,开发小组需要一两个星期才能把写完整的代码推送进中央仓库,而这段时间测试小组和发布小组只能干等着。等到中央仓库有了最新的代码,测试小组才能拉取下来开始工作,而发布小组还在后面排着队呢。不仅如此,这种模式的提交笨重且低频,即使在同一个小组里,如果有三个人坐在一起写一个新功能,剩下两个没坐在旁边的人就无法及时沟通、了解自己的代码会不会与其发生冲突。

​  在集中式的设计里,对应工厂的比喻,此时大家只是按职责岗位分工,大家围在一起对着一个中央的庞然大物加工,并且不能同时加工,得按指定先后顺序依次加工。此时是没有具体的生产流水线,无法大幅提高效率。

​  在分布式的设计里,由于每个人都有自己的分支空间,无论怎样的改动都发生在本地,并且分支的各种操作都很便捷,作为一个普通开发的想法是——把需求优先级排好、粒度切分具体到子需求、各开一个分支开始写吧!此时不需要担心其他细枝末节,每当一个子需求的代码在本地分支写好了,就推送到远程分支,给测试小组打个招呼,测试就能把远程分支拉取到本地,针对这一次提交的变更进行测试,而无需等待整个大需求完整的代码都提交,发布小组也是如此。此时,开发小组推送了上个分支后,新开了一个本地分支继续开发;测试小组测试完了上个分支后,新开了一个本地分支继续测试;发布小组亦是这样,每个小组都有自己的代码树,大家并行工作,不再是有先后的依赖顺序制约,最后演化形成自己团队的工作流( Workflow)。不仅如此,由于这种模式的提交轻巧且高频,即使在同一个小组里,不管大家是不是坐在一起,任何一个人往远程分支提交了数行代码,剩下的人都可以对这次改动进行检视讨论(Code Review),甚至马上在别人的分支上加上自己的改进。

​  在分布式的设计里,对应工厂的比喻,大家按照职责岗位依次有序坐在流水线上的不同位置,当流水线运转起来时,经过每个人前面的交付物是可以实时同步更新到最新形态的,每个人都在自己的位置上对着最新的交付物加工,并且每次加工好一小块就能同步给其他人,此时诞生了一条完整的生产流水线,这条流水线就是属于自己团队的工作流(Workflow),工程师产能得以解放,效率得以大幅提升。

Git 的技术实现优势

分支合并时间

在汽车发明出来之前,大家只想要更快的马。

​  SVN 引以为豪把创建分支的时间缩短了,但衡量开发效率的从来不是分支时间,而是合并时间。程序员并不在意创建分支的耗时,痛苦的是做分支合并。而 Git 得益于从底层重新设计,工作流设计得好的话,大部分时候分支合并都是自动且无痛进行的,速度非常快。

性能第一

​  性能并非不重要,相反,它非常非常重要。在以前版本的 SVN 中,在大型仓库里做每次操作都要等待一会儿。而这一会儿,人就可能去点点网页走神了,甚至走开去倒杯咖啡。俗话说由奢入俭难,当你用上了各种秒级回应的 Git 后,会感觉如丝般流畅。而没有了咖啡时间,会让人更愿意对每一次具体的改动提交非常小、但完整的分支,从而实现了频密交付。而频密交付,不正是敏捷的要点之一吗?

SVN 与 Git 在软件工程上的区别

​  从软件工程发展历史上来看,先是有了瀑布模型,所以比 Git 更早诞生的 SVN 自然就和瀑布绑在了一起使用。在瀑布模型里,交付产物是一开始就定下来了,随后以约定的大版本号分批少次、全量交付,具体到版本控制里,SVN 关注的是“文件”,比如这一次瀑布约定好了要交付多少个文件。但随着市场需求的变化,瀑布模型暴露出以下三个方面的缺点:研发周期过长,导致研发跟不上业务发展的节奏;不能很好地响应需求变化,导致客户满意度低;由于是一次性交付,不能很好地管控风险。

​  伴随着克服这些缺点的敏捷的崛起,Git 有意无意间也成为了敏捷的基石。与 SVN 相对的,Git 最重要的是关注“变化”,工程师并不关注今天有多少个文件更新,而是关心这些文件具体发生了什么变化。每当需求有变化时,Git 可以快速地进行小步增量迭代。这正对应了敏捷价值观里的“客户合作胜过合同谈判”,“响应变化胜过遵循计划”。

​  简而言之,瀑布模式下,关心的是这一个版本交付了哪些文件;敏捷模式下,关心的是这一次迭代做了哪些改进。而这正是 SVN 与 Git 在软件工程上的区别。

敏捷与 Git 合并,我们得到了 CODING

​  虽然 Git 很好,但归根到底它也只是一个工具。并不是说只要用上了 Git,大家再按照敏捷分配一下角色,团队就自动获得了敏捷赋能。工具和团队价值交付之间的鸿沟如此之深,我们还需要一整套融合了敏捷理念的产品来帮助实现目标,这就是 CODING。

​  在底层工具支持上,CODING 不仅有体验优秀的代码评审、单项目多仓库等特点,还有基于 Git hooks 触发的自动状态检查、持续集成、持续部署等完整 DevOps 工具链。基于 Git 的 DevOps 解决方案可以参考 CODING 知识库相关资料。

​  不过即使有了底层功能支持,很多团队也只是往往把“大瀑布”变成了“小瀑布”,把一个大项目分成若干个模块,仿照 “敏捷” 的做法,每数个迭代做一个模块,节奏虽然加快了,但要真正实现频繁的价值交付,最后还是要靠加班(当然,即使是“小瀑布”,也比“大瀑布”好得多)。这是因为没有对项目需求进行有效拆分,没有科学控制小颗粒度需求的流转,在流程上也没有为不同的角色设计好协同。比如,光程序员拥有趁手的工具不够,产品经理、测试等其他角色也要有相对应的得心应手的工具,并且不同工具之间可以流转价值。

​  所以在项目协同功能上,CODING 基于敏捷的理念设计了先进的迭代规划、史诗、自定义工作流等,还从完整的研发生命周期考虑,提供了测试管理、知识库管理、OKR 管理等为团队不同岗位角色量身定制的方案。

​  如此,完善的底层工具配合全流程项目协同,我们就可以逐步实现敏捷所追求的拥抱变化、响应变化,最终得以持续交付、创造价值。