目标:快速且安全地交付工作代码
持续集成的目的是将代码传递到存储库的主分支:
- 快速地:从将新代码推送到存储库以及将其合并到主分支的事情,应该在几分钟内完成。
- 安全地:我们怎么知道新代码生效呢?持续集成会设置正确的检查项以顺利地自动合并代码。
持续集成有点关于工具以及团队中的思维方式和文化。你希望在开发的过程中能够保持主分支的同时快速集成新代码。此工作主分支将在之后启用持续交付或持续部署(的操作)。但是,这些不是本文的内容。让我们先来关注下持续集成。
实现持续集成有两大支柱。
以小块工作
想象一下,一个五人组成的团队致力于一个SaaS产品。每个人都开发一个单独的新功能。每个功能的工作量大约是1或2周。有两种方式可以实现这个目标。
- 团队可以选择(一个)功能分支。每个开发人员都将在这个
"功能分支"
上工作。一旦每个人对自己的工作感到满意,此分支将被被合并到主分支。 - 团队(仍然)可以使用分支机构,但是每次推送时,将他们的工作集成到主分支。即使事情仍然在进行中!正在进行的工作对主分支的任何最终用户或测试者来说仍然是不可见的。
你认为哪种方法效果最好?
第一种方法最终将导致**“不可预测的释放综合症”**。长时效的特征分支为每个开发人员创造了一种虚假的安全感和舒适感。由于分支分离了很长一段时间,没办法衡量将它们合并(到主分支)的难度。其最好的情况是出现些少的代码冲突,在最坏的情况下,基本的设计假设将受到挑战,事情将不得不重新进行...这是艰难的方式。
返工的工作将在时间压力下进行,导致质量下降和技术债务积累。这是个恶性循环。
请参考关于为何不应该使用特性分支来处理脏细节的文章。
第二种方法是我们实现持续集成所需要的方法。每个开发人员都在自己的分支上工作。差异是:
每次推送都会将其更改合并到主分支,每个开发人员每天会将其分支与最新的主分支版本同步几次。
通过这种方式,团队可以更快且轻松地修复冲突并协调设计假想。**早期发现五个小问题比发布日前发现1个大问题更好。**查看下面的“功能切换”
部分,了解如何将“正在进行的工作”集成到主分支。
带有自动检查功能的安全性
之前的软件开发工程基于构建周期,然后是测试周期。这可能仍然适用“特征分支”方法(法一)。如果我们每天数十次集成和合并代码,那么,手动测试就没有意义了。这将花费太长的时间。我们需要自动检查以验证代码是否正常工作。我们需要一个CI工具,帮助开发人员自动推送并运行构建和测试。
测试类型和内容应该为:
- 足够快,能在几分钟内向开发人员提供反馈
- 足够彻底,能够安全放心地将代码合并到主分支
不幸的是,没有一种方式适合所有测试类型和内容。这要根据你的项目适当平衡。在CI阶段,不要运行大而耗时的测试套件。虽然这些测试提供了更好的安全性,但它们的代价就是对开发人员的延迟反馈。这将导致上下文工作切换,纯粹就是浪费时间。
优化开发者时间并减少上下文切换
长时间CI检查,我的意思是超过3分钟的(CI),消耗团队中的每个开发人员的大量时间。
让我们来比较下“好”和”坏“的工作流程
。“好”的工作流程:
- 你提交并推送你的代码
- CI构建和测试运行1到3分钟
- 在这1到3分钟内,你可以查看下手头的任务,在某些管理工具中查看状态,或者再次查看代码
- 在3分钟内,你获得CI(返回的)成功状态:你可以继续执行下一部分任务。如果你的构建失败:你可以立即解决问题
“坏”的工作流程:
- 你提交并推送你的代码
- CI构建和测试运行15分钟
- 你在这15分钟内做什么?
- 你可以和团队一起喝杯咖啡。这很公平,但是你每天有多少这些时间呢?
- 你可能会开始关注管道(工作流)中的下一个任务
- 15分钟之后,你收到构建失败的通知。你需要切回到上一个任务,尝试解决问题...并再循环一次15分钟...
- 那时你可能想:我是否应该再次回到下一个任务呢,还是再等15分钟,心平气和地去完成当前的任务...
这糟糕的工作流程不仅仅是浪费时间。对开发人员来说也是令人沮丧的。高效的开发会使得开发人员很开心的。
你需要调整工具和工作流程以保证开发人员的满意度。
工具
分支
持续集成是指将来自不同开发人员分支的代码集成到配置管理系中的公共分支。有可能你正在使用git。在git中,存储库中的默认主分支称为"master"
。一些团队创建了一个名为"develop"
的分支作为(开发时)持续集成的主分支。他们使用"master"
来跟踪交付和部署(develop分支将合并到master分支)。
你(的项目中)可能已经有了一个主分支,你的团队将代码推送或合并到那里。坚持(这样做)下去。
每个开发人员都应该在自己的分支上工作。如果同时处理许多不同的功能内容,可以使用多个分支。虽然这可能是"不专心"
工作的标志。只要代码连贯部分准备就绪,就可以推送到你的存储库。如果成功,CI将检查、启动并将代码合并到主分支。如果检查失败,您仍然在自己的分支上,可以修复需要的任何内容并再次推送。
上述过程中的关键语是你代码连贯的部分。那么,你怎么知道它是连贯的?简单。
如果你能够轻松地想出一个好的提交信息,那就是连贯的。
另一方面,如果你提交的信息需要分三次且带有许多形容词或副词,那可能并不好。多次拆分你的工作内容,连贯的提交,然后推送代码。连贯的提交有助于代码的审查,且能让仓库的历史记录更容易被遵循。
不要乱推送任何东西,因为这(有可能)意味着一天的结束!
拉取请求
pull request (拉取请求)
是什么呢?拉取请求是种概念,其要求团队将你的分支合并到主分支。接受你的请求应该通过你的CI工具提供的状态和潜在代码审查。最终由负责合并拉取请求
的人手动合并。
拉取请求诞生于开源项目中。维护者需要一种结构化的方式来评估合并之前的贡献。拉取请求并不是Git
的一部分。他们受到任何Git提供程序的支持(GitHub, BitBucket, GitLab, ...)。
请注意,在持续集成中,拉取请求并不是必须的。而拉取请求的主要好处是支持代码审查过程,这过程无法通过设计自动化。
如果你正在使用拉取请求,适用(下面)相同的原则或(上面提到的)“分块工作”和“优化开发者时间”:
- 保持每个拉取请求内容很小,并有一个明确的目的(它将使代码审查更容易)
- 快速完成CI检查
自动检查
持续过程的核心是自动检查。它们确保在合并代码后,主分支代码能正常工作。如果它们失败,则代码不会合并。至少代码应该编译或转换,或者你的技术堆栈应该做点什么以使其为运行时做好准备。
在编译之上,你应该运行自动化测试以确保软件正常工作。测试覆盖率越高,在将新代码合并到主分支时你就越有信心。注意了!更好的覆盖率意味着更多测试和更长的执行时间。你需要找到正确的权衡。
当你完全没有测试或者需要减少一些长时间运行的测试时,你要从哪里开始呢?专注于你项目或产品的至关重要的事项。
如果你要构建一个SaaS应用,则应该检查用户是否可以注册或登录,以及执行SaaS提供的最基本操纵。除非你正在开发Salesforce竞争产品,否则你应该能够在几分钟内运行测试,如果不是马上运行。如果要构建繁重的数据处理后端:使用有限的数据集来运行不同的构建块。在持续集成中保持大型数据集的长时间运行。合并代码之后,可以触发长时间运行的测试。
专业提示
功能切换
持续集成的关键概念是尽快将代码放在主分支中,甚至工作正在进行中。如果功能不完全正常,或者你不希望暴露给测试的人员或终端用户。实现这一目标的方法就是功能切换。在启用/禁止
切换下启用新功能。这切换可以是编译时布尔标志,环境变量或运行时事物。正确的方法取决于你想要 实现的目标。
功能切换的第一个主要好处是,你可以根据需求将它们投入生产并启用/禁止
新功能。你可以使用更改的环境变量来重新启动服务器,或者切换打开/关闭
一个新的UI仪表盘的布局。通过这种方式,你可以灵活地推出功能。如果在线上中导致意外问题,请将其禁用。或允许终端用户选择加入或退出该功能(使用UI切换)。
功能切换的第二个主要好处是它们会强制你考虑你正在执行的操纵与现有代码之间的界限。这是一个好的练习,如论何时,每次添加到现有系统时,都应该从这里开始。功能切换步骤使得该过程的这一步更加明显。
功能切换的唯一缺点是你需要定期从环境和代码中清除它们。一旦功能经过实测并被用户采用,它应该是默认(成功的)。应该清理切换的代码和旧版本的东西(如果有的话)。不要陷入“配置为切换”
系统的陷阱。你无法维护和测试切换的所有组合,(带来的缺点是)你最终拥有一个脆弱的架构。
保持CI构建时间不超过3分钟
谨记本文中的“好”和“坏”工作流程
。我们希望避免开发人员的上下文切换工作(的情况)。拿起你的手机,并开启3分钟的计时器。看看你等待构建完的时间有多长!3分钟应该是个绝对最大值,你可以集中精力并安全有效地从一个任务移动到另一个任务。
对一些团队来说,3分钟内的构建可能看起来很疯狂,但这绝对可以实现的。它和你组织工作的方式有关,而不是你使用的工具。优化构建的方法有:
- 使用更多构建容纳能力:如果你的CI工具上没有足够的并发构建和构建事件排队,开发人员就会浪费时间
- 利用缓存:大多数技术堆栈需要在运行新构建时安装和配置依赖项。当依赖项未更改,你的CI工具应该能够缓存这些步骤,以优化构建时间。
- 检查你的测试:检查你的测试是否经过时间优化。删除超时和“漫长地安全”等待步骤。如果要运行繁重的测试套件,请考虑在合并到主分支之后,在运行的单独构建中移除它们。它们不再是持续集成保护措施的一部分,但是无论如何都不应该进行繁重的测试。
- 拆分你的代码库:你必须在一个存储库中存储所有东西吗?你是否必须在所有内容上构建和运行测试,即使某些小部分发生了变化?这里可能就是突破点。
- 有条件地运行测试:仅在某些目录发生更改时运行测试。如果你的代码库组织得很好,这将是重大的成功。
强制缩短时间来限制你的CI检查的好处在于它使你从根本上改善整个开发过程。
正如Jim Rohn所说:
“成为一个百万富翁,不是为了百万美元,而是为了实现这一目标会让你很成功”
虚拟合并:不必全凭你的代码
大多数持续集成工具在你的分支上运行CI构建,以确保它是否可以合并。但是这不是我们感兴趣的内容。如果你知道自己在做什么,那么你推送的代码已经很有可能生效了。你的CI工具应该验证的是你的分支和主分支合并正常。
你的CI工具应该执行分支到主分支的本地合并,并针对该分支来运行构建和测试。如果主分支在此期间没有变化,则可以自动合并你的分支。如果确实发生了更改,则应该再次运行CI检查,直到你的代码可以安全合并为止。如果你的CI工具不支持此类工作流程,请换一个工具。
邪恶的任务管理
有种误解是,能够跟踪Agile板或像JIRA之类的bug跟踪器中相关的代码是件很酷的(事情)。这是一个很好的教科书概念,但是对开发的过程的影响肯定不值得付出努力。任务管理器提供了“功能和错误”
的视图。代码以非常不同的方式构建和分层。尝试协调任务管理器中的项目和一组提交是没有意义的。如果你想知道为什么编写一段代码,你应该能够从上下文和注释中获取信息。
后绪
工具仅仅是工具而已。设置工具可能是(花费)一个小时的事情。如果你错误的使用工具,你将无法得到预期的效果。
谨记我们为持续集成设定的目标:
- 快速且安全的传输工作代码
- 优化开发人员的时间并减少上下文切换
真正的意义是将你的思维方式转变为“不断为你的项目或产品提供价值”
。
将你的软件开发过程视为硬件生产设施。开发人员的代码代表可移动的部件。主要分支就是组装产品。
更快地将不同部分集成在一起并检查其能正常工作,你最终将获得更好的工作产品。
一些实操例子:
- 你正在开发一项新功能,并且必须更改其他人最有可能使用的低级别组件。为该公共组件进行相关的提交并将其合并。然后继续处理你的其它功能。其它开发人员将能够立即根据你的更改来开展工作。
- 你正在开发一项耗时和需要编码的大型功能?(这时)使用功能切换。不要孤立的工作,永远都不要!
- 你正在等待代码审核,但是没人可以执行此操作。如果你的代码正在通过CI检查,那么只需要合并它并在之后进行代码审查。这听起来好像是打破了既定的过程,但是请记住
“完成比完美更好”
。如果它正常工作,它在主分支中提供的价值比停滞在一旁几天要好。
后话
原文:fire.ci/blog/how-to…
文章首发:github.com/reng99/blog…
更多内容:github.com/reng99/blog…