33 Comments

  1. collin

    「现在我要告诉你,Github在企业软件开发中甚至不是一个最佳实践。」
    ——–
    这里是 Gitflow 而不是 Github 吧?

    还有这里:
    ·「然而最根本问题在于Github背后的这一套feature branch模型。」

  2. Paul

    对作者的一些观点和论据有些质疑, 怀疑作者是重度svn用户,还没真正把握git的精髓。

    随便举几个例子:

    > 如果feature开发的平均时间是一个月,feature A所基于的代码可能在一个月前已经被feature B所修改掉了,这一个月来一直是基于错误的代码进行开发,而直到feature branch B被merge回develop才能获得反馈,到最后merge的成本是非常高的。

    您都说是长期分支了,那么这个分支相对而言,不会随随便便的出问题。好吧,就像你说的出问题了,并且也已经清楚了feature B已经修复, 那么完全可以再开发feature A的分支上merge feature B, 难道必须等到1个月feature A开发完成? 而且对于bug fixes, gitflow是不建议在开发新功能的同时修复bug, 最好新建专门的分支修复bug. 我们在软件开发中,知道去耦合,在git的使用上就不知道了?

    > 然而面对哪怕最基本的语义冲突上,git仍是束手无策。在同一个codebase里使用IDE进行rename是一件非常简单安全的事情。如果branch A对某函数进行了rename,于此同时另一个独立的branch仍然使用旧的函数名称进行大量调用,在两个branch进行合并时就会产生无法自动处理的冲突。

    大哥你说的是重构了,相当于您要修改接口了,那版本至少是从1.0 跨越到 1.1了, 您在1.1版本分支上去合并1.0开发分支,当然会出问题,如果这样做,只能说修改代码太没有大局观了,太没有替小伙伴考虑了,最起码,您要通知下伙伴,我要重构了,我们先对下代码。 另外,您对于冲突的理解也有些偏差,什么时候出现冲突,仅仅当出现3种不同路子的时候才会冲突。比如分支A上,让他向北走, 分支B上让他向东走, 那当然会冲突了,最后当然需要由人决定,到底怎么办? 解决冲突,更多的应该是总裁。

    另,作者对于分支的看法,还停留在svn的时代, 对于分支,对于分布式,还没理解精髓。 在git中,分支仅仅是个指针,不是像svn重新复制一份。用git,就是要善用分支,控制好粒度,把Feature再细分成小的模块,在更小的模块上操作,作为后,测试没问题,就合并。

    文章还没有开完, 有些观点不同,于是吐槽下。Git这么好的东西,希望给大家或新人一个正确的指引。

    后续我会写一篇博客,对作者的文章逐一推敲,评论,以正Git.

    话说的有点直,还望见谅,各抒己见而已。

    • y JD

      作者说基于的代码被修改了,没说是因为bugfix,开发新feature也会修改已有代码。
      新建更多分支也不一定能减少耦合,有时候耦合是代码本身的耦合造成的,与分支无关。

      第二点我的理解是,作者说重构困难,你说尽量别重构。

    • Shangqi

      对这位同学的质疑,我们已经在https://ruby-china.org/topics/29263 进行了讨论。以下是原贴中的回复:

      Hi, 我是原文的作者。很高兴能在这里看到对这篇文章的讨论。

      原文的观点是long lived branch considered harmful。关于什么是Gitflow章节的反驳我就不花太多精力了,关于如果不用gitflow章节的质疑,原文也给出了链接,TrunkBasedDevelopment和feature toggle等技术是被Google, Facebook, ThoughtWorks等技术公司验证的最佳实践。

      剩下的部分其实基于一个基本假设,这个假设也是软件开发行业几十年来的共识:我们应该尽可能地缩短反馈周期。在这里feature branch会鼓励长时间分支,每个分支在合并到主干前都无法获得及时的反馈。楼主举的例子很好:

      我们新开一个分支,把功能做好,测试确认没问题,马上就合并到枝干上,把这个分支给删除了。这个分支,甚至都不会出现在中心仓库的分支里,升值自己的remote仓库里,仅仅在个人本地仓库出现,可能一天,或者半天我们就完成了一个merge迭代,把这个分支给删除了。
      这也正是原文所鼓励的,避免long lived branch。无论是否用gitflow或feature branch,使用者在实践中都会发现频繁地跟主干合并代码(频繁从主干rebase回feature branch,频繁把feature branch代码merge回主干)能大大减轻big bang conflict的merge。而当你的每个feature branch只存在于本地一天或半天时,为什么还要单拉这么一个branch呢?毕竟本质上git并没有主干分支,每个repository都是一个独立分支。

      楼主对于原文一些例子的质疑,可能因为楼主的工作背景是嵌入式领域,对于互联网模式/敏捷软件开发中的许多基本实践(代码共有,重构,持续集成)缺乏了解。如果不澄清这一点可能会对一些基本的实践产生观念冲突,而完全展开也不是三言两语就能覆盖的。如果有兴趣我们可以case by case地聊。

      另外原文讨论的是基于企业软件开发的上下文。如果是开源软件开发的协作就是另外的情况了。事实上开源社区的繁荣和魅力的根源也正是其多样性——每个人都可以fork自己的分支。

      • 在 gitflow 中,跟 master 长期并存的是 develop 分支,不是feature分支。develop分支的目的才是隔离作用,避免开发中的代码污染master线上的稳定环境。feature 不应该是一个 long lived branch,他应该粒度被尽可能小,尽快的合并到develop分支中,这是好的gitflow实践,也符合作者提倡的持续集成的思想。

        另外一点就是,作者提出的git方案,没有解决分布式开发的问题。基于feature分支的开发,更重要的作用是分布式。

      • “这也正是原文所鼓励的,避免long lived branch。无论是否用gitflow或feature branch,使用者在实践中都会发现频繁地跟主干合并代码(频繁从主干rebase回feature branch,频繁把feature branch代码merge回主干)能大大减轻big bang conflict的merge。而当你的每个feature branch只存在于本地一天或半天时,为什么还要单拉这么一个branch呢?毕竟本质上git并没有主干分支,每个repository都是一个独立分支。”

        上一条回复已经说明了,gitflow中,短周期小粒度feature频繁的合并到develop这中方式,就是一种避免long lived branch的实践。隔离的作用是develop分支,feature分支更重要的作用是 commit语义和解决分布式开发。

        按照作者提出的第二种方式,commit频繁的提交到master上,如果多人同时在做开发,并不能解决冲突问题,因为你每次提交有可能照成冲突,每次都需要解决。还有一个问题就是你一下我一下杂乱的提交会破坏commit语义。合并一个feature有一个完整的语义,而不是作者提出方案的各种加错的片段。

        “在软件开发的世界里,如果一件事很痛苦,那就频繁地去做它。”
        “能大大减轻big bang conflict的merge。”

        频繁的解决小冲突,和一口气解决一个稍微大一点的冲突(feature),哪种方式更好呢?我不清楚。但是我觉得gitflow更有利于分布式和语义。

  3. steveltn

    我想问下如何才能做到不同的人同时在 master 上进行开发。毕竟任何人一旦把代码 clone 到本地,本地 master 和 origin/master 就形成了两个不同的 branch。当你在本地 master 上开发完毕想 push 到 origin/master 时,别人的代码说不定已经 push 进来了。这时候你的本地 master 就相当于一个 feature branch,只是换了一个名字而已。

    • Microsoft Office组就是这样的。其实解决起来很简单,我们一天会有几百个push到main branch上面,后push的那个如果有冲突需要sync前面的那个下来merge,然后就形成了树的关系。到了一天结束的时候,服务器就把所有的新push拿起来编译,最后有一个精通所有代码的人(根据观察有几百个人轮流执行)值班把造成build break的那颗子树删掉,顺便发邮件让他们重来。就这样运行了几十年,迭代非常快。

    • Shangqi

      对这位同学的质疑,我们已经在https://ruby-china.org/topics/29263 进行了讨论。以下是原贴中的回复:

      @steveltn 是的,前面也提到了代码clone到本地就成为分支。分布式版本控制系统其实没有严格意义上trunk的概念,我们只是挑选一个master作为主分支。branch按周期分有long lived branch, short term branch和temporary branch,按用途分有feature branch, release branch… branch是一种事实,而feature branch特指利用branch隔离feature代码进行开发的一种使用方式。

      原文的点在于避免long lived branch,而feature branch会鼓励长期跟master并行的分支。原文鼓励的是频繁地跟master合并,以release track(bug fix), 探索性活动为目的创建分支,而不是以feature代码隔离为目的创建分支。

      • “原文的点在于避免long lived branch,而feature branch会鼓励长期跟master并行的分支。原文鼓励的是频繁地跟master合并,以release track(bug fix), 探索性活动为目的创建分支,而不是以feature代码隔离为目的创建分支。”

        在gitflow中,跟master长期并存的是develop分支,不是feature分支。develop的作用是隔离。feature是小粒度,并频繁的创建和合并到develop分支中的,他的主要作用是解决分布式开发和commit语义的作用。

        当develop分支积累到一定程度,测试通过,才会合并到master分支。develop分支合并到master的过程中,通常不会照成冲突,因为按照gitflow流程来实践,是不会有分支直接提交到master的,除了一种情况就是bugfix分支,他来源于master分支,最终合并于master和develop分支。bugfix合并到develop分支后,其分支根基础的变更和master又是相同的了。所以,虽然develop分支是一个长期分支,但是通常并不会产生 big merge,所有并不存在作者所说的问题。

        至于feature,把他用作一个长期分支,隔离分支,本身就是对gitflow的错误理解和错误使用,或者并不是一个好的实践。

      • 还有一种可能是这样的,就是作者认为 feature 分支被滥用的,错误的使用了。这个是确实存在的,在现实中的很多团队中。但是,这不是gitflow的锅,并不是分支的锅。文字却冠名 《gitflow有害论》,这非常冤枉。

  4. 作者是重度svn用户无误,整篇理论基本胡扯。

    两点反驳:
    1.在Gitflow中,保持feature的粒度尽可能小是好的实践。作者认为feature分支会存在很长时间,是因为作者吧feature分支当成了release part分支,这么看当然无法马上合并到develop,自己为是,根本就没理解。

    2.合并冲突这个是永恒的问题,不论哪种方式,什么工作流,都无法解决,除非一个人开发。作者认为合并feature冲突很痛苦,我想问,第二种方法,大家都在master上开发,就不会冲突了吗?

    git是分布式的,冲突是必然的无法避免的,作者是在用svn集中化的思想去理解git。

    在gitflow中,feature分支除了有隔离代码的作用外,本身这个概念也是有意义的,因为feature的合并给了commit一个明确和实际的意义。提交历史,代码的衍生情况对git来说是有意义的。一个符合gitflow实际的代码树可读性是非常棒的。第二种方式,产生的代码树毫无可读性。

    关于feature分支好的实践的两点建议:
    1.保持feature和bugfix分支的粒度尽可能小是一种好的实践。
    2.即便按照1来做,冲突仍然是不可避免的。相比合并产生冲突,在合并之前,对feature分支进行rebase到最新的develop结点是一种好的实践。和合并冲突相比,工作量相当,但是产生的代码树可读性更好。

    • Shangqi

      Hi,首先我不是svn重度用户。相反,我们在2008年以前就开始大规模使用分布式版本控制系统,并在社区和业界推动 svn 向 git/hg 的转换(http://www.infoq.com/cn/articles/thoughtworks-practice-partiv)。

      对@TakWolf 同学的两点反驳我试着回应一下:

      1. 本文里明确区分了feature branch和release branch,并在结尾部分指出用release branch进行track或hotfix。short branch over long lived branch是值得肯定的实践。我们曾经多次在技术雷达里表达对long lived branch的担忧。然而基于我们对业界大量团队的观察,使用Gitflow的团队非常容易偏向long lived branch,这也是为什么我们在雷达里专门highlight了Gitflow。

      还有一个比较有趣的点是。如果feature开发周期比较短(几次commit)就能完成,即使不用feature branch也能很好地完成任务。毕竟在DVCS里每个repository都是独立的分支。即使几天的feature,往往也可以通过先后端、后前端的开发方式减少对用户的影响。然而遗憾的是,我们看到太多的团队把feature branch当做一种向用户隐藏未完成feature的手段,而不是采用更合理的模块划分或特性开关等技术。

      2. 本文承认合并冲突这个是永恒的问题,并指出“在软件开发的世界里,如果一件事很痛苦,那就频繁地去做它”。通过在master上进行开发,可以以更小步的增量获得更快速的反馈。鼓励快速反馈,鼓励增量式变化而不是big bang change是TDD, 持续集成等很多实践的前提,相信@TakWolf 同学也是同意的。

      我们大量使用Trunk Based Development团队的经验表示,关于git历史的阅读并没有什么问题。这里有个小实践:为每一个commit规定固定格式(#storyNumber [author] commit message,如 #123 [shangqi] add validation for creating user),这样就能在code review时非常方便地找到相关的代码。支持这样的固定格式只需要十几行bash脚本(还能自动缓存上个commit的story number,author等信息)。

      值得一提的是,这种更小步的merge反而更容易在code reivew时找到问题。big bang merge往往会导致代码面目全非,review起来非常费劲。毕竟人眼不是compiler或test suites,很难在review里快速发现冲突带来的问题。

      至于是否有必要为feature专门保留一个版本历史树,我个人持保留态度。实践中更高效的方式是直接阅读最新代码,如果有问题,使用git blame。

  5. 在补充一个点:

    作者认为github没有使用gitflow,而是使用了自己的一套简化的pr流程。
    github这没做不是因为gitflow流程有问题,而是因为在github的模式上,代码的合并需要权限管理以及质量评审。
    使用pr的方式提交代码,可以使这个提交附带issues功能,保证权限,也可以是大家产生讨论和评估。
    换句话说,pr是一个升级版的feature或者bugfix分支。除去issues和权限,pr跟feature或者bugfix没啥本质区别.

    • Shangqi

      补充一下。Github Flow跟Pull Request不完全划等号。Github没有使用Gitflow是因为”it does have its issues”, “one of the bigger issues for me is that it’s more complicated than I think most developers and development teams actually require”,对于Github这种需要持续部署(feature as hotfix)的组织来说Gitflow太过繁琐。

      相关的背景在本文原文里有引用,可以在这里(http://scottchacon.com/2011/08/31/github-flow.html)找到。

  6. Branch by Abstraction本来就是一个多余的东西。使用正确的面向对象分析做出来的架构,每一个部分天然就有一个“抽象”,又是在炒冷饭,40年前的知识了。

    • Shangqi

      因为看了一圈发现很多人又做错了。不断有人无视软件行业几十年来积攒下来的那一点点经验。而Branch by Abstraction是说,恩你又做错了,如果你不想推翻重来,可以试试这个…

  7. 根据作者给出的图片以及 Trunk Based Development 关键词搜索了一下。感觉这篇文章写得更清晰明白,建议大家直接看这个文章。

    http://blog.jobbole.com/73930/

    实际上是两种分支模式 Feature Branches 和 Feature Toggle,跟gitflow没啥直接关系。
    两种方式各有优缺点,分别适用于不同的场景,没有所谓好坏之分,更不存在啥的有害论。

    作者估计是想安利 Trunk Based Development 这种开发模式,但是却起了一个《Gitflow有害论》这种一看就让人撕逼的标题。

    全文只有最后一句写的好:技术用的对不对,还是要看上下文。

  8. Sanster

    标题可以再斟酌一下。。文章的内容对于大规模的软件团队(特别是刚从svn转过来,觉得git的分支超爽)来说的确是存在的问题,深有体会。。

  9. 如果线上有紧急 bug 需要修复,是从 release 分支修复吗?如果从 master 分支修复,可能会带入新的 bug 的吧。

    还有这种方式对于测试要求更严格

    • Shangqi

      对于线上紧急bug的修复,推荐的方式是在master分支上做修复,然后把修复的commit cherry-pick到release分支上。不是整个merge哦。

  10. Li

    首先我曾经是svn重度用户,最近两年才开始使用git的。维护过不少大团队长时间的git branch开发。就我的个人经验来说,如果git都冲突,svn早都冲突到死了。我比较楼上说的,作者还没有领略到git的精髓。
    和作者以及大家一样,对long-lived version-control branches 深恶痛绝。但是怎么解决,并没有看到特别有说服力的,或者特别系统的解决方案。说说我个人的经验,第一,就是能细分就细分,细分不但解决了long-lived问题,而且在工程管理上,也极大的降低了风险;从质量和发布上,也可控和回滚操作性强。第二,就是无法细分的项目,其实这样的项目,一般都是公司和人的问题,没有不能拆分的代码,只能不愿意拆分的代码,实在拆不开也只是说明烂到死,应该重构了。但是,遇到这样开发层面无法解决的问题,我们怎么积极的解决问题?我们的方案就是定期从master拉到feature分支上,让feature分支的开发来handle这些master的新变更。然后在feature branch上完成了full regression再回去。

    • Shangqi

      git基于行的diff在处理冲突时确实比svn更好,问题不是出在git和svn的区别,而是long lived branch和short lived branch的却别。如果使用svn的本地代码提交频率跟git的feature branch提交频率一样低,那合并起来会更痛苦。

      关于解决方案,文章后面没有展开讲,其实就是Trunk Based Development加持续集成,这已经是业界非常成熟的实践了,Google数十亿行代码都在单一代码库上使用基于主干开发的策略。

      另外一点就是你提到的“能细分就细分”,文章也点了一下没有展开(“feature branch是穷人版的模块化架构”),做到架构解耦才是治本的方法。

  11. Fredyj

    # master 是什么?

    * Anything in the master branch is deployable

    * 禁止直接提交到 master 分支

    # 稳定分支

    * master 是稳定分支

    * 其他分支 都是不稳定分支

    # 在分支上做什么

    * 一个分支做且只做一件事 (包括但不限于功能开发 修复Bug 整合 发布 紧急修复 等)

    * 如果要做多件事,开多个分支

    * 尽量避免多个人在同一分支上做事

    # 分支生命周期

    * 如果做完一件事,分支的生命周期就结束了,要删掉这个分支

    * 分支的生命周期,越短越好,不要长期持有一个分支

    # 从哪里来,到哪里去

    * 分支的上游只能是 master

    * 分支的下游只能是 release

    # 如何上线

    * 测试的版本就是上线的版本,上线的版本必须经过测试

  12. Tuzki Zhang

    不管 gitflow 的方式流程是否优秀但是它 “不仅需要每个人都能正确地理解和选择正确的分支进行工作,还对整个团队的纪律性提出了很高的要求”的缺点在实际工作中导致的问题经常是致命的!这点我深有体会

  13. 个人觉得,如何toggle是个大问题,为了CI,关掉那些没有完成或不想看到的feature, 如何关闭?高耦合的架构下,是不是要在code里加入大量if else来实现toggle?如何管理这些toggle? developer是不是要频繁的跟QA解释加了什么toggle? 不同toggle之间如果有依赖关系怎么办?

  14. tc

    个人觉得作者从根本上忘记了为什么会有commit这件事情。我们所遵循的原则是No Issue No commit,每个commit应该出现的位置是由issue所在的位置的决定的,而这个issue应该处于主线还是需要分支,这是一个技术决定。那么,当关闭issue时需要进行的合并,也是最自然合理的事情。而作者所推荐的那种Trunk Based Development方式,套用作者自己的话,就是Let’s commit code just because… we can!让release engineer在垃圾堆似的commit中挑选需要合并到发布分支里面去的commit,你确定有人愿意做这个release engineer嘛?

  15. dkmilan

    提出两点想法:
    1. 关于如何保持一个长期开发中的feature branch和主分支(可以是master或者这个feature对应的release branch)保持一致?可以每天把主分支merge回功能分支啊。。你只要在几个主分支上有持续集成,保证主分支代码随时可用,当你需要加新功能就从你需要的主分支上开一个新的feature branch开始开发。你的开发可以持续一段时间,最终完成了再merge回主分支。但是这段期间每天从你的主分支merge回你的branch上,就可以基本保证你的代码不至于在最终merge的时候需要处理大量冲突

    所有人都在master上干活然后release的时候一个一个往外cherry-pick? 这个工作太有风险了吧。。。

发表评论

电子邮件地址不会被公开。 必填项已用*标注