查看原文
其他

谷歌软件工程:文化、实践与工具

Test Ninja 软件质量报道 2022-06-03

摘要

  • 我们努力不说:"谷歌的软件工程”是唯一真正的方法,因为我们认识到,我们的规模和资源与其他组织完全不同。
  • 软件工程不是(单纯的)编程,而是长期的编程。我们主要关注的是如何让事情随着时间的推移持续发展,以及如何与其他人协调和合作。书中的代码很少,但其主要的内容依旧是关于软件的讨论。
  • 谷歌在技术上和组织上都是不完美的。你可以从我们对外部软件包的维护等技术问题的处理中看到,也可以从我们围绕公平和多样性的文化问题中看到。
  • 代码审查主要不是为了发现缺陷,它作为一种交流活动更有用。它提供了一个很好的教育机会,并建立了一个共同的理解。
  • 在技术领域,选择往往是效率的敌人。我们的版本控制和依赖性管理政策主要是通过取消工程师的选择来发挥作用。如果你没有选择,你就不会有问题。

Winters

Manshreck

Wright

下面是采访《谷歌软件工程》三位作者的内容。

1. 你如何定义软件工程? 
Winters:我认为一个最古老的定义仍然是最好的定义之一。软件工程是 "多人开发多版本程序",这句话(来自20世纪70年代)仍然抓住了时间(多版本)和规模(尤其是过程和通信的扩展)等关键信息。写一个程序来解决你的问题是一回事。与一个10人的团队合作解决业务问题,并且在团队中的一些人都去干新的事情之后,你还能在十年内继续工作,这完全是另一回事。
 
2. 我们如何才能建立一个伟大的软件团队?
Manshreck:开发好的软件所需要的技能与(在某一时期)大规模生产汽车等所需要的技能是不同的。我们需要工程师做出创造性的反应,并不断学习,而不是重复做一件事。如果没有创造性的自由,他们将无法随着行业的快速变化而发展。为了培养这种创造力,我们必须允许人们成为真正的人(而不是工具),并培养一种信任、谦逊和尊重的团队氛围。信任是为了做正确的事情;谦逊会让你认识到你不能独自完成,可能会犯错误;尊重团队成员,而不是依赖几个人。你根本不可能用几个按自己的规则行事的 "摇滚明星 "工程师来组成一个好的软件团队。这在短期内可能有效,但从长远来看,它将会失败。你需要让人们不断地发展并为组织做出贡献,而且他们需要成为团队的一部分。
 
3. 心理安全对学习至关重要。 
Winters:根据我的经验,最关键的一点是领导者要承认自己的错误。将犯错正常化,让人们摆脱完美是可预期的(或可实现的)错误想法。一旦我们不再将错误视为失败,而是将其视为一个学习的机会,你的团队就会加快很多。而且,反其道而行之,当你明确表示犯新的错误是可以的时候,从长远来看,你就会减少犯错误。对我的团队来说,这的确是个好例子。
 
4. 谷歌是如何在其组织中建立多元文化能力的? 
Winters:这在整个组织中都是不同的,因为有谷歌范围内的建立多元文化能力的举措,也有在团队层面上的举措。可以通过研究某些被忽视的偏见如何在软件工程中表现出来,并最终对我们的用户产生负面影响,来验证多元文化主义和拥抱多样性的重要性。我们认为,与一个多元化的团队合作,对于确保满足更多元化的用户群的需求至关重要。我们在历史上看到:第一代安全气囊对于那些没有参与其设计的其他工程团队来说是非常危险的,例如,碰撞测试假人是依据普通人设计的,结果对妇女和儿童来说是很糟糕的。换句话说,我们不只是在努力为每个人构建软件,我们还在努力与每个人一起构建软件
 
要在一个组织中真正建立起多元文化的能力,需要大量的机构和盟友支持、需要培训和本地的力量。即使有了这些东西,我们也在问自己,作为一个公司和工程师,我们如何能够继续做得更多、做得更好。 但是, "通往公平的道路是漫长而复杂的",我们仍有大量的工作要做,以缩小我们所处的位置和想要达到的位置之间的差距,但我们正在改进。不过,我们改进得越多,对我们的用户和工作就越好。
 
5. 一个典型的代码审查有哪些步骤?
Manshreck:在Google,代码评审需要两种批准:一种是来自工程师(任何工程师)的LGTM(Looks good to me,即代码是正确的);另一种是来自代码库目录的拥有者的批准,即它属于这个目录,并且他们愿意维护它(很重要!)。但仅此而已(而且通常这两种类型的批准被包在一个人身上)。谷歌的代码评审过程也有点轻,但很频繁。我们鼓励较小的修改,让评审者能够快速评审,这样可以保持开发者的速度。
 
6. 从做代码评审中得到了什么好处?
Winters:几年前在ICSE上发表的论文:Google的案例研究 指出了代码评审在实践中真正的价值。很多组织似乎认为,代码评审的主要作用是发现错误。很多人(尤其是那些在技术领域代表性不足的群体)也发现,代码评审被用作一种把关机制,这当然也是一个存在偏见的、有风险的地方。也就是说,根据我们的经验,代码评审主要用于教育,确保事情符合我们的标准并遵循最佳实践,以及确保 "团队 "理解并愿意拥有和维护这些代码。正确性和性能也在清单上,但在软件工作流程中还有其他步骤,可能在这些问题上做得更好。所以在我看来,主要是沟通方面的好处:别人能不能理解这个,作者和评审员能从有关的代码中学到什么,等等。
 
Manshreck:正如Winters所提到的,我想强调的一个见解是,代码评审不是一个纯粹的技术互动,而是一种社会互动。我们实施代码评审的原因主要不是为了捕捉错误(尽管这仍然很重要),而是为了知识共享和团队建设。在谷歌,代码评审在某些方面(理想情况下)是一个低水平的乒乓游戏,每次互动都是一个学习的机会。这也是我们学习新的做事方法的重要途径。最后,代码评审是一种强制执行的手段,即我们工作的代码是一种团队努力的概念。这不是 "我的 "代码或 "你的 "代码,而是 "我们的 "代码。
 
Wright:代码评审实际上是一个学习的机会。 在一个心理安全的团队中,成员可以利用代码评审过程来提出对他们必须共同维护的代码的担忧。 代码评审可以用来帮助指导审查者和作者,但它只是整体审查过程(设计审查、API审查等)的一部分。 有时,代码评审的重点是 "如何 "的问题。"这段代码在做它声称要做的事情吗?",但 "为什么 "的问题也同样重要:"我们为什么要写这段代码?"  为什么 "是避免错误设计和技术债务出现在代码库中的一个重要部分。
 
7. 谷歌的审查工具Critique提供了哪些功能?
 
Manshreck:Critique是为了反映谷歌的流程而定制的,所以它非常善于巧妙地执行对代码审查应该包含的内容的想法。Critique提供的真正功能是在引擎底层,它的界面相对精简。由于Critique是所有工程师用来发送代码评审和提交代码的一个界面,但是,我们可以把各种工具都挂到这个共同的过程中。例如,Critique是让测试运行(通常是自动的)或对相关代码进行静态分析的地方。
 
8. 是什么让静态分析工具在谷歌工作得很好?
Wright:有几个主要的部分可以使其顺利工作。 首先,我们要在开发工作流程中的正确时间将静态分析的结果呈现出来。 在已经提交到版本库的代码上运行静态分析工具可能是有用的,但是当开发者已经在编写代码时,他们更有可能应用静态分析所标出的建议。 使用像Tricorder这样的系统将静态分析整合到代码评审流水线中,这样工程师就能在其最需要的时候(如代码评审时)看到这些建议。
 
其次,借助Tricorder非常容易建立静态分析,并将其与整个平台整合。 构建工具的编译器和语言专家不必担心在代码库中可扩展地运行它们:他们可以专注于自己最熟悉的事情。 提供一个强大的、可扩展的平台可以让这些专家写出最好的工具。
 
9. 静态代码分析会带来哪些好处?
Wright:静态分析有助于将各种错误类别从运行时 "左移 "到代码编译或评审时。 没有人愿意在半夜因为一个编译时的错误或警告而被呼唤。 越早发现错误就越容易修复,静态分析可以帮助在开发生命周期中更早地发现某些类别的错误。
 
几年前,我们引入了编译时注释,这将有助于检测C++代码中的线程违规。 例如,工程师可以将一个变量标记为需要在访问过程中保持一个特定的锁,编译器会做一些静态分析来确定是否真的保持了锁。 这种技术并不能抓住所有的错误(大多数工程师比编译器更聪明),但却是预防错误的一个额外工具。
 
一个团队在生产中遇到了间歇性的崩溃,经过大量的工作,将问题缩小到一个竞争条件(race condition),但无法可靠地重现它。 另外,他们决定在代码库中使用这些新的注释,当某些代码不再被编译时,他们就惊奇地发现问题所在。 在修复了静态分析发现的缺陷后,生产崩溃就自然而然地消失了:静态分析已经把故障从生产时间转移到了编译时间,使得发现和修复缺陷更加容易。有很多类型的错误和缺陷是计算机能够更好地识别的。在开发的早期(编译或代码审查时)识别这些缺陷,而不是让它一直逃逸到生产中去,这样做更彻底、代价更低。
 
10. Google使用的是什么分支策略,这种策略的利弊是什么?
Winters:这里几乎没有分支,至少没有开发分支。也就是说,存在的分支是发布分支,而且它们往往是短暂的,不会被合并到主干中。这一点变得非常重要:没有人可以选择依赖哪个版本或选择在哪里提交其修改。没有人讨论哪个开发分支会被合并。相反,在一个有数万名工程师的共享代码库中,我们保持主干的工作相当可靠,只有一个版本是重要的。这给基础设施团队带来了更多的工作,因为他们没有选择发布一个突破性的变化。相反,他们必须逐步做出改变,并经常直接更新他们的用户,为改变做准备。这是一个不同的模式,但它没有那么混乱。
 
11. 依赖性管理的挑战是什么,是什么让它如此艰难?
Winters:我们将 "依赖性管理 "定义为 "对我们无法控制的库、包和依赖性网络的管理"。大多数时候,这是在处理外部库、开放源码软件之类的事情时所遇到的。但有些组织是如此松散的耦合,其内部开发被分割成许多不相干的存储库,他们将固有地遇到同样的(非常困难的)依赖性管理问题。依赖关系管理的最重要指导是宁愿选择版本控制问题而不是依赖关系管理问题(这也是我们喜欢monorepo概念的部分原因)。
 
大部分的困难来自于你不能控制外部依赖的事实。你不能假设这些依赖关系的提供者有你同样的资源或优先级。许多今天发生的事情可以在未来停止,因为不兼容的版本、暂时依赖性和不可满足的依赖性。业界似乎已经将语义版本管理(semver)作为识别兼容性的一种方式,但是semver(充其量)是一种高度压缩的人类预估,即任何给定的变化可能会有多大的兼容性。在简单的情况下,它是可行的,但较大的依赖关系网络既容易出错,又受到过度约束,这是一个拖累。在实践中,我们希望这个行业更多地使用持续集成和正确性证据(即:运行测试)。但是大多数开发者没有谷歌那样的资源,也没有足够的产品生命周期使这些问题变得如此关键。当你无法看到依赖性的差距时,试图协调软件的顺利发展,这是一个非常困难的问题。这就是谷歌为什么经常建立自己的东西。
 
12. 你在谷歌是如何管理依赖关系的?
Winters:尽量减少对外部的依赖:大部分的代码都是内部编写的。对于需要从开源世界或授权合作伙伴那里获取的部分,所有这些代码都以与我们自己的代码相同的方式存在于monorepo中,但在一个独立的子目录(third_party)中,以确定可能存在所有权或授权这样的问题。我们在管理这些代码方面的大部分困难是找到必要的激励措施,以保持版本数量的约束和签入版本的更新。当有一些流行的开源项目没有承诺任何版本之间的兼容性时,这就特别具有挑战性。当它被一万个项目使用时,试图大规模地更新这样的东西并不有趣。

参考:

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存