文章目录
书籍信息
书名:《Google软件工程》
作者:[美]提图斯·温特斯 / [美]汤姆·曼什雷克 / [美]海勒姆·赖特
豆瓣链接:https://book.douban.com/subject/35838155/
理论
什么是软件工程
时间与变化
对任何一个从一开始就没有计划升级的项目,在持续数年之后必须进行重大升级时会遇到非常大的困难:
- 存在更多隐藏的假设被注入程序当中。
- 工程师不太可能有此类任务的相关经验。
- 升级的规模比较大,而非渐进式的。
海勒姆定律:当一个API有足够多的用户时,在约定中你所承诺的已不重要:所有在你系统里面被观察到的行为都会被一些用户所依赖。
在代码的预期寿命内,我们能够对依赖关系、技术或者产品需求的变化做出响应时,软件是可持续的。
规模与效率
服务器、人力成本、代码库,都面临规模问题。
促进规模化的政策:
- 让负责基础设施的团队能够安全地进行基础设施的变更。
- 专业知识和共享的交流论坛也提供了巨大的价值。
影响代码库灵活性的因素:
- 专业知识:如何进行渐进式更新和遇到的问题
- 稳定性:频繁的渐进式更新
- 一致性:渐进式更新减少没有升级的旧代码
- 熟悉:在升级过程中发现优化点与改进
- 政策:通用的规则与分享周知
当与规模经济相结合时,专业知识的回报尤其丰厚。
权衡与成本
决策涉及到权衡成本:
- 财务成本:金钱等
- 资源成本:CPU时间等
- 人员成本
- 交易成本:采取行动的成本
- 机会成本
- 社会成本:对社会的影响
有些工程量很微妙,或者我们不知道如何度量。这种情况,没有简单的答案。需要依赖经验、领导才能和先例来协商这些问题。
数据驱动的文化带来的一个隐性的好处是:它将承认错误的能力和必要性结合在了一起。
即:随着新数据的出现会推翻原来错误的决定。
数据驱动是一个好的开始,但实际上,大多数决策都是基于数据、假设、先例和论据的混合。
组织必须重复执行的每项任务的人力投入都应该具有可扩展性,随着时间和规模的变化,线性变化或者更高。
软件工程 VS 编程
编程和软件工程之间有3个关键差异:时间、规模和权衡。
软件工程就是随着时间而不断集成的编程。
两者是不同的问题领域,就有不同的约束、价值和最佳实践。
文化
如何更好地参与团队合作
隐藏有害
早期分享可以尽早防止个人错误。
巴士系数:软件开发有一个“巴士系数”,指多少关键开发者被巴士撞了会让项目停摆。
一切为了团队
社交的三大支柱:谦虚、尊重、信任。
- 快速失败和迭代。
- 无指责的回顾文化。
谷歌范儿
- 在变化莫测的环境中施展才能
- 让反馈变得有价值
- 挑战现状
- 用户优先
- 关心团队
- 做正确的事
知识共享
学习的挑战
- 缺乏心理安全感
- 信息孤岛
- 单点故障
- 专家或新手两极化
- 机械魔方
- 害怕出错
知识共享的哲学
软件工程可以定义为多人开发多版本程序。
设定基调:心理安全
- 导师制
- 大型群体的心理安全:团队互动
不断充实知识
- 持续学习,不断提问
- 理解上下文
扩大提问渠道:向社区提问
比如:群聊、邮件列表、问答平台。
分享你的知识:你总有可以教别人的地方
比如:Office Hours(定期安排的问答活动)、技术讲座与课程、文档、代码。
组织知识发展
一个组织需要一种学习型文化,它要能为员工创造一种心理安全感,让员工承认自己缺乏知识。
在系统层面上,鼓励和奖励那些花时间去教授和扩充他们专业知识的人或团队、组织。
建立规范的信息源:开发者指南、GO/LINKS(谷歌内部URL缩写,指向一个具体内部条目的介绍,如:go/python 就是谷歌的 Python 开发指南)、Codelabs(谷歌的指导性的实践教程)、静态分析(共享最佳实践)。
让信息流动:资讯、社区。
可读性:通过代码评审规范化指导
代码的阅读量远远超过它的书写量,可读性重要性不言而喻。代码评审在谷歌是强制的,这已经是一种文化。
平等工程
人类的偏见
偏见是固有的。
理解多样性的必要性
对于面向所有用户群体的设计来说多样性是必要的。
建立多元文化能力
使多样性具有可操作性
不要纸上谈兵,把重点放在可量化和可操作性的步骤上。
拒绝单一的方式
问题是复杂和多因素的。
挑战既定流程
挑战自己构建更公平的系统。
价值观与成果
- Build With Everybody,让用户参与进来
- 为困难用户而设计
- 在整个系统中衡量公平
- 改变是可能的
保持好奇心,向前推进
团队领导的艺术
从个人贡献者到领导者
最重要的是,要克制住管理的冲动。
方法是:充分运用“仆人式领导”。
反模式
避免如下行为:
- 雇佣平庸的人
- 忽视低绩效员工
- 忽视“人”的问题(不能只注重技术)
- 做老好人(不必成为团队的亲密朋友)
- 打破招聘门槛(不要只是选择headcount的人,而是选择符合招聘标准的人,宁缺毋滥)
- 像对待孩子一样对待你的团队(应该信任和放权)
积极的模式
- 抛弃“自我”意识
- 成为一名禅师
- 成为催化剂
- 移除障碍
- 成为老师和导师
- 设定清晰的目标
- 坦诚
- 追踪幸福感
其他提示和技巧
- 委派工作,也要亲自动手
- 寻找替代者
- 知道什么时候该搞事情
- 保护团队远离混乱
- 给团队提供空中掩护
- 让团队知道他们做得很好
- 对“低成本尝试”的事情说“是”
对待人像植物一样
内在激励和外在激励。
大规模团队领导力
总是在做决策
模糊不清的问题没有神奇的答案;它们都是需要找到当下正确的权衡并进行迭代。
总是不在场
建立一个组织,能在不需要你在场的情况下自动地解决一类含糊不清的问题。
总是在扩展
随着时间的推移,成功会产生更多的责任,必须主动管理这项工作的规模扩展,以保护稀缺的个人时间、注意力和精力。
度量工程生产力
为什么要度量工程生产力
为了让每一个人更有生产力,并高效地做到这一点。
鉴别:它值得度量吗
不适合进行度量的情况:
- 现在还无力改变流程/工具
- 任何结果不久都会因为其他因素而无效
- 结果将仅用作虚荣心指标,以支持你无论如何都要做的事情
- 仅有的度量指标不够精确,不足以度量问题,而且可能会与其他因素混淆
根据目标和信号来选择有意义的指标
在谷歌使用目标/信号/指标(GSM)框架来指导指标创建。
- 在创建指标时鼓励使用一些理想的属性。
- 在实际测量结果之前,提出适当的度量指标集,并制订一些有原则的方法,从而避免指标蔓延和指标偏差。
- GSM可以向我们展示度量覆盖到了哪些地方,哪些未覆盖。
目标
将生产力分为五要素,使用QUANTS:
- 代码质量;
- 工程师的专注力;
- 认知复杂度;
- 节奏和速度;
- 满意度。
举例,可读性流程的生产力目标,代码质量方面:工程师通过可读性流程编写出高质量的代码、更具有一致性的代码。
信号
信号就是用来让我们知道是否已经实现了目标的。
并非所有的信号都是可测量的,但是这个阶段还是可以接受的。
每个目标至少有一个信号。有些目标也可能共享同一个信号。
举例,目标:工程师通过可读性流程编写出高质量的代码,信号:拥有可读性认证的工程师认为其代码的质量比未拥有可读性认证的工程师更高。
指标
指标是我们决定如何测量信号的地方,是信号的可度量代理。一个信号可能有多个度量指标。
有些信号可能根本无法测量,那么这些信号就可能没有任何相关的指标。
通过抽样研究,发送调查问卷来验证指标是否正确表达了信号。
样例
评估可读性对生产力的影响:
采取行动并跟踪结果
度量生产力后,无论结果是正面还是负面的,都要针对结果采取行动。
流程
风格指南与规则
为什么要有规则
规则和指南应该旨在支持对时间和规模的韧性。
创建规则
关注要达成什么目标。
总体原则:
- 发挥规则的作用。
- 为读者优化。
- 保持一致性。
- 避免容易出错和出乎意料的构件。
- 必要的时候要服从实际情况。
语言风格指南:
- 避免危险的规则。
- 执行最佳实践的规则。
- 确保一致性的规则。
修改规则
更新规则需要流程,随着时间推移而更新。
在某些情况下,允许打破规则的例外存在比死守规则更有益。
指南
通过编程指南来对复杂主题进行长时间深入讨论,对最佳实践提出针对性的建议。
应用规则
只有当规则被强制执行时,才能产生更大的价值。
规则可以通过教学和培训这种社会性方式实施,也可以通过工具的技术手段实施。
检查代码合规性时,更推荐使用工具自动执行,确保不会遗忘且执行没有差异性,适应各种规模的扩张。
具体又有:错误检查工具,代码格式化工具。
代码评审
代码评审的好处
- 检查代码正确性。
- 确保代码变更能够被其他工程师理解。
- 增强整个代码库的一致性。
- 心理上促进团队的责任感。
- 组织内的知识共享。
- 提供代码评审本身的历史记录。
代码评审最佳实践
- 礼貌而专业
- 提供批评反馈的机会
- 小的变更
- 清晰的变更描述
- 评审者数量最少化
- 尽可能的自动化
代码评审类型
- 绿地代码评审:全新代码。
- 行为变更、改进和优化。
- 缺陷修复和回滚。
- 重构和大规模变更。
文档
什么是文档
文档是指工程师为了完成工作而需要编写的所有补充文本,不仅包括独立文档,还有代码注释。
为什么需要文档
- 在时间和规模维度上,文档都非常重要。
- 高质量的文档对工程组织来说作用巨大,受益者往往是多个团队。
- 帮助制定API。
- 提供了维护路线图和历史记录。
- 使你的代码看起来更专业。
- 减少其他用户提的问题。
像代码一样对待文档
- 要有遵守的内部政策或规则。
- 置于源代码管理系统之下。
- 有明确的责任主体负责维护。
- 文档变更应该利用现有的开发人员工作流。
- 进行变更评审。
- 跟踪问题。
- 定期评估。
- 如果可能的话,要对准确性、有效期等方面进行度量。
文档类型
- 参考文档,包括代码注释。
- 设计文档。
- 新手教程。
- 概念文档:比参考文档更深入的解释。
- 着陆页面:团队页面的主页链接。
文档评审
- 技术评审,保证文档的准确性。
- 读者评审,保证文档的清晰度。
- 写作评审,保证文档的一致性。
文档的哲学
5W:
- who
- what
- when
- where
- why
好文档:完整性、准确性和清晰性。
聚焦文档读者的需求,少即是多。
测试概述
为什么要写测试
- 更少的调试。
- 增强变更的信心。
- 改进的文档。
- 更简单的评审。
- 深思熟虑的设计。
- 快速、高质量的发布。
设计测试套件
碧昂丝规则:如果你喜欢它,你就应该测试它。
自动化测试是实现软件变更的基础。
要进行大规模测试,必须实现自动化。
为了保持监控的测试覆盖率,平衡的测试套件是必要的。
代码覆盖率可以提供对未测试代码的一些洞察,但它不能代替对系统的测试情况的批判性思考。
如果不能保证测试套件的确定性和运行快速性,那么它将成为提高生产力的障碍。
谷歌测试的历史
三个关键的举措帮助塑造了谷歌的工程文化:
- 入职培训课
- 测试认证
- 马桶测试:在厕所里张贴传单
自动化测试的局限性
通过自动化测试来涵盖人们已经充分理解的行为,使人工测试人员能够花费大量的精力和定性的工作,来专注于产品中他们能够为之提供最大价值的部分。
单元测试
可维护性的重要性
防止脆弱的测试
脆弱的测试是指在对生产代码进行不相干的变更,而又不会引入任何真正的缺陷时,但却令其失败的那些测试。
脆弱的测试会给任何规模的代码库带来痛苦。
- 努力做到不更改测试。
- 通过公共 API 进行测试。
- 测试状态,而不是交互。
编写清晰的测试
- 使测试完整简洁。
- 测试行为,而不是方法:强调行为的结构化测试,以行为给测试命名。
- 不要把逻辑放进测试。
- 编写清晰的失败信息。
测试与代码共享
测试代码不应该完全是 DRY,而应该努力保持 DAMP(描述性和有意义的短语)。
DAMP 不能代替 DRY,而是对 DRY 的补充。比如工具类,有些setup方法或@BeforeTest方法。
测试替身
测试替身对软件开发的影响
使用测试替身也会带来一些复杂性,需要进行一些权衡:
- 可测试性
- 适用性
- 保真度
在谷歌,因为过度使用模拟框架也带来了教训,即:尽管这些测试很容易编写,但我们却遭受了巨大的损失,因为它们需要不断的维护,而很少发现缺陷。
基本概念
缝(Seams)
如果可以为代码编写单元测试,那么我们称代码是可测试的。
缝是一种通过允许使用测试替身来实现代码可测性的方法,它可以为被测系统使用不同的依赖项来替代在生产环境中所使用的依赖项。
依赖注入是引入缝的常用技术。
如果是动态语言(如 Python 或 JavaScript),那么可以动态替换单个函数或对象方法。
模拟(Mocking)框架
模拟框架是一个软件库,它使在测试中创建测试替身更加容易;它允许使用一个模拟对象来替换真实对象,模拟对象是一个在测试中内联指定其行为的测试替身。
模拟框架实际上是减少了样板代码。
大多数主要编程语言都有模拟框架。
使用测试替身的技术
伪实现(Faking)
伪实现是 API 的轻量级实现,其行为与实际实现类似,但不适合生产环境。
打桩(Stubbing)
打桩是将行为赋给一个函数的过程,该函数本身并没有行为,你可以确切地为该函数指定要返回的值。
交互测试
交互测试是一种在不实际调用某函数具体实现的前提下验证如何调用该函数的方法。比如,测试是否已经调用,调用参数是否正确,以及调用次数是否不多不少。
实际实现
尽管测试替身是非常宝贵的测试工具,但是我们对测试的第一选择是在有测试依赖项时,直接使用系统的实际实现。
在谷歌,对实际实现的偏好随着时间的推移而增强,其优点有:
- 被测系统更加真实;
- 能给我们更多信心;
- 更容易发现实际实现中存在的问题;
使用实际实现的考量:
- 执行时间:是否足够快速
- 确定性:是否稳定输出相同结果
- 依赖构造:层级依赖数量和难度
伪实现
- 伪实现是比其他测试替身技术更好的选择,因为它的行为与实际实现类似;
- 为了减少需要维护的伪实现的数量,通常应该只在测试中不可用的代码“根”上创建;
- 伪实现必须对实际实现具有适度的、完美的保真度,即保真程度不多不少;
- 伪实现必须有自己的测试;
打桩
过度使用打桩的危险性:
- 测试变得不清晰
- 测试变得脆弱
- 测试变得低效(无法保存状态)
交互测试
交互测试不如状态测试(验证返回值等是否正确,如其他测试替身技术):
- 不能告诉你被测系统是否正常工作,而只是验证某些函数是否被按照预期调用过;
- 利用了被测系统的实现细节,并将实现细节暴露到测试中。
如果调用函数的次数或者顺序会导致非预期的行为,那么,状态测试无法胜任,只能使用交互测试。
较大型的测试
什么是较大型的测试
较大型测试覆盖率单元测试难以覆盖的范围,比如:范围最大的测试又称为系统测试或者端到端测试。
一个好的设计包括一个可以识别风险的测试策略,以及可以缓解这些风险的较大型的测试。
它的特点有:
- 可能会慢;
- 可能是非封闭的;
- 可能是非确定性的;
较大型测试存在的主要原因是为了解决保真度问题。保真度是测试反映被测系统真实行为的属性。影响的因素很多,比如:
- 替身的存在;
- 配置问题;
- 负载时出现的问题;
- 意外的行为,输入和副作用;
- 突发行为和“真空效应”。(单元测试就像理论物理学:被困在真空中,巧妙地避开了现实世界的混乱,而只是关注理想的速度、加速度等)
较大型的测试的实施困难和挑战:
- 不满足可靠、快速、可扩展的测试原则;
- 所有权不清晰,影响组织、执行和维护等;
- 在基础设施和流程上都缺乏标准化;
大型测试的结构
录制/重播代理:在谷歌,最流行的方法是使用一个较大型的测试来生成较小型的测试,其方法是:在运行较大型的测试时,将这些外部服务器的流量录制下来,并在运行较小型的测试时重放它。
测试数据类型:
- 种子数据:初始的业务数据,表征了被测系统的初始状态
- 测试流量
- 领域数据:即元数据、配置数据
- 现实的基线
- 播种 API:通过 API 来构造数据
测试数据来源:
- 手工构造的数据
- 复制的数据,通常是从线上进行复制
- 采样数据,即只采用部分的复制数据,也包括“智能采样”:通过覆盖率来筛选最小数据的技术
验证的类型:
- 手动
- 断言
- A/B比较
较大型的测试的类型
- 一个或多个交互二进制文件的功能测试
- 浏览器和设备测试
- 性能、负载和压力测试
- 部署配置测试
- 探索性测试
- A/B差异回归测试
- 用户验收测试
- 探针和生产监控
- 灾难恢复与混沌工程
- 用户评估
大型测试和开发者工作流
必须对较大型测试做出额外的努力,以防止它们在开发人员工作流中产生摩擦。
弃用
为什么弃用
代码是负债,而不是资产。
代码是要消耗维护成本的,这就意味着有必要对是否应该维持原有系统还是弃用之间进行权衡。
但坦诚地说,很难真实地评估软件维护的成本来决定是否弃用,因为维护成本还涉及到难以评估的隐含的软件生态成本,关联和影响成本。
为什么弃用很难
- 系统的用户越多,用户以意外和无法预料的方式使用它的可能性就越大,因而弃用该系统就更难。
- 开发者对旧系统的情感依赖。
- 执行弃用工作的金钱成本、人力成本过高,而带来的利益有时并不易被察觉。
弃用的类型
- 建议性弃用。没有截止日期。
- 强制性弃用。给出截止日期。
弃用流程的管理
当考虑转换成本时,对现有系统进行演进通常比用新系统替换现有系统成本更低。
工具
版本控制与分支管理
什么是版本控制
版本控制系统VCS是一个跟踪文件修订版本的系统。
集中式版本控制系统:开发人员提交的任何代码都将提交到远程的中心存储库中,最初的VCS实现都是集中式的。
分布式版本控制系统DVCS:任何中心的概念都是纯概念性的,你可以克隆任一个现有的存储库,并本地编辑和提交,也可以将本地存储库推送到任一个远程的存储库。而项目的中心存储库实际上是通过政策来指定的,也会声明一个特定的分支为真实来源(即主干)。
分支管理
跟踪和管理版本控制中的不同修订版本,这些都是分支管理的范畴。
单一版本:对于存储库中的每个依赖项,必须只能选择该依赖项的唯一版本。
开发分支数量应该是最小化的,或者最好是非常短暂的。
单一代码仓
单一代码仓最主要的好处是“让遵循单一版本”很简单。
版本控制的未来
越来越多的 VCS 和构建基础设施允许你拥有小的、细粒度的存储库,同时又为整个组织提供一致的“虚拟” head/trunk 概念。
代码搜索
Code Search
Code Search 是谷歌用于浏览和搜索代码的工具,由前端 UI 和各种后端模块组成。
谷歌员工使用 Code Search 的动机和比例为:
- 在哪里:16%
- 做什么:25%
- 如何用:33%
- 为什么:16%
- 谁以及何时:8%
帮助你的开发人员理解代码可以极大地提高工程生产力。
Code Search 作为其他工具的基础,以及作为所有文件和开发者工具都能链接到的一个中心、标准的地方,具有额外的价值。
为什么需要一个单独的 Web 工具
在谷歌之外,大部分代码搜索都是在本地 IDE 内完成的。谷歌为什么要使用单独的 Web 工具呢?因为规模、无需设置即可浏览全局代码、专业化、与其他开发工具集成和开放 API。
规模对设计的影响
- 搜索查询延迟
- 索引延迟
权衡
在谷歌这样规模的代码库中实现 Code Search,并保持它的快速响应,需要做出各种权衡:
- 完整性:代码库的 Head
- 完整性:所有结果 VS 最相关的结果
- 完整性:Head VS 分支 VS 所有历史 VS 工作空间
- 表达性:Token VS 子串 VS 正则表达式
构建工具与构建哲学
构建系统的目的
所有构建系统都有一个直接的目的:它们将工程师编写的源代码转换成可由机器读取的可执行二进制文件。
好的构建系统会优化2个重要属性:快和正确。
随着组织规模的扩大,一个功能齐全的构建系统对于保持开发人员的生产力是必要的。
授权和灵活是有代价的。适当地限制构建系统可以使开发人员更加轻松。
在定义制品和依赖项时,最好以细粒度的模块为目标。细粒度模块能够更好地利用并行和增量构建。
现代构建系统
- 一切都是为了依赖;
- 基于制品而不是基于任务的构建系统,这样具有更好的扩展性和可靠性;
- 分布式构建;
- 时间和规模的权衡;
处理模块和依赖
- 使用细粒度依赖与1:1:1规则(每个目录通常包含一个单独的包、目标和 BUILD 文件);
- 最小化模块可见性;
- 管理内部和外部依赖,外部依赖项应该在源代码控制下显式地进行版本控制,依赖“最新”版本会导致灾难和不确定的构建;
谷歌的代码评审工具
代码评审工具的原则
- 简单;
- 基本的信任;
- 日常的沟通;
- 工作流集成;
代码评审流程
第一步:创建一个变更
核心功能:差异比较、分析结果、紧密的工具集成。
第二步:请求评审
第三步和第四步:理解和评论变更
核心功能:评论、了解变更状态。
第五步:批准变更(评价变更)
第六步:提交变更
核心功能:提交后:跟踪历史。
静态分析
有效静态分析的特点
- 可扩展性;
- 易用性;
让静态分析发挥作用的关键经验
- 关注开发者的体验,建立分析器用户和分析器编写者之间的反馈渠道;
- 让静态分析成为核心开发者工作流的一部分,比如在提交代码评审之前使用静态分析来进行预提交检查;
- 赋予用户贡献的权力;
谷歌的静态分析平台
- 集成了不同类型的静态分析工具;
- 集成反馈渠道;
- 提供建议的自动修复的功能;
- 支持按项目定制;
- 预提交检查;
- 编译器集成;
- 编辑和浏览代码时的分析;
依赖管理
为什么依赖管理这么难
核心问题是:冲突的需求和菱形依赖。
对一个软件工程项目,添加依赖并非是免费的,而且建立“持续的”信任关系的复杂性也具有挑战性。
从理论上讲,依赖管理
四种常见的依赖管理解决方案:
- 没有变化(又名静态依赖模型)
- 语义化版本号(SemVer)
- 绑定分发模式:一个组织收集一组依赖项,并找到相互兼容的一组版本,然后将它们作为一个单元发布。
- Live at Head:在谷歌这样的组织范围内,它成本高但效果好;在行业的应用和推广还不是很清晰。
SemVer 的局限性
SemVer 是当今依赖管理的事实标准,但仍然存在以下局限性:
- 可能过度约束;
- 可能过度承诺;
- 动机:SemVer 并不总是激励维护者创建稳定的代码;
- 最小版本选择:需要更新时选择最小可用版本,这样会产生高保真构建,即用户构建的依赖项尽可能接近作者开发的依赖项;
大规模变更
什么是大规模变更
大规模变更(LSC)是逻辑上相关但实际上不能作为单个原子单元提交的任何一组变更。
LSC 流程使得重新思考某些不可变的技术决策成为可能。
原子变更的障碍
- 技术限制:对于特定的一组基础设施可能根本不可行。
- 合并冲突。
- 没有闹鬼的墓地:一个非常古老、迟钝或复杂的系统/代码库,以至于没有人敢进去。
- 异构性:将多种环境进行简化以增加更多的一致性,这将有助于自动化处理。
- 测试:每一个变更都应该被测试,但是变更越大,实际测试它就越困难。
- 代码评审:每一个变更都需要在提交之前进行评审,而评审大型提交可能是乏味的、繁重的,甚至容易出错的。
LSC 的基础设施
- 政策与文化
- 代码库分析
- 变更管理
- 测试
- 编程语言支持
LSC 流程
- 授权:通过邮件列表发送变更申请,经过委员会批准。
- 变更创建
- 切片管理:将一个大的变更分解为可以原子提交的变更,并分别提交。每个切片都经历:测试、邮件通知评审者、评审、提交的过程。
- 清理:对旧系统或旧符号的处理。
持续集成
CI 的概念
持续集成,即 CI,通常被定义为:一种软件开发实践,其中团队成员经常集成他们的工作。每个集成都通过一个自动化构建(包括测试)进行验证,以尽可能快地检测集成错误。
在当下的规模化开发的情况下,对 CI 的一个更好的定义是:对我们整个复杂而快速演进的生态系统的持续组装和测试。
核心概念:
- 快速反馈循环:可访问和可操作的反馈
- 自动化:持续构建、持续交付
- 持续测试:只有预提交检查是不够的、预提交与提交后、发布候选版本测试、生成测试
CI 的挑战:
- 预提交优化
- 罪魁祸首定位和故障隔离
- 资源约束
通过封闭测试来提升稳定性:
- 封闭测试:测试运行在完全自包含(即没有外部依赖性,如生产后端)的测试环境(即应用服务器和资源)上。
- 封闭测试有2个重要的特性:更好的确定性和隔离性。
- 自从 CI 系统被创建以来,录制/回放已然成为更大系统的流行范例。
持续交付
持续交付和敏捷方法论
持续交付(CD)和敏捷方法论的一个核心原则是,随着时间的推移,小批量的变更会带来更高的质量。
其关键有:
- 敏捷:频繁小批量发布
- 自动化:减少或消除频繁发布的重复开销
- 隔离:争取采用模块化架构,以隔离变更并使故障排除更加容易
- 可靠:度量关键的健康指标,如崩溃或延迟,并不断改进它们。
- 数据驱动决策:对健康指标进行 A/B 测试以确保质量。
- 分阶段发布:在发布给所有人之前,向少数用户发布变更。
速度是一项团队运动:如何将部署分解为可管理的单元
对于一个协作开发代码的大型团队来说,最佳的工作流程需要模块化的架构和持续集成。
隔离评估变更:特性开关
为特性设置特性开关能尽早隔离问题。
力求敏捷:建立发布火车
使用灰度部署来解决设备多样性和用户群的广度问题。
质量与聚焦用户:只发布有用的功能
监控任何功能的成本和价值,以了解它是否仍然有意义,是否提供了足够的用户价值。
左移:更早地做出数据驱动的决策
在所有变更之前实现更快、更数据驱动的决策。
改变团队文化:建立发布规则
尽早且频繁地小批量发布,以降低每次发布的风险,并最大限度地缩短上市时间。
计算即服务
驯服计算环境
其历史的发展:
- 将琐事自动化:简单自动化、自动调度
- 容器化与多租户:规模优化和弹性伸缩
规模化需要一个公共的基础设施来运行生产中的工作负载。
为托管计算编写软件
- 为失效设计架构
- 批处理 VS 服务
- 管理状态
- 通过间接层连接到服务
- 一次性代码
CaaS 随时间和规模的演化
- 抽象容器:容器和隐式依赖
- 一个服务统御余众:多租户服务作业
- 标准化配置语言提供标准配置
选择计算服务
计算解决方案可以为软件提供标准化、稳定的抽象和环境。
软件需要适应分布式、托管计算环境。
计算解决方案的选择:
- 集中化与定制化
- 抽象层次:Serverless
- 公有云 VS 私有云