---
title: 敏捷五问（二）：为什么我们要做主干开发
---

敏捷正逐渐变成开发日常的空气和水，对实践日渐熟悉的我们，有时反而忘记一些实践背后的目的，为实践而实践，把敏捷变成了另一种流程，然后反过来骂敏捷。本文将结合作者几年来的一线经验，重温敏捷的核心精神：「沟通」和「反馈」，以期达到正本清源，温故知新的效果。

主干开发是持续集成的基石，是代码级敏捷的重要实践，也是实现高响应力的基本技术实践。没有主干开发，将阻碍团队进行持续重构、持续改进、持续部署，进一步阻碍了团队敏捷和响应力的提升。

**基于主干分支开发**与**基于分支开发**，应该是使用 Git 做 VCS 最常见的两种开发模型了。我一直是支持主干开发的，过去的一年在推广的过程中，我得以了解到拥护分支开发同事的观点。借助这种对比，我们可以清楚了解，什么项目上适合推 TBD（Trunk Based Development，基于主干开发），什么项目适合使用分支开发。

我们可以把 TBD 和分支的优缺点拿出来做一个表格比较。并且在这个表格中，我的观点是，我们看重左边「持续集成」的优点，多于看重右边「隔离开发」的优点，但并不认为「隔离开发」不重要。

|              |                   主干开发                   |                                      分支开发                                      |
| :----------: | :------------------------------------------: | :--------------------------------------------------------------------------------: |
|     优点     |               持续集成团队代码               |                                      隔离开发                                      |
|              |                  对重构友好                  |                         合并代码前有 PR 保证代码质量的机会                         |
|              |        最早暴露冲突减少修复风险与成本        |                                PR 允许更透彻的讨论                                 |
| 缺点（成本） | 需要开发者做原子提交，也就需要任务分解的能力 |                                对「语义重构」不友好                                |
|              |   需要将测试随产品代码一并提交做到质量内置   |                            合并代码时可能有大冲突的风险                            |
|              |                                              | 周期变长（比如同时修文档，写脚本），需要频繁切换分支，并且需要**等待**它们被 merge |

在推动主干分支的过程中，依次遇到以下的疑虑、担忧和挑战：

> 持续往主干上合并代码，不会使得提交历史非常琐碎、非常混乱吗？我希望提交历史是完整的，看提交信息就能知道做了哪个卡，实现了什么功能

这是最初的疑虑，特别是对于习惯了分支开发的同学而言。持续往主干上提交，会不会使提交历史粒度变细？会；会不会使提交历史琐碎？不会；会不会使提交历史混乱？不会。提交信息的完整性，相反，当你的提交粒度很大时，提交信息除了写「实现了新闻快讯功能」以外，没法更细，看提交信息确实清楚，但看具体提交内容时，则不容易看出大量提交中某些代码所解决的问题，回滚起来除了整个提交回滚也没法进行更细粒度的操作。一旦该提交使主干挂掉，调试、回滚起来响应力往往不快。不敏捷。

这里其实提出了「提交历史」这个价值。但我认为，放在「持续集成」这个角度下去看，提交历史的价值在于「提供上下文」以及「快速回滚」。相对于「经常回去看提交信息并希望看到一个完整的提交信息」这个场景，经实践认为它发生得并不如「主干发现了问题需要快速定位、回滚某个提交」频繁。

> 那么什么是持续集成？持续集成的价值在哪里？

这是第二个常见的问题。当我们谈「持续集成」时，团队对于「持续集成」是什么、有啥用，是模糊的、不可感的。

那么持续集成是啥意思呢？是指 [团队成员每人每天至少向仓库（主干）提交一次代码](https://martinfowler.com/articles/continuousIntegration.html)。

那么持续集成有啥用呢？为啥要每人每天至少集成一次呢？好处多多，我认为关键有二：**尽早集成**、**尽早暴露冲突**，核心就是**减少集成带来的项目风险和修复成本**。

> 但我们因为集成带来的冲突场景多吗？就算持续集成了，部署还是每月一次，依然没法做到持续交付给用户，持续集成了又有啥意义呢？

如果集成冲突并不多，部署频率由于流程无法提升，那么没有做持续集成的动力，是可以理解的。但这只是回答说要不要的问题，没有回答对不对这个问题。就好比你说，我 20 几岁身体健康无病无灾，所以随意饮食、不锻炼身体也没毛病。当然没毛病，你自个负责就好。没有动力、也没有迫切需求做持续集成的团队，这个负责的人基本上就是 TL。他要负责评估、推动、担责。

集成冲突不多，可能是因为客观上没有工作在交叉的代码上，可能是因为没有经常重构。前者团队并没有决定权，要看客观上业务有没有交叉；后者…

> 对啊，我们基本都不做重构的。代码一旦提交，一般就不会再动，否则又要 QA 再测一遍，浪费人力物力。

所以说这有问题啊。不持续重构，怎么持续清理代码呢？代码质量靠一次弄对来保证么？持续改进从何谈起呢？

不做重构的团队，可能有很多原因，除了意识没跟上以外，也有可能是自动化测试体系不合理、落地不到位，也可能根本就没测试；有可能没有团队代码所有制意识怕背锅，也可能团队成员编制部门不同怕背锅；可能是自动化测试不属于项目愿景/要求/验收标准的一部分所以无人关心，可能公司流程部门墙所导致…不一而足。但这都是应该要去解决、至少尝试解决的问题，不经常重构是不合理的。由 TL 来回答这个问题。

> 好，我已经认可「持续集成」有价值了，但我还是习惯手动测试、在分支上一口气搞完后再合并。而且现在也没有频繁上线、频繁部署、重构的需求，我觉得维持现状就挺好，学原子提交学写测试优先级不高。

优先级不高就是不想做嘛，这不是个技术问题。

> 直接向主干上提代码，可能造成新人或没经验的同事代码质量低，怎么解决？

分情况。我觉得可行的做法是：

1.  对于有经验的团队新人，可以通过 pair 的方式使其快速上手
2.  对于没有（敏捷）工作经验的新人，除了 pair 的方式外，还可以让他在分支上先开发一段时间，再切回主干上来

原则是，确实需要用分支来保证质量，那就用；当团队能力达到比较高的水准后，依靠能力来做「持续集成」带来的好处就大于依靠流程来保证「代码质量」。还是那句话，做正确的事情。

> 我们用了一些其他的工作模型，比如 git submodule，使得保证提交原子性变得更加困难了，怎么办？

使用了 submodule，对团队进行原子性提交的要求确实高得多的多，并且它也要求团队不仅基于主干来开发，还需要频繁 pull/push 更新。一旦在本地积累过多提交，对 submodule 的引用更新就会使得保证中间提交的引用正确性非常困难。在我们上个项目中，虽然做了主干开发，但 submodule 引用的原子性就是牺牲掉了的。也没出大问题。因为，我们也是一个月上线一次，本质上做了持续集成也不会体现在持续交付上。

怎么办呢，如果团队能力一般，项目又有了 submodule，那么尝试推广时要做更多的工作；如果团队能力还行，对主干开发也接受，那解决起来也简单，习惯就好。

## 问题及解决

**做不到原子提交，导致别人一拉代码跑不起来，或者经常挂 CI**

多注意几次就好了。特别是在 submodule 模型下，对原子性要求很高，有时忘了更新 submodule 的引用，就会被 CI 检测出来。但有时检测不出来。

**rebase 有时在同时新增同一份代码或同时删除同一份代码的场景下不够智能**

暂时没法。这是 `git rebase` 本身的问题。解决方法：提升集成频率、提高 git rebase 技术。

**code review/refactor 可能在 QA 测试之后发生，引起重新回归**

几个问题：

1.  流程问题。把尽可能多的 refactor 在验卡的时候一并交付
2.  测试问题。建立合适的自动化测试体系，减少回归成本，不惧重构

**只要有人不基于主干去开发，还是有不小的冲突风险，因为他个人没有与团队进行集成**

个人确实有自主决定是否拉取主干代码、是否更新代码到主干的权利。这不是纯粹的技术问题。由 TL 解决。

**rebase master 有时确实会打断开发流程，并不能经常进行无缝 rebase**

有时没法直接 `git checkout master`，因为有冲突时就必须修。而有可能你只是想看一下 master 的代码。那这种情况的话，是否可以 `git fetch` 而不 `git pull`？显然可以。

总之一句话，要想保证主干分支的优势，**需要开发者保证 commit 级别的原子性和代码质量**，**需要开发者保证 commit 级别的原子性和代码质量**，**需要开发者保证 commit 级别的原子性和代码质量**。就这一点。这个东西和其他实践是相辅相成的，一起跟进效果更好，它不是一个单点的问题。
