首页 存档 技术 查看内容

Google的软件工程概述

2018-3-30 13:00 |来自: 互联网 292 0

摘要: 点击上方蓝色字体关注「**程序员」 授权转载自精益领导力,拒绝二次转载 1. 介绍 Google已经是一个非常成功的公司。正如在搜索和竞价广告方面的成功一样,Google也提供了许多其他突出产品,包括Google地图,Google ...

点击上方蓝色字体关注「顶级程序员」

授权转载自精益领导力,拒绝二次转载

1. 介绍


Google已经是一个非常成功的公司。正如在搜索和竞价广告方面的成功一样,Google也提供了许多其他突出产品,包括Google地图,Google新闻,Google翻译,Google语音识别,Chrome和Android。Google还通过购买小公司大大增强和扩展了许多产品,例如YouTube,并对多种多样的开源项目做出了重大贡献。Google也展示了一些尚未投入市场的惊人产品,如自动驾驶汽车。


Google的成功有很多原因,包括开明的领导力,技术牛人,高标准招聘,以及成功带来的经济实力,可以在非常迅速增长的市场早期进行介入。但其中一个原因是谷歌开发出的优秀软件工程实践,这帮助谷歌走向成功。这些实践基于全球最有才华的软件工程师的大量积累和提取的智慧,随着时间的推移而不断演化。我们想跟全球各地的人们分享我们的知识与实践以及我们从中学到的一些教训。


本文的目的是记载并简要介绍Google的关键软件工程实践。其他组织和个人可以进行比较和对比,并考虑是否应用一些做法。


许多作者(例如[9],[10],[11])都有书籍或文章来分析Google的成功历史。但大多数主要涉及商业,管理和文化;只有一小部分(例如[1,2,3,4,5,6,7,13,14,16,21])谈到了软件工程方面的内容,大多数只探讨一个方面;并且没有从整体上提供一个简短的、书面的关于谷歌软件工程实践的概述,本文目的正在于此。


2. 软件开发

2.1 源码存储库

大多数Google代码存储在一个统一的源代码存储库中,Google的所有软件工程师都可以访问。有一些值得注意的例外,特别是两个大型开源项目Chrome和Android,分别使用了独立的开源代码存储库,以及一些高价值或关键的安全代码有更严格的访问限制。但大多数Google项目共享相同的存储库。自2015年1月起,这个86 TB的存储库包含了10亿个文件,包括超过900万个源代码文件,20亿行源代码,具有3500万个版本修改的历史记录和每工作日提交的4万个版本修改的变更率[18]。存储库的写访问是受控的:只有存储库的每个子分支列出的所有者才可以批准修改该分支。但一般来说任何工程师都可以访问任一代码段,可以签出并构建,可以进行本地修改,可以测试它们,并可以发送变更以供代码所有者审核,如果所有者同意,可以签入(提交)这些变更。在文化上,鼓励工程师修复他们看到的任何有问题的知道如何修复的软件,无论项目边界如何。这鼓励了工程师并带来了更高质量的基础设施,更好地满足了使用它的人的需求。


几乎所有的开发都发生在仓库的“头部head”(指git中的head),而不是在分支上。这有助于早期识别集成问题,并最大限度地减少所需的合并工作量。也更容易和更快地推出安全修复程序。


频繁运行测试的自动化系统,通常在每次更改任何文件,进行传递性依赖测试之后进行,虽然这并不总是可行的。这些系统自动通知作者和审阅者测试失败的任何变更,通常都在几分钟之内完成。大多数团队通过安装使构建的当前状态突出显示,甚至用颜色编码来表示(绿色为建立成功和所有测试通过,红色表示有一些测试未通过,黑色表示失败的构建)。这有助于工程师集中精力注意保证构建通过。大多数更大的团队也有一个“构建巡警”负责确保测试的持续通过,通过与作者合作,不期望的变更能快速修复或回滚。(构建巡警角色通常在团队或其经验丰富的成员之间轮流承担。)这种专注于保持构建通过的做法使开发在头部进行具有实用性,即使是规模非常大的团队。


代码所有权。存储库的每个分支都可以有一个列出分支“所有者”用户ID的文件。子目录还从父目录继承所有者,尽管可以有选择地限制。每个分支的所有者控制写访问权限,如下面的代码审查部分所述。每个分支都需要有至少两个所有者,虽然通常需要有更多,特别是在地理分布较远的团队。通常将整个团队列在所有者文件中。变更分支可以由Google公司的任何员工实施,不只是所有者,但必须由所有者批准。这确保每个变更由理解软件的工程师审核


有关Google源代码存储库的更多信息,请参阅[17,18,21];以及另一个大公司如何处理同样的挑战,见[19]。


2.2 构建系统

Google使用一种称为Blaze的分布式构建系统,负责构建和链接软件和运行测试。它提供了在整个存储库用于构建和测试的标准命令。这些标准命令和高度优化的实现意味着对于Google工程师构建和测试存储库中的任何软件变得相当简单和迅速。这种一致性是关键的推动者,这有助于工程师能够跨项目边界进行变更。


程序员编写“BUILD”文件,Blaze用它来确定如何构建他们的软件。构建实体(例如库,程序和测试)使用相当高级别的声明性构建规范,为每个实体指定其名称,源文件和库或其依赖的其他构建实体。这些构建规范包括称为“构建规则”的声明,每个都指定高级概念,如“这是一个C 库,这些源文件依赖这些其他库文件”,这是由构建系统将每个构建规则映射到一组构建步骤,例如编译每个源文件的步骤和链接步骤,以及确定编译器和编译标志。


在某些情况下,特别是Go程序,可以自动生成(和更新)构建文件,因为BUILD文件中的依赖信息是(通常是)源文件中依赖信息的摘要。但是,他们仍然会签入到存储库中。这是确保构建系统可以通过仅分析构建文件来快速确定依赖而不是分析源文件,并且它避免了构建系统与编译器或分析工具之间的过度耦合,支持许多不同的编程语言。


构建系统的实现使用Google的分布式计算基础架构。每个构建工作通常分布在数百或甚至数千个机器上。这使得快速构建大型程序或并行运行数千个测试成为可能。


各个构建步骤必须是“密封的”:仅取决于他们所声明的输入。强调所有依赖正确声明是分发构建的结果:只有声明的输入被发送到构建步骤的机器上运行。结果是构建系统能可靠地知道真正的依赖。甚至是构建系统调用的编译器被视为输入。


独立构建步骤是确定的。因此,构建系统可以缓存构建结果。软件工程师可以将其工作区同步到旧的变更号,可以重建并获得完全相同的二进制代码。此外,可以在不同用户之间安全地共享该缓存。 (为了使这项工作正常,我们必须在构建调用的工具中消除非确定性,,例如通过清除输出文件中的时间戳。)

构建系统是可靠的。构建系统跟踪构建规则自身的变更依赖性,并且如果产生它们的操作发生改变,则知道要重建目标,即使该操作没有输入,例如当只有编译器选项改变时。它也可以正确处理被中断的构建部分,或在构建期间修改源文件:在这种情况下,只需要重新运行build命令。不需要运行相当于“make clean”的命令。


构建结果缓存在“云中”。这包括中间结果。如果另一个构建请求需要相同的结果,构建系统会自动重用它们而不是重建,即使请求来自不同的用户。


增量重建速度快。构建系统驻留在内存中,以便为了重建,它可以递增地分析上次构建以来已变更的文件。


预提交检查。Google提供了在启动时自动运行一系列测试的工具,当启动代码审查和/或准备向存储库提交变更时。每个存储库分支可以包含测试运行的配置文件,以及是否在代码审查时或在提交之前立即运行它们。测试可以是同步的,即在发送变更以供审阅之前和/或在提交变更到存储库之前运行(有利于快速运行测试);或异步结果通过电子邮件发送给审查讨论线程。 [审查线程是代码审查之上的电子邮件线程;该线程中的所有信息也显示在基于Web的代码审查工具中。]


2.3 代码审查

Google已经建立了完善的基于网络的代码审查工具,与电子邮件集成,允许作者提出审查请求,并允许审阅者并排比较差异(同时有漂亮的颜色编码)并进行评论。当提出变更的作者启动代码审查时,通过电子邮件通知审核人,并提供指向该变更的评审工具所在页面的链接。当审核人提交审核评论时,会自动发送通知电子邮件。此外,自动化工具可以发送通知,包含例如自动测试或静态分析工具发现的结果。


对主要源代码存储库的所有变更至少需要由另一个工程师进行审核。此外,如果变更的作者不是文件的所有者,则至少需要一个所有者进行审核并批准该变更。


在特殊情况下,在审查之前,分支所有者可以签入(提交)紧急变更到该分支,但仍然必须指定审查者,并且变更作者和审查者将自动被定期提醒,直到变更被审核批准。在这种情况下,任何回应评审意见的修改必须提交另一个变更,因为最早的变更已经提交。


Google有一些工具可以为特定变更建议审阅者,通过查阅要修改代码的所有权和著作权,最近审阅者的历史记录,以及每个潜在审阅者承担的待审查代码数量。至少一个受变更影响的分支的所有者必须审核并批准该变更,但除此之外,作者可自由选择他们认为合适的审阅者。


代码审查的一个潜在问题是,如果审阅者的响应太慢,或者是不太愿意批准变更,这可能会阻碍开发的进展。事实是代码作者选择审阅者有助于避免这样的问题,允许工程师避开可能对其代码过度干涉的审阅者,或发送简单变更给经验少的审阅者,复杂变更发送给有经验的审阅者或同时发给好几个审阅者共同审阅。


每个项目的代码审查的讨论将自动复制到项目维护者的专属邮件列表中。任何人都可以对任何变更发表评论,无论他们在变更前后是否被指定为该变更的审阅者。如果一个bug被发现,它通常会跟踪导致bug的变更并在初始的代码审查线程中指出错误以便原来的作者和审阅者意识到该错误。


也可以向几个审阅者发送代码审查请求,然后在他们其中之一批准后尽快提交变更(假设作者或第一个答复的审阅者是分支所有者),在其他审阅者还没有评论之前。任何随后的审查意见在后续变更中处理。这可以减少评审的周转时间。


除了仓库的主库部分,还有一个“实验”库的正常代码审查要求不是强制的。然而,在产品中运行的代码必须放在仓库的主库中,工程师被强烈鼓励在存储库的主库中开发代码,而不是在实验库中开发然后迁移到主库,因为代码审查最有效是在代码开发阶段而不是之后。实际上,工程师们经常要求代码评审即使是在实验库中写代码阶段。


鼓励工程师保持独立的小变更,大变更最好分解成一组小的变更,审阅者可以一次轻松地查看。这也使作者更容易在评审期间对提出的主要变更作出响应;非常大变更的作者往往太死板,并且抗拒审阅者建议的变更。鼓励保持小变更的一种方法是代码审查工具将变更规模描述在每个代码审查中,30-99行的添加/删除/移除被标记为“中”变更,大于300行的变更被标记为受蔑视的标签,如“Large大”(300-999),“freakin hug出奇大”(1000-1999),(但是,在通常的Google方式中,在每年的那几天用一些搞笑的方式替换这些熟悉的描述,talk-like-a-pirate day。 :)

1.近年来这种情况发生了一些变化。更新版本的代码审查工具不再使用更多的蔑视标签,但是仍然标记变更的规模,例如, “S”,“M”,“L”,“XL”。


2.4 测试

我们强烈鼓励并广泛使用单元测试。产品中所使用的所有代码期望都有单元测试,代码审查工具将突出显示没有进行相应测试的源代码。代码审查人通常要求任何变更添加新功能时,也应添加相应的测试。Mocking框架(允许构建轻量级单元测试,甚至是依赖重量级库文件的代码)是相当普遍的。


集成测试和回归测试也被广泛应用。


如“预提交检查”中所述,测试可以作为的一部分自动执行的代码审查和提交过程。


Google还提供用于测量测试覆盖率的自动化工具。结果也被整合到源代码浏览器中,作为一个可选层次。


在部署之前进行压力测试也是Google的重点。团队希望产生表格或图形来显示关键指标(特别是延迟和错误率)如何随传入请求的变化而变化。


2.5 缺陷追踪

Google使用一个名为Buganizer的错误跟踪系统来跟踪问题:缺陷,功能请求,客户问题和过程任务(如版本发布或清理工作)。缺陷被分配到有层次的组件上,并且每个组件可以具体到可以抄送的默认责任人和默认的电子邮件列表。当发送源代码变更以供审核时,系统会提示工程师将该变更与特定版本发行号相关联。


Google的团队通常(但不是通用的)定期扫描期组件中的未解决问题,确定优先级,并在适当时候分配给特定的工程师。一些团队有专人负责缺陷分类,其他团队采取在小组常规会议上进行缺陷分类的方法。 Google的许多团队都使用缺陷标签表示缺陷是否已被分类,以及哪个版本将修复缺陷。


2.6 编程语言

Google强烈鼓励软件工程师选用4个官方批准的编程语言之一:C ,Java,Python或Go。减少编程语言的数量降低了代码重用和程序员间的协作障碍。


针对每种语言的Google风格指南,确保遍布公司各处的代码有相似的风格,布局,命名约定等。另外还有一个公司范围内的代码可读性培训流程,由经验丰富的工程师来培训其他工程师编写可读性好,特定语言的常用代码,通过重大变更审查或系列变更审查,直到审阅者认为工程师知道如何用该语言编写出可读性好的代码。一门特定语言上每个重大新代码的变更必须由已经通过“可读性”训练过程的人审批。


除了这四种语言之外,还使用了许多专用领域特定语言(DSL)用于特定目的(例如用于指定构建目标及其依赖性的构建语言)。


这些不同的编程语言之间的交互操作主要使用Protocol Buffer。ProtocolBuffer是一种以高效而可扩展的方式编码的结构化数据的方法。它包括用于指定结构化数据的特定领域语言,带有相应描述的编译器并在C ,Java,Python中生成代码,构建,访问,序列化和反序列化这些对象。 Google的ProtocolBuffer版本与谷歌的RPC库集成,可使用简单的跨语言RPC,具有请求和响应的序列化和反序列化,由RPC框架自动处理。


流程通用性是使开发变得容易的关键,即使使用巨大的代码库和多种语言:有一组单一命令执行所有通用的软件工程任务(如签出,编辑,构建,测试,审查,提交,文件错误报告等等),并且无论什么项目或语言都可以使用相同的命令。开发人员不需要学习一个新的开发过程,只是因为他们正在编辑的代码恰好是另一个项目的一部分或者是用不同的语言编写的相同功能。


2.7 调试和分析工具

Google服务器与库相关联,这些库提供了许多用于调试运行的工具。在服务器崩溃的情况下,信号处理程序将自动将堆栈跟踪转储到日志文件,而且保存核心文件。如果崩溃是由于内存泄漏,服务器将转储活动堆对象的采样子集的分配站点的堆栈跟踪。还有用于调试的Web界面,允许检查传入和传出的RPC(包括定时,错误率,速率限制等),改变命令行标志值(例如增加特定模块的日志级别),资源消耗,概要分析等。这些工具大大增加了调试的整体便利性,传统的调试器难以实现该目的,如gdb。


2.8 发布工程

只有一部分团队有专门的发布工程师,但对于Google的大多数团队来说,发布工程工作由常规软件工程师完成。


大多数软件经常发布;每周或每两周发布一次是常见的,有些团队甚至每天发布。这是通过自动化大部分正常的发布工程任务实现的。经常发布有助于保持工程师的积极性(如果许多个月甚至几年后才发布,很难被认为是激动人心的事),通过允许更多的迭代从而增加整体速度,因而在给定的时间越多的反馈带来越多响应反馈的机会。


发布通常在新的工作区中开始,通过同步到最新的“绿色”构建的变更号(即,所有自动测试通过的最后一个变更),并产生一个发布分支。发布工程师可以选择“最优选择”的额外变更,即从主分支合并到发布分支上。然后从草稿重建软件并执行测试。如果有任何失败的测试,则进行变更以修复缺陷并且让那些额外的变更在发布分支上达到最优,之后再次进行软件重建和测试重运行。当测试全部通过,构建的可执行文件和数据文件被打包。所有这些步骤都是自动的,以便发布工程师只需运行一些简单的命令,甚至只需在菜单驱动的UI上选择一些条目并选择那些最优变更(如果有)。


一旦候选构建被打包,它通常被加载到“预演staging”服务器上做进一步通过少量用户的集成测试(有时只是开发团队)。


一种发送来自生产流量的请求副本(或子集)到预演服务器的有用技术,但也将这些相同的请求发送到当前生产服务器用于实际处理。来自预演服务器的响应被丢弃,并且从实际生产服务器的响应发回给用户。这有助于确保任何可能会导致严重的问题(例如服务器崩溃)在服务器投入生产之前可以被检测到。


下一步通常是推出一个或多个“公测canary”服务器来处理一小部分实时生产流量。与“预演”服务器不同,这些是处理和响应是真实的用户。


最后,该版本可以推广到所有数据中心中的所有服务器。对于超高流量,高可靠性的服务,这种逐步推出在几天的时间内完成,有助于减少任何运行中断的影响,因为新引入的缺陷没有被前面的步骤捕获。


有关Google发布工程的更多信息,请参见SRE手册[7]的第8章。也可以参见[15]。


声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部