| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 游戏开发 -> Software Engineering at Google翻译-III-8-Style Guides and Rules(风格指南和规则 ) -> 正文阅读 |
|
[游戏开发]Software Engineering at Google翻译-III-8-Style Guides and Rules(风格指南和规则 ) |
第8章
风格指南和规则
作者: Shaindel Schwartz 编辑: Tom Manshreck 参考:https://github.com/daizhenhong/swe-at-google/tree/main/Part_III_Processes/Style_Guides_and_Rules 欢迎大家来github上一起贡献! 大多数工程组织都有规则来管理他们的代码库——关于源文件的存储位置规则,关于代码格式的规则,关于命名、模式、异常和线程的规则。大多数软件工程师都在一组策略的范围内工作,这些策略控制着他们如何操作。在谷歌,为了管理我们的代码库,我们维护了一组定义规则的样式指南。 规则是法律。它们不只是建议或建议,而是严格的、强制性的法律。因此,它们是普遍可执行的——除非是在需要使用的基础上批准,否则规则不会被忽视。与规则相反,指导提供了帮助、建议和最佳实践。这些部分是很好的遵循,甚至是非常可取的遵循,但与规则不同的是,它们通常有一些变化空间。 我们收集我们定义的规则,编写代码时必须遵守的规则。接下来,在我们的编程风格指南中,它被视为经典。这里的“风格”可能有点用词不当,它暗示的是一个仅限于格式化实践的集合。我们的风格指南不止于此;它们是管理我们代码的一整套约定。这并不是说我们的风格指南是严格规定的;风格指导规则可能需要判断,比如使用“尽可能合理的描述性”名称的规则。相反,我们的风格指南是我们的工程师负责的规则的最终来源。 在我们的编程风格指南中,我们收集了我们定义的规则,编写代码时必须遵循的做和不做的规则,这些规则被视为标准。这里的“风格”可能有点用词不当,它暗示的是一个仅限于格式化实践的集合。我们的风格指南不止于此;它们是管理我们代码的一整套约定。这并不是说我们的风格指南是严格规定的;风格指导规则可能需要判断,比如使用“尽可能合理的描述性”名称的规则。相反,我们的风格指南是我们的工程师负责的规则的最终来源。 我们为google使用的每一种编程语言都保留了独立的风格指南。1在高层次上,所有的指南都有相似的目标,旨在以可持续性的眼光引导代码开发。同时,它们在范围、长度和内容上也存在着很大的差异。编程语言有不同的优势、不同的特性、不同的优先级,在谷歌不断发展的代码存储库中采用的历史路径也不同。因此,独立地定制每种语言的指导方针要实用得多。我们的一些风格指南是简洁的,专注于一些主要的原则,如命名和格式,在我们的Dart, R和Shell指南中演示了。其他风格指南包括更多的细节,深入研究特定的语言特性,并延伸到更长的文档—特别是我们的c++、Python和Java指南。一些风格指南对典型的非google语言的使用给予了重视——我们的Go风格指南非常简短,只在一个摘要指令中添加了一些规则,以遵守外部认可的惯例中概述的实践。其他包括从根本上不同于外部规范的规则;我们的c++规则不允许使用异常,这是谷歌代码之外广泛使用的语言特性。 即使是我们自己的风格指南也存在很大的差异,这使得我们很难精确地描述一个风格指南应该涵盖什么内容。指导谷歌风格指南开发的决策源于保持代码库可持续性的需要。其他组织的代码库天生对可持续性有不同的要求,这就需要一套不同的定制规则。本章讨论了指导我们规则和指南开发的原则和过程,主要从谷歌的c++、Python和Java风格指南中抽取示例。 为什么需要规则那么我们为什么要有规则呢?制定规则的目的是鼓励“好的”行为,阻止“坏的”行为。对“好”和“坏”的解释因组织而异,这取决于组织关心的是什么。这样的设计不是普遍的偏好;好与坏是主观的,是根据需要而定的。对于一些组织,“good”可能会促进支持小内存占用或优先考虑潜在运行时优化的使用模式。在其他组织中,“好”可能促进使用新语言特性的选择。有时,组织非常关心一致性,因此与现有模式不一致的任何东西都是“不好的”。我们必须首先认识到一个给定的组织的价值;我们使用规则和指导来鼓励和阻止相应的行为。 随着组织的发展,已建立的规则和指导方针形成了通用的编码词汇表。通用词汇表可以让工程师专注于他们的代码需要表达什么,而不是如何表达。通过塑造这种词汇,工程师会倾向于默认地、甚至是潜意识地去做“好的”事情。因此,规则为我们提供了广泛的杠杆作用,以便将共同的开发模式推向所需的方向。 定义规则当定义一组规则时,关键问题不是“我们应该有什么规则?”我们要问的问题是:“我们想要实现的目标是什么?”当我们关注规则将服务的目标时,识别哪些规则支持这个目标,可以更容易地提取有用的规则集。在谷歌,风格指南作为编码实践的法律,我们不会问,“风格指南中包含什么?”而是“为什么要把一些东西放进风格指南?”我们的组织通过制定一套规范代码编写的规则获得了什么? 指导原则让我们把事情放在背景中:谷歌的工程组织由3万多名工程师组成。工程师群体在技能和背景方面表现出巨大的差异。每天大约有6万份文件提交给超过20亿行代码的代码库,这些代码库可能会存在几十年。我们正在优化一套不同于大多数其他组织所需要的价值,但在某种程度上,这些关注是无处不在的——我们需要维持一个对规模和时间都有弹性的工程环境。 在这种情况下,我们规则的目标是管理开发环境的复杂性,保持代码库的可管理性,同时仍然允许工程师高效地工作。我们在这里做了取舍:帮助我们实现这一目标的大量规则确实意味着我们在限制选择。我们失去了一些灵活性,甚至可能会冒犯一些人,但权威标准带来的一致性和减少冲突的收益会胜出。 鉴于这一观点,我们认识到一些指导我们制定规则的首要原则,这些原则必须:
规则必须发挥作用并不是所有的东西都应该放在风格指南中。要求组织中的所有工程师学习和适应任何新规则的成本是非零的。有了太多的规则,2不仅会让工程师在编写代码时更难记住所有相关的规则,而且也会让新工程师更难学会他们的方法。更多的规则也会使维护规则集更具挑战性和更昂贵。 为代码阅读者优化我们规则的另一个原则是优化代码的读者而不是作者。随着时间的推移,我们的代码被阅读的频率将远远高于编写的频率。我们宁愿代码是乏味的键入,而不是难以阅读。在我们的Python风格指南中,当讨论条件表达式时,我们认识到它们比if语句短,因此对代码作者来说更方便。但是,由于它们往往比更冗长的if语句更难让读者理解,所以我们限制了它们的使用。我们认为“读起来简单”比“写起来简单”更重要。我们在这里做了一个权衡:当工程师必须重复地为变量和类型输入可能更长的描述性名称时,前期的成本会更高。我们选择支付这笔费用,是因为它为所有未来的读者提供了可读性。 作为优先级划分的一部分,我们还要求工程师在他们的代码中留下明确的行为证据。我们希望读者在阅读代码时能够清楚地理解代码在做什么。例如,我们的Java、JavaScript和c++风格指南要求在方法重写超类方法时使用override注释或关键字。就算没有明确的设计证据,读者页很可能会明白这一意图,尽管这需要对每个阅读代码的读者进行更多的挖掘。 当它可能令人惊讶时,有意行为的证据变得更加重要。在c++中,有时仅通过阅读一段代码片段来跟踪指针的所有权是困难的。如果一个指针被传递给一个函数,在不熟悉该函数的行为的情况下,我们不能确定将会发生什么。调用者仍然拥有指针吗?这个函数拥有所有权了吗?我可以在函数返回后继续使用指针吗?或者它可能已经被删除了?为了避免这个问题,我们的c++风格指南更倾向于使用std::unique_ptr来实现所有权转移。Unique_ptr是一个管理指针所有权的构造,确保指针只有一个副本存在。当函数接受一个unique_ptr作为参数,并打算获得指针的所有权时,调用者必须显式地调用move语义:
和以下代码进行比较:
鉴于风格指南规则,我们保证所有呼叫站点将包括明确的所有权转移证据,无论何时适用。有了这个信号,代码的读者就不需要理解每个函数调用的行为了。我们在API中提供了足够的信息来推断它的交互。这种清晰的调用站点行为文档确保了代码片段的可读性和可理解性。我们的目标是进行局部推理,目标是清楚地了解在调用点发生了什么,而不需要查找和引用其他代码,包括函数的实现。 大多数涉及注释的风格指南规则也被设计成支持为读者提供就地证据的目标。文档注释(预先挂在给定文件、类或函数上的块注释)描述了后面代码的设计或意图。实现注释(注释穿插在代码本身中)说明或突出不明显的选择,解释棘手的部分,并强调代码的重要部分。我们有涵盖这两种类型注释的风格指导规则,要求工程师提供其他工程师在阅读代码时可能正在寻找的解释。 保持一致我们对代码库一致性的看法类似于我们应用于谷歌办公室的哲学。由于工程人员数量庞大、分布广泛,团队经常被分散到不同的办公室,谷歌员工经常会发现自己要到其他站点去工作。尽管每个办公室都保留了自己独特的个性,融入了当地的风味和风格,但为了完成工作,所有的东西都被刻意保持不变。访问谷歌员工的徽章将与所有当地的徽章阅读器合作;任何谷歌设备都可以使用WiFi;任何一间会议室的视频会议设置都将具有相同的界面。谷歌员工不需要花时间去学习如何设置这些;他们知道,无论他们在哪里,他们的梦想都是一样的。在不同的办公室之间转换工作很容易,而且还能完成工作。 这就是我们在源代码中所追求的。一致性使任何工程师都能快速地进入代码库中不熟悉的部分并开始工作。一个本地项目可以有它独特的个性,但是它的工具是一样的,它的技术是一样的,它的库是一样的,而且都是Just Works。 一致性的优点尽管不允许办公室定制徽章阅读器或视频会议界面可能会让人觉得受到限制,但一致性带来的好处远远大于我们失去的创作自由。代码也是如此:一致性有时可能会让人觉得受到限制,但它意味着更多的工程师用更少的努力完成更多的工作3:
在规模上 几年前,我们的c++风格指南承诺,几乎不会改变会使旧代码不一致的风格指南规则:“在某些情况下,改变某些风格规则可能有很好的理由,但我们仍然保持事物的原样,以保持一致性。” 当代码库变得更大、更老时,这就不再是需要优先考虑的事情了。这是(至少对于我们c++风格指南背后的仲裁人来说)一个有意识的改变:当改变这一点时,我们明确地声明c++代码库将不再是完全一致的,我们甚至也不打算这样做。 设置标准 当我们提倡一致性时,我们倾向于关注内部一致性。有时,当地的惯例在全球惯例被采用之前就已经出现了,因此调整一切以适应全球惯例是不合理的。在这种情况下,我们提倡一致性的层次结构:“保持一致”从本地开始,在这里,给定文件中的规范先于给定团队的规范,后者先于更大项目的规范,后者先于整个代码库的规范。事实上,风格指南包含了许多明确遵守当地惯例的规则6,重视当地的一致性而不是科学技术的选择。
间隔数量 谷歌的Python风格指南最初要求我们所有的Python代码都采用双空格缩进。外部Python社区使用的标准Python风格指南使用四空格缩进。我们早期的大部分Python开发都是直接支持我们的c++项目,而不是实际的Python应用程序。因此,我们选择使用双空格缩进,以与我们的c++代码保持一致,c++代码已经以这种方式格式化了。随着时间的推移,我们发现这种理论并不成立。编写Python代码的工程师读和写其他Python代码的频率要比读和写c++代码的频率高得多。每次我们的工程师需要查找或引用外部代码片段时,我们都要花费额外的精力。每次我们试图将代码片段输出到开源时,我们都经历了很多痛苦,花了很多时间来调和内部代码和我们想要加入的外部世界之间的差异。 避免容易出错和令人惊讶的结构(Avoid error-prone and surprising constructs)我们的风格指南限制了我们使用的语言中一些更令人惊讶、不寻常或棘手的结构的使用。复杂的特征往往有一些不易察觉的缺陷,乍一看并不明显。在没有彻底了解其复杂性的情况下使用这些特性,很容易误用它们并引入错误。即使项目工程师很好地理解了一个结构,未来的项目成员和维护者也不能保证有相同的理解。
现在,看下面这个例子,一切看起来都很好,但细看一下:
other_file.py:
当查看代码时,您如何知道字段foo, bar和baz在这里被访问?没有给读者留下明确的证据。您不容易看到,因此也不容易验证哪些字符串用于访问对象的属性。如果不是从A_CONSTANT读取这些值,而是从远程过程调用(Remote Procedure Call, RPC)请求消息或数据存储读取这些值,那会怎么样呢?这种模糊化的代码可能会导致重大的安全缺陷,如果只是通过简单地验证返回值是否错误,这种缺陷很难被发现。测试和验证这样的代码也很困难。 Python的动态特性允许这样的行为,并且在非常有限的情况下,使用 让步实用性 (Concede to practicalities)用拉尔夫·沃尔多·爱默生的话说:“愚蠢的一致性是小心灵的妖怪。”在我们追求一致的、简化的代码库时,我们不能盲目地忽略其他所有东西。我们知道样式指南中的一些规则会遇到保证例外的情况,这是可以的。在必要的时候,我们允许对可能与我们的规则相冲突的优化和实际问题做出让步。 样式指南(The Style Guide)那么,语言风格指南应该包含哪些内容呢?所有的风格指南规则大致分为三类:
避免危险的规则首先,我们的风格指南包括关于语言特性的规则,这些规则出于技术原因确定了什么是必须做的和什么是必须不能做的。我们有关于如何使用静态成员和变量的规则;关于使用lambda表达式的规则;处理异常的规则;关于构建线程、访问控制和类继承的规则。我们将介绍要使用哪些语言特性以及要避免哪些结构。我们指出可能使用的标准词汇表类型以及用于什么目的。我们特别包括了关于难以使用和难以正确使用的规则——一些语言特性具有微妙的使用模式,这些模式可能不直观或不容易正确应用,从而导致微妙的bug。对于指南中的每一项裁决,我们的目标是在解释所达成的决定时,包括权衡过的利弊。这些决策大多基于对时间的弹性需求,支持和鼓励可维护的语言使用。 执行最佳实践我们的风格指南还包括一些规则,强制执行一些编写源代码的最佳实践。这些规则有助于保持代码库的健康和可维护性。例如,我们指定代码作者必须在哪里以及如何包含注释我们的注释规则涵盖了注释的一般约定10,并扩展到包括必须包含代码内文档的特定情况——在这些情况下,意图并不总是明显,例如switch语句中的fall-through、空的异常捕获块和模板元编程。我们也有规则来详细描述源文件的结构,概述预期内容的组织。我们有命名规则:包的命名,类的命名,函数的命名,变量的命名。所有这些规则都是为了指导工程师实践更健康、更可持续的代码。 我们的风格指南中实施的一些最佳实践旨在使源代码更具可读性。许多格式规则都属于这一类。我们的样式指南指定了何时以及如何使用垂直和水平空格,以提高可读性。它们还包括行长度限制和支撑对齐。对于某些语言,我们通过使用自动格式化工具来满足格式化需求——gofmt用于Go, dartfmt用于Dart。逐项列出格式化需求的详细列表,或者为必须应用的工具命名,目标是相同的:我们有一组一致的格式化规则,旨在提高应用于所有代码的可读性。 我们的风格指南还包括对新的和尚未被很好理解的语言特性的限制。我们的目标是在学习过程中,在一个功能的潜在缺陷周围预先安装安全围栏。与此同时,在每个人开始跑步之前,限制使用让我们有机会观察从我们观察的例子中开发和提取最佳实践的使用模式。对于这些新特性,在开始的时候,我们有时不确定应该给予适当的指导。随着选项的传播,希望以不同方式使用新特性的工程师与风格指南的所有者讨论他们的例子,要求允许在最初限制范围之外的其他用例。通过观察出现的放弃请求,我们了解了该特性是如何被使用的,并最终收集了足够多的例子来总结好的实践。在我们得到这些信息之后,我们可以回到限制性裁决,并修改它以允许更广泛的使用。 案例分析: 介绍 std::unique_ptr 当c++ 11引入std::unique_ptr(一种智能指针类型,表示动态分配对象的独占所有权,并在unique_ptr超出作用域时删除对象)时,我们的样式指南最初不允许使用该指针。对于大多数工程师来说,unique_ptr的行为是不熟悉的,而该语言引入的相关的move语义是非常新的,对大多数工程师来说,非常令人困惑。防止在代码库中引入std::unique_ptr似乎是更安全的选择。我们更新了工具来捕获对不允许类型的引用,并保留了现有的指导意见,建议使用其他类型的现有智能指针。 随着时间流逝,工程师有机会调整move语义的含义,我们也越来越相信使用std::unique_ptr直接符合我们的风格指南的目标。在函数调用站点上,std::unique_ptr所提供的关于对象所有权的信息使读者更容易理解该代码。引入这种新类型所增加的复杂性,以及随之而来的新的move语义,仍然是一个值得关注的问题,但是代码库长期整体状态的显著改进使得采用std::unique_ptr是一个值得的权衡。确保一致性我们的风格指南还包含了一些规则,涵盖了许多较小的内容。对于这些规则,我们做出并记录一个决定主要是为了做出并记录一个决定。这类规则中的许多规则都没有重大的技术影响。像命名约定、缩进间隔、导入顺序这样的东西:通常一种形式比另一种形式没有明确的、可衡量的技术优势,这可能是技术社区一直在争论它们的原因11。选择一个,我们就退出了无休止的辩论循环,可以继续前进了。我们的工程师不再花时间讨论两个空间和四个空间的对比。这类规则的重要部分不是我们为给定的规则选择了什么,而是我们选择的事实。 其他除此之外,还有很多内容不在我们的风格指南中。我们试着专注于那些对我们代码库的健康状况有最大影响的事情。这些文档中绝对有一些没有详细说明的最佳实践,包括许多很好的工程建议:不要太灵活、不要fork代码库、不要重复造轮子等等。像我们的风格指南这样的文档并不能让一个完全的新手对软件工程有全面的理解——我们有一些假设,这是有意的。 变更Rules(Changing the Rules)我们的风格指南不是静态的。与大多数事情一样,随着时间的推移,风格指导决策的环境和指导给定规则的因素很可能会改变。有时,情况的变化足以使人们需要重新评估。如果发布了新的语言版本,我们可能需要更新规则以允许或排除新的特性和习惯用法。如果一条规则导致工程师努力绕过它,我们可能需要重新检查该规则应该提供的好处。如果我们用来执行规则的工具变得过于复杂和难于维护,那么规则本身可能已经衰败,需要重新审视。注意规则何时准备好进行另一个检查是保持规则集相关和最新的过程的重要部分。 在我们的风格指南中,规则背后的决定是有证据支持的。在添加规则时,我们将花时间讨论和分析相关的利弊以及潜在的后果,并试图验证给定的更改是否适合谷歌运行的规模。谷歌风格指南中的大多数条目都包含了这些考虑,列出了在过程中权衡的利弊,并给出了最终裁决的理由。理想情况下,我们优先考虑这种详细的推理,并将其包含在每条规则中。 记录给定决策背后的原因,使我们能够在需要改变的时候识别出事情。随着时间的推移和环境的变化,以前做出的一个好的决定可能不是现在最好的决定。清楚地指出影响因素后,我们就能够确定何时与一个或多个因素相关的更改需要重新评估规则。 案例分析: CamelCase命名 在谷歌,当我们为Python代码定义初始风格指导时,我们选择使用CamelCase命名风格,而不是使用snake_case命名风格来命名方法名。尽管公共Python风格指南(PEP 8)和大多数Python社区使用了snake_case命名,但当时谷歌的大多数Python用法是为c++开发人员使用Python作为c++代码库之上的脚本层。许多已定义的Python类型都是相应c++类型的包装器,由于谷歌的c++命名约定遵循CamelCase风格,因此跨语言一致性被视为关键。 后来,我们开始构建并支持独立的Python应用程序。最经常使用Python的工程师是开发Python项目的Python工程师,而不是编写快速脚本的c++工程师。我们给我们的Python工程师造成了一定程度的笨拙和可读性问题,要求他们为我们的内部代码维护一个标准,但在每次引用外部代码时不断调整另一个标准。我们还让有Python经验的新员工更难以适应我们的代码库规范。 随着Python项目的发展,我们的代码与外部Python项目的交互越来越频繁。我们在一些项目中合并了第三方Python库,导致我们的代码库中混合了我们自己的CamelCase格式和外部偏爱的snake_case样式。当我们开始开源我们的一些Python项目时,在一个我们的惯例不墨守常规的外部世界中维护它们,既增加了我们的复杂性,也增加了社区对我们风格的警惕,他们觉得我们的风格令人惊讶,有些怪异。 提出这些论点后,讨论了成本(与其他谷歌代码失去一致性,谷歌人习惯了我们的Python风格)和好处(获得与大多数其他Python代码的一致性,允许已经泄漏到第三方库),Python风格指南的风格仲裁者决定改变规则。有了它被应用为文件范围内的选择的限制,现有代码的豁免,以及项目决定什么是最适合他们的自由,谷歌Python风格指南被更新为允许snake_case命名。过程(The Process)考虑到我们所追求的长生命周期和扩展能力,我们认识到事情需要改变,因此我们创建了一个更新规则的过程。改变我们的风格指南的过程是基于解决方案的。风格指南更新的建议是用这个视图来框定的,识别现有的问题,并将建议的更改作为修复问题的一种方法。在这个过程中,“问题”并不是可能出错的假设例子;问题是通过在现有谷歌代码中发现的模式来证明的。给定一个被证明的问题,因为我们已经在现有的风格指南决策背后有了详细的理由,我们可以重新评估,检查一个不同的结论现在是否更有意义。 编写由风格指南管理的代码的工程师社区通常最容易注意到何时需要更改规则。事实上,在谷歌,我们的风格指南的大多数改变都是从社区讨论开始的。任何工程师都可以提出问题或提出更改建议,通常从专门讨论风格指南的特定语言邮件列表开始。 关于风格指南更改的建议可能是完整的,包括建议的具体的、更新的措辞,或者可能以关于给定规则的适用性的模糊问题开始。社区会讨论新想法,并从其他语言用户那里获得反馈。一些提案被社区一致否决,被认为是不必要的、过于模糊的或无益的。另一些则收到了积极的反馈,被认为是有价值的,或者有一些建议的改进。这些通过社区评审的提案将受到最终决策批准的制约。 Style决策者在谷歌,对于每种语言的风格指南,最终的决定和批准都是由风格指南的所有者——我们的风格仲裁者——做出的。对于每一种编程语言,一组长期的语言专家是风格指南的所有者和指定的决策者。特定语言的风格仲裁人通常是该语言库团队的高级成员,以及其他具有相关语言经验的长期谷歌员工。 例外 (Exceptions)没错,我们的规则就是法律,但也有例外。我们的规则通常是为更大的一般情况而设计的。有时,特定的情况会受益于对特定规则的豁免。当出现这种情况时,会咨询风格仲裁者,以确定是否存在授予某个特定规则豁免的有效案例。 豁免不是轻易就能获得的。在c++代码中,如果引入了宏API,风格指南要求使用特定于项目的前缀来命名它。由于c++处理宏的方式,将它们视为全局命名空间的成员,所有从头文件导出的宏必须具有全局唯一的名称,以防止冲突。关于宏命名的风格指南规则确实允许对一些真正全局的实用宏进行仲裁授予的豁免。但是,当请求排除特定于项目的前缀的放弃请求背后的原因归结为宏名称长度或项目一致性的首选项时,放弃请求将被拒绝。代码库的完整性比项目的一致性更重要。 允许例外的情况是,允许打破规则比避免打破规则更有益。c++风格指南禁止隐式类型转换,包括单参数构造函数。但是,对于那些设计成透明包装其他类型的类型,其中底层数据仍然是精确和精确表示的,允许隐式转换是完全合理的。在这种情况下,授予对no-implicit-conversion规则的豁免。对有效的豁免有这样明确的理由可能表明有关规则需要加以澄清或修订。然而,对于这个特定的规则,足够的豁免请求,接收符合有效的豁免情况但实际上不是因为特定类型的问题实际上并不是一个透明的包装器类型或因为类型是一个包装器但实际上并不需要保持地方原有的规则仍然是值得的。 指南(Guidance)除了规则之外,我们还以各种形式提供编程指导,从对复杂主题的长而深入的讨论到对我们认可的最佳实践的短而有针对性的建议。 指导代表了我们收集的工程经验的智慧,记录了我们从一路上学到的教训中提取的最佳实践。指导倾向于关注我们观察到的人们经常出错的事情,或者不熟悉的新事物,从而导致困惑。如果规则是“必须”,那么我们的指导就是“应该”。 我们培养的一个指导库的例子是我们使用的一些主要语言的一套引物。虽然我们的风格指南是规定性的,规定了哪些语言特性是允许的,哪些是不允许的,但primer是描述性的,解释了指南认可的特性。他们的内容相当广泛,几乎涉及了谷歌中新接触语言使用的工程师需要引用的每一个主题。它们不会深入研究给定主题的每一个细节,但它们提供解释和推荐使用。当工程师需要弄清楚如何应用他们想要使用的功能时,primer的目的是作为指导参考。 几年前,我们开始发布一系列c++技巧,其中混合了通用语言建议和特定于google的技巧。我们将讨论硬的东西——对象生命期、复制和移动语义、依赖于参数的查找;新的东西——c++的11个特性在代码库中被采用,预先采用的c++的17种类型,如string_view、optional和variant;还有一些东西需要轻微的纠正——提醒不要使用using指令,提醒要记住寻找隐式bool转换。这些技巧来源于所遇到的实际问题,解决了样式指南中没有涉及的实际编程问题。与风格指南中的规则不同,他们的建议并不是真正的经典;他们仍然属于建议类,而不是统治类。然而,考虑到它们是从观察到的模式而不是抽象的理想中成长起来的方式,它们广泛而直接的适用性使它们有别于大多数其他建议,成为一种“共同准则”。这些小贴士的内容都比较狭隘,篇幅也相对较短,每一条都不超过几分钟的阅读时间。这个“每周技巧”系列在内部已经非常成功,在代码评审和技术讨论中经常被引用12。 软件工程师进入一个新的项目或代码库时,已经掌握了他们将要使用的编程语言的知识,但却不知道如何在谷歌中使用编程语言。为了弥补这一差距,我们为使用中的每一种主要编程语言开设了一系列“@Google 101”课程。这些全天的课程关注的是,是什么使使用该语言开发的代码库与众不同。它们涵盖了最常用的库和习惯用法、内部首选项和自定义工具的使用。对于一个刚刚成为谷歌c++工程师的c++工程师,本课程填补了他们不仅是一个好的工程师,而且是一个好的谷歌代码库工程师的缺失。 除了教授旨在让完全不熟悉我们的设置和快速运行的课程外,我们还为深入代码库的工程师提供现成的参考资料,以找到可以帮助他们在工作中发挥作用的信息。这些引用的形式各不相同,并且跨越了我们使用的语言。我们内部维护的一些有用的参考资料包括:
应用Rules(Applying the Rules)就其本质而言,规则在可执行的情况下会带来更大的价值。规则可以通过教学和培训在社会上执行,也可以通过工具在技术上执行。我们在谷歌有各种正式的培训课程,涵盖了我们的规则所要求的许多最佳实践。我们还投入资源保持我们的文件最新,以确保参考材料保持准确和最新。当涉及到对规则的了解和理解时,我们整体培训方法的一个关键部分是代码评审所扮演的角色。我们在谷歌运行的可读性过程——在谷歌特定语言开发环境的新工程师通过代码审查得到指导——在很大程度上,培养我们的风格指南所要求的习惯和模式(详见第三章中可读性过程的细节)。这个过程是我们如何确保这些实践在项目范围内被学习和应用的一个重要部分。 尽管一定程度的培训总是必要的——毕竟,工程师必须学习规则,这样他们才能编写遵循规则的代码——当涉及到检查符合性时,而不是完全依赖于基于工程师的验证,我们强烈倾向于使用工具自动化执行。 自动化规则实施确保规则不会随着时间的推移或组织的规模扩大而被删除或遗忘。新的人加入;他们可能还不知道所有的规则。规则会随着时间而改变;即使有良好的沟通,也不是每个人都能记住所有事情的当前状态。项目不断发展并添加新功能;以前不相关的规则突然适用了。工程师检查规则遵从性取决于内存或文档,这两者都可能失败。只要我们的工具保持最新,与我们的规则更改同步,我们就知道我们的规则被我们所有的工程师应用于我们所有的项目。自动化实施的另一个优点是将解释和应用规则的差异最小化。当我们编写脚本或使用工具检查符合性时,我们根据单个不变的规则定义验证所有输入。我们不会让每个工程师来解释。人类工程师用带有偏见的视角看待一切事物。无论是否是无意识的,潜在的微妙的,甚至可能是无害的,偏见仍然会改变人们看待事物的方式。让工程师来执行很可能导致对规则的解释和应用不一致,也可能导致对责任的期望不一致。我们授权给工具的越多,留给人类偏见进入的入口就越少。 工具还使执行具有可伸缩性。随着组织的成长,一个专家团队就可以编写公司其他部门都可以使用的工具。如果公司的规模扩大一倍,在整个组织内执行所有规则的努力不会增加一倍,它的成本与以前差不多。 即使具有我们通过合并工具获得的优势,也不可能对所有规则进行自动化执行。一些技术规则明确要求人的判断。例如,在c++风格指南中:“避免复杂的模板元编程。”“使用auto来避免类型名称过于嘈杂、明显或不重要——在这种情况下,类型不能帮助读者清晰地理解。“组合通常比继承更合适。”在Java风格指南中:“对于如何[排序你的类的成员和初始化式]并没有一个正确的方法;不同的类可能以不同的方式排列内容。”“对所捕获的异常不做任何响应很少是正确的。"极少覆盖Object.inalize。"对于所有这些规则,判断是必需的,工具还不能代替判断。 其他规则是社会性的而不是技术性的,用技术性的解决方案来解决社会性问题通常是不明智的。对于这个类别下的许多规则,细节往往定义得不太好,工具将变得复杂和昂贵。让人类来执行这些规则通常会更好。例如,当涉及到给定代码变更的大小时(即,受影响的文件数量和被修改的行数),我们建议工程师喜欢较小的变更。对于工程师来说,小的变更更容易进行审查,因此审查往往更快、更彻底。它们也不太可能引入bug,因为更容易推断出较小更改的潜在影响和效果。然而,“小”的定义有些模糊。将相同的单行更新传播到数百个文件的更改实际上可能很容易检查。相比之下,一个较小的20行修改可能会引入复杂的逻辑,并产生难以评估的副作用。我们认识到有许多不同的衡量尺度,其中一些可能是主观的——特别是当考虑到变化的复杂性时。这就是为什么我们没有任何工具来自动拒绝超过任意行限制的建议更改。如果评审者认为一个变化太大,他们可以(而且确实会)推迟。对于这种规则和类似的规则,执行取决于编写和审查代码的工程师。然而,当涉及到技术规则时,只要它是可行的,我们就支持技术强制执行。 错误检查(Error Checkers)许多涉及语言使用的规则可以通过静态分析工具强制执行。事实上,我们的一些c++图书管理员在2018年年中对c++风格指南进行的一项非正式调查估计,其中大约90%的规则可以自动验证。错误检查工具采用一组规则或模式,并验证给定的代码示例是否完全符合。自动验证消除了代码作者记住所有适用规则的负担。如果工程师只需要查找违规警告(其中许多都带有建议的修复程序),那么我们就可以将遵守规则所需的努力最小化。当我们开始使用工具基于源代码标记来标记已弃用的函数时,警告和建议的就地修复都浮出水面,弃用api的新用法问题几乎一夜之间就消失了。降低合规成本使工程师更有可能愉快地贯彻执行。 我们使用像clang-tidy(用于c++)和Error Prone(用于Java)这样的工具来自动化执行规则的过程。关于我们的方法的深入讨论见第20章。 我们使用的工具都是为支持我们定义的规则而设计和定制的。大多数支持规则的工具都是绝对的;每个人都必须遵守规则,所以每个人都使用检查规则的工具。有时,当工具支持最佳实践时,在符合约定方面有更多的灵活性,就会有选择退出机制来允许项目根据其需求进行调整。 代码格式器(Code Formatters)在谷歌,我们通常使用自动样式检查器和格式化器来强制代码内的格式一致。行长度的问题已经不再有趣工程师只是运行风格检查程序13,并继续前进。如果每次都以相同的方式进行格式化,那么在代码审查期间就不会出现问题,从而消除了用来查找、标记和修复小样式错误的审查周期。 在管理有史以来最大的代码库时,我们有机会观察人工格式化和自动化工具格式化的结果。平均而言,机器人比人类好很多。在某些地方,领域专业知识很重要——例如,格式化矩阵,人类通常可以比通用格式化程序做得更好。如果做不到这一点,用自动样式检查器格式化代码很少出错。 我们通过预提交检查来强制使用这些格式化器:在代码可以提交之前,服务会检查在代码上运行格式化器是否会产生任何差异。如果是,提交将被拒绝,并说明如何运行格式化程序来修复代码。谷歌上的大多数代码都要接受这种预提交检查。在我们的代码中,c++使用了clang-format;Python的yapf内部包装器;Go的gofmt;Dart的dartfmt;和 案例分析:gofmt 谷歌于2009年11月10日以开源方式发布了Go编程语言。从那时起,Go 已经发展成为一种开发服务、工具、云基础设施和开源软件的语言14 我们从一开始就知道我们需要一个Go代码的标准格式。我们也知道,在开源版本发布后,几乎不可能再改进标准格式。因此,最初的Go版本包括gofmt,这是Go的标准格式工具。 代码评审是软件工程的最佳实践,但是太多的时间被花在评审中争论格式。虽然标准格式不是每个人都喜欢的,但它足以消除这些浪费的时间。15 通过标准化格式,我们为自动更新Go代码而不产生虚假差异的工具奠定了基础:机器编辑的代码将与人工编辑的代码区分开来。16 例如,在2012年Go 1.0发布之前的几个月里,Go团队使用了一个名为gofix的工具,自动将1.0之前的Go代码更新为Go语言和库的稳定版本。多亏了gofmt, diffs gofix只包含了重要的部分:语言和api使用的变化。这允许程序员更容易地检查更改并从工具所做的更改中学习。 Go程序员希望所有的Go代码都使用gofmt格式。Gofmt没有配置旋杆,它的行为很少改变。所有主要的编辑器和ide都使用gofmt或模仿它的行为,所以几乎所有的Go代码的格式都是相同的。一开始, 成千上万的开源包读写Go代码因为所有的编辑器和idea都支持Go格式17,所以Go工具是可移植的,并且很容易通过命令行集成到新的开发环境和工作流中。 Retrotting 一名工程师花了六周时间,才让谷歌的20万个BUILD文件被不同的代码所有者接受,在此期间,每周都要添加超过1000个新的BUILD文件。谷歌为进行大规模变革而建立的基础设施大大加快了这一努力。(第22章)。 总结(Conclusion)对于任何组织,尤其是像谷歌的工程团队这么大的组织,规则帮助我们管理复杂性并构建一个可维护的代码库。一组共享的规则框定了工程过程,以便它们能够扩展和持续增长,保持代码库和组织的长期可持续性。 内容提要(TL;DRs)
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/16 18:47:42- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |