对嵌入式软件开发中导入敏捷和持续集成CI的思考

date
Jul 27, 2024
slug
2024-07-27-Embedded-Software-Development-with-Scrum-and-CI
status
Published
tags
嵌入式
软件工程
type
Post
AI summary
summary
本文基于自己阅读《持续交付2.0》一书的心得体会,以及与客户交流所学习得到的信息,整理出来一些把敏捷开发和持续集成等软件工程理念应用在嵌入式软件领域中的思路。
我自己主要从事的工作是消费类电子领域中的嵌入式软件开发,之前在软件工程方面,对敏捷开发和DevOps这类概念看到的非常多了,也阅读过几本书建立了系统的概念,但是始终认为这类概念更多适用于纯软件,尤其是Web、APP、PC软件等很容易更新和部署新版本软件的开发领域。对于嵌入式软件及电子产品而言:
  • 软件的开发需求受限于前期的硬件规划和成本(尤其是在消费类电子领域,为了尽量降低成本,不可能在项目前期选用超过需求太多的硬件规格),产品需求和硬件规格一定要在项目立项之初就要考虑的非常清楚,否则等到了项目后期,发现硬件无法满足某些新增加的需求,那就是灾难性的后果了,
  • OTA升级受限于网络连接以及软件实现的完备性往往也没有那么方便和安全。
所以对于在嵌入式软件中,还是踏踏实实的做好瀑布开发管理模型才是王道:一开始就把需求严格定义好,根据需求确定好硬件规格和软件设计方案,然后进行开发、测试最后交付,整个过程中尽最大可能避免需求变动。
最近读了一本叫做《持续交付2.0:业务引领的DevOps精要》的书,以及与某个大客户讨论敏捷和持续集成在他们其他嵌入式产品中的落地运行,对于我以前的想法有比较大的冲击。尽管因为嵌入式软件以及硬件产品本身的特性,持续集成部署以及敏捷的很多做法在这类软件开发中全面导入是不现实的,但是导入部分做法,尤其是频繁提交与持续集成、自动化测试等思想,还是可以帮助在嵌入式软件领域较为有效的提升团队的工作效率。

瀑布式开发的问题以及敏捷/CI如何解决

放在硬件产品和嵌入式开发领域来进行思考,瀑布式开发管理模型的问题主要是:
  • 没有办法很有效的应对在软件项目中,尤其是复杂的、快速变动的需求环境中的大型软件存在的软件复杂性和不确定性。频繁变动的需求对于瀑布式开发的规划绝对是一个灾难.
  • 按照瀑布式的工作流程设计,软件模块设计和开发完成,到了项目的后期才会进行集成测试,不光后期的模块集成和调试解决集成问题会花费巨大的精力和时间,而且整个团队和用户只能在项目后期才能看到整体的运行效果,验证其是否与前期的构想设计一致。一旦在后期发现实际的实现与预期的规划不一致或者框架方面出现较大的问题,解决问题的成本会几何量级的飙升(尤其是该问题与硬件相关的情况下),项目的进度和成本风险自然极难管控。
针对瀑布式开发方法的问题,敏捷开发方法把需求按照user story进行详细拆分并排列优先级,并且按照2-4周为作为一个迭代单位,一次迭代只开发其中一部分需求,但是每次迭代都会产生一个可以运行、质量有保证的软件(当然其中只包含了总体设计的一部分功能)。迭代过程中尽可能通过自动化的构建和测试,加快构建和开发速度,减少不必要的工作量。迭代结束后通过回顾会议总结本次迭代的得失作为后续持续改善的基础,并且再在需求池中选择合适的需求列表确定下一次迭代的开发内容。这样的做法,从项目开发初期开始,就始终持续有能够可以正常运行的版本用于验证初期的构想,后期阶段不同代码模块之间的集成压力与工作量也要小很多,问题尽可能暴露在项目早期,解决的成本会小很多。

Git与CI环境

其实如果只是作为一个团队协作的代码管理工具,无论是SVN还是Git都可以运行的非常好了。但是在分支管理、代码评审以及与各种CI/CD工具的流程整合方面,Git明显要更优秀一些,相关的资料和解决方案也更多。
经过10多年的发展,目前业界已经有不少成熟的CI/CD平台可用,对于中小型公司及其项目的持续集成和开发方面的需求而言,基本上已经足够了,典型的有Gitlab,Jenkins以及《持续交付2.0》作者所参与开发的GoCD等。
参考文档2提供了一篇非常详尽的使用Gitlab CI/CD模块创建持续集成任务的说明,整个过程实际上就是通过在脚本上调用序列化以及并行开展的任务,在代码提交到Git仓库后触发,完成自动化测构建、测试和部署的任务。对于嵌入式软件的持续开发和集成而言,也没有什么特殊的地方,只需要按照脚本的流程在Linux环境中调用不同的脚本去执行对应的任务即可。
notion image

需求拆分

要支持敏捷或者CI的开发,需求一定要被拆分的足够细且相互独立,才能够确保安排开发人员并行工作,并且在一个迭代周期内顺利完成开发测试的完整工作。所以作者提出来需求拆分的INVEST原则:
  • Independence:各个拆分后的需求是相互独立的)
  • Negotiable:需求在产品人员和开发人员之间是可协商的
  • Valueable:这个开发需求是有价值的,而且在当前开发阶段是高价值的优先安排
  • Estimable:需求的工作量和成本是可估算,并且可以在当前的开发迭代中完成
  • Small:需求的规模小并且适中,要考虑开发和解决测试问题的周期符合迭代的时间安排
  • Testable:需求开发完成是可以独立测试的
最终拆分出来的需求就是所谓的User Story。
每个敏捷迭代中应该从需求池中选择最有价值的User Story列表,结合对工作量和开发测试验证所需时间的评估,在当前的迭代周期中安排完成。

自动化单元测试和模块测试

对于嵌入式软件的开发和测试而言,导入敏捷开发和持续集成的理念,最大的问题实际上就是如何实现自动化测试。毕竟无论是敏捷还是CI,最终的结果都是通过频繁的提交代码,频繁的出编译版本,把代码中存在的问题尽可能暴露在早期。如果没有自动化测试工具和流程的加持,仅仅依赖于测试人员的人工测试来完成这么频繁的版本测试,毫无疑问是不现实的。所以,要能够让敏捷和持续集成的工作流程在嵌入式开发中真正发挥作用,拥有一个强大的自动化测试流程必不可少。
但是自动化测试如何能够在不同规格的嵌入式硬件上运行,并且能够发挥出来稳定、有效的作用,则是一个难度比较大、而且与产品需求本身以及硬件规格、嵌入式运行的操作系统等方面密切相关的问题。因此,往往只能根据项目本身的情况,量身定制才能做的比较好。当然针对嵌入式开发软件中常用的C、C++语言而言,也有完备的单元测试框架可用,例如CppUnit。
开发人员在开发代码的过程中,就需要同时针对性的基于自动化测试框架和流程,完成测试自己代码的测试用例的实现,并且能够在自动化测试流程中执行。并且要确保测试代码不是代码中的二等公民,为了保证测试环节的严谨性和完善性,一定要持续关注测试代码的实现质量。

代码评审和提交

对于一个人员变动会比较频繁的项目团队而言,如何保证团队中每个开发人员所提交的代码的内建质量,对于软件项目可维护性的持续以及不断改善,始终是需要严肃关注的课题。如果不做严格的要求和控制,任由各个开发人员按照自己的思路和代码风格上传代码,代码的可维护性会变得越来越差。
针对这个问题,需要在团队中有明确的编码规范,要求开发人员按照编码规范实现代码实现以外,通过Code Review来控制代码的提交质量也是一个必不可少的环节。
在之前的瀑布式开发模型中,往往是各个模块完成开发以后,才会在主干上进行代码合并,代码合并过完带来了巨大的Code review工作量,这种情况下Code review的效果很难做好,尤其是考虑到模块集成后还有那么多bug要解决,Code review这种看不到短期效果的事情往往会被调低优先级,久而久之这个过程就失去了控制代码内建质量的价值,纯粹成了走流程的一个环节而已。
在持续集成的工作流程中,通过把需求拆解的足够细,开发人员每次提交到主干的代码工作量限制在2-3天之内,进行一次Code Review的工作量就不会太大,整个流程就可以比较流畅的运行起来。
所以,配合持续集成的开发流程,应该限定每个开发人员单个需求开发的工作量和代码提交量,并且所有提交代码一定要经过code review后才能合入主干。

完整开发流程

  • 需求来源:需求池中遗留的需求列表,客户提供新的开发需求,测试人员反馈的bug
  • 设计方案及其Review:技术Leader或者架构师组织开发人员,针对开发需求讨论确定技术方案,提供相关设计文档,并在内部针对具体的实现确认清楚
  • 开发人员在自己的开发分支上按照确定好的技术方案文档实现功能需求或者解决bug,自测功能测试通过
  • 开发人员在提交代码之前,通过脚本自动化运行动静态扫描、代码规范扫描、自动化执行冒烟测试用例,解决扫描出来的问题
  • 开发人员通过Git Pull Request提交代码合入主干的申请
  • 技术Leader进行code review,确保代码符合规范要求以及设计方案的实现思路后合入代码到主干
  • 新的代码合入主干后,自动触发CI服务器的持续集成流程:在持续集成环境中搭建编译环境,下载全新的代码,进行代码静态扫描、自动化编译,以及自动化执行单元、模块以及集成测试用例,以上过程均通过后上传编译好的镜像到镜像版本服务器。
  • 测试人员从镜像版本服务器上拿到新的版本,开始进行手工测试,以及安排进行其他的自动化性能和压力测试。以上测试均通过后发布正式版本。
总的来讲,对于硬件产品上的软件开发,也就是嵌入式软件而言,持续CI/CD流程中的CI完全是可行的,整个需求拆解、代码提交和自动化构建的流程与其他软件别无二致,只不过在以下方面需要额外重视:
  • 需求方面,需要考虑到硬件本身的规格,对于硬件定型以后需求方面的延展有着非常明确的约束,这类约束没有可能像Web开发那样通过在云环境中增加服务器或者带宽就能够轻易解决。
  • 自动化测试方面,哪些环节和模块的测试可以放在开发环境中测试逻辑就可以,哪些必须要放在目标机的硬件上运行才能得到准确可靠的测试结果。
但是作为CD的持续交付和部署,自然就不适用了,无论如何都不可能自动地把持续集成流程所验证过的镜像直接部署到OTA服务器上。当然,对于绝大多数电子产品而言,也不需要频繁的嵌入式软件更新,更新的过于频繁往往还会给用户非常糟糕的使用体验,所以CD对于嵌入式开发流程而言并非是一个多大的痛点。

参考资料

  • 《持续交付2.0:业务引领的DevOps精要》

© Pavel Han 2020 - 2024