𝚲

迭代交付

迭浪

CICD1已经屡见不鲜了,然而在实践过程中,我们很有可能遗忘了其中隐含的一个前提,使得CICD落地的效果大打折扣。

正如团队里有人提出如下疑问:

对于一个长流程如何做到持续集成?若一个长流程本身就有几天的开发工作量,那该怎么去做持续集成?

先将流程的框架搭好再逐步去完善其中的细节?那框架应该达到什么程度,若中间涉及到数据存取的使用、依赖,因依赖部件未完成而导致无法集成怎么办?

软件开发长久以来受瀑布式开发2模式的影响,让我们惯性的认为集成是在所有组件功能完备以后才开始的。然而,迭代式开发3打破了瀑布的模式,强调以增量的方式,快速交付-验证-改进-再交付,如此迭代提升效率。CICD正是在迭代交付的背景下发展出来的.

迭代交付这个前提并非必要的,即便没有,实施CICD价值仍然是有的。不过,若能具备这个前提,无疑能让CICD更早的产生收益。

讲到这里,关于迭代交付这个概念还是太虚,不容易理解。 那么就来个栗子 🌰 吧.

interface FullTextSearchService {
  List<String> search(
    String keyword
  );
}

假设,要实现一个全文查询服务(FullTextSearchService)如上接口代码。根据设计其实现会非常复杂,包括:

从零实现上述特性的话,怕是要数周不止。为了能够尽早集成, 我们可以提供一个最小可用交付版本:

class EmptyResultSearchService
  implements FullTextSearchService {

  @Override
  public List<String> search(
      String keyword
    ) {
    return Collections.empty();
  }
}

你可能会认为这有什么好测的嘛? 是的,只有一行代码,显然不是期望中最终交付的样子,但就是这样一个最小可用版本,能够让你的代码编译-打包-部署-运行。

集成测试重点在于验证系统各组件之间的连通性,即接口的实现是否满足契约。换句话说,这个版本的集成测试重点是,search方法能否调通。

那是不是说集成测试就不关心逻辑功能了?

不是,能用集成测试覆盖所有场景那最好, 但那样的话自动化测试的研发投入很可能不亚于功能实现。相较而言,采用单元测试来覆盖接口的各种实现情况,才是投入产出比更高的选择。

以此类推,随着代码逻辑的叠加,每次迭代的版本就离最终交付的目标更近,同时每次的增量变动一旦出现偏差,即可在CICD中被发现并得以纠正。比如为了实现分页,接口需要调整:

interface FullTextSearchService {
  List<String> search(
    String keyword, 
    int page,
    int, pageSize
  );
}

接口的调用方若是同一个工程,那编译阶段就能发现问题,这是最幸运的。然而, 微服务架构下,调用方很可能是其他人开发的另一个服务, 这样破坏契约的变动越晚发现,代价就越惨痛。

知易行难啊,如果你问我,如何把握每次迭代的最小可用增量? 惭愧,我没有标准答案,唯有具体情况具体分析啦,共勉!


  1. https://en.wikipedia.org/wiki/CI/CD↩︎

  2. https://zh.wikipedia.org/wiki/%E7%80%91%E5%B8%83%E6%A8%A1%E5%9E%8B↩︎

  3. https://zh.wikipedia.org/wiki/%E8%BF%AD%E4%BB%A3%E5%BC%8F%E5%BC%80%E5%8F%91↩︎