Contents

谷歌软件工程之道-1

《Software Engineering at Google》:

  • 前言,序言
  • 什么是软件工程?

EN: https://abseil.io/resources/swe-book/html/toc.html

CN: https://qiangmzsx.github.io/Software-Engineering-at-Google/#/

前言

技术的伟大之处在于,做一件事永远不会只有一种方法。相反,有一系列的权衡,我们都必须根据我们的团队和现状来选择。

如果认为运行你的软件工程组织的正确方法是精确地复制他们的模式,那就太愚蠢了。在实际应用中,这本书会给你提供关于如何做事情的想法,以及很多信息,你可以用这些信息来支持你采用最佳实践的论据,如测试、知识共享和建立协作团队。

看菜吃饭,经过权衡之后,选择适合当前规模的模式。

序言

本书强调了三个基本原则,我们认为软件组织在设计、架构和编写代码时应该牢记这些原则:

  • 时间和变化​代码会如何在其生命周期内进行适配。
  • 规模和增长​一个组织会如何适应它的发展过程。
  • 权衡和成本​一个组织如何根据时间和变化以及规模和增长的经验教训做出决策。

什么是软件工程?

在一个软件工程项目中,工程师需要更多关注时间成本和需求变更

在软件工程中,我们需要更加关注规模和效率,无论是对我们生产的软件,还是对生产软件的组织。

最后,作为软件工程师,我们被要求做出更复杂的决策,其结果风险更大,而且往往是基于对时间和规模增长的不确定性的预估

软件工程是随着时间推移的编程。 编程当然是软件工程的一个重要部分:毕竟,编程首先是生成新软件的方式。如果你接受这一区别,那么很明显,我们可能需要在编程任务(开发)和软件工程任务(开发、修改、维护)之间进行划分。 时间的增加为编程增加了一个重要的新维度。这是一个立方体三维模型不是正方形的二维模型,距离不是速度。软件工程不是编程。

了解时间对程序的影响的一种方法是思考“代码的预期生命周期是多少?”(这里生命周期指 “维护生命周期” —— 代码将持续构建、执行和维护多长时间?这个软件能提供多长时间的价值?

随着我们扩大时间维度,允许更长的生命周期,改变显得更加重要。

的确,对于只是短期存在的代码,是不需要考虑太多的,也不值得投入太多,因为像上面说的,时间这个维度已经被压缩地非常低,所以这可能会是一个“海拔”并不太高的细小长方体。

对于将会存在相当长一段时间的代码来说,任何一种细微的变化,都将可能影响到他本来已经稳定的工作。这是需要投入相对等的时间去进行工程建设的。

这种区别是我们所说的软件可持续性的核心。如果在软件的预期生命周期内,你能够对任何有价值的变化做出反应,无论是技术还是商业原因,那么你的项目是可持续的。

这里说的是,已经因为依赖项已经不被支持(维护)等而无法做出改变,或者是已经到了不能改,改不了的地步,这个时候,这个项目已经是真正意义上的屎山(危楼)了。

当你基本上无法对基础技术或产品方向的变化做出反应时,你就把高风险赌注押在希望这种变化永远不会变得至关重要。

比如操作系统升级,Node 版本升级等。

另一种看待软件工程的方法是考虑规模。

软件工程和程序设计之间的区别是时间和人的区别。团队协作带来了新的问题,但也提供了比任何单个程序员更多的潜力来产生有价值的系统。

时间与变化

下图演示了两个软件项目的“预期生命周期”的范围。对于从事预期生命周期为小时的任务的程序来说,什么类型的维护是合理的?也就是说,如果在编写一个Python脚本时出现了一个新版本的操作系统,该脚本将执行一次,你应该放弃你正在做的事情并升级吗?当然不是:升级并不是紧急的。但与此相反,谷歌搜索停留在 20 世纪 90 年代的操作系统版本上显然是一个问题。

/images/figure 1-1.png

不仅完成了第一次大升级,而且达到可靠地保持当前状态的程度,这是项目长期可持续性的本质。可持续性要求规划和管理所需变化的影响。对于谷歌的许多项目,我们相信我们已经实现了这种持续能力,主要是通过试验和错误

那么,具体来说,短期编程与生成预期生命周期更长的代码有何不同?随着时间的推移,我们需要更多地意识到“正常工作”和“可维护”之间的区别。识别这些问题没有完美的解决方案。这是不幸的,因为保持软件的长期可维护性是一场持久战。

海勒姆定律

如果你正在维护一个由其他工程师使用的项目,那么关于“有效”与“可维护”最重要的一课就是我们所说的海勒姆定律: 当一个 API 有足够多的用户的时候,在约定中你承诺的什么都无所谓,所有在你系统里面被观察到的行为都会被一些用户直接依赖。

思考一下用“当前可用”和“一直可用”心态编写的代码之间的差异,我们可以提取出一些明确的关系。将代码视为具有(高度)可变生命周期需求的构件,我们可以开始对编程风格进行分类:依赖其依赖性的脆弱和未发布特性的代码可能被描述为“黑客”或“聪明”而遵循最佳实践并为未来规划的代码更可能被描述为“干净”和“可维护”。两者都有其目的,但你选择哪一个关键取决于所讨论代码的预期生命周期。我们常说,“如果‘聪明’是一种恭维,那就是程序,如果‘聪明’是一种指责,那就是软件工程。”

这里吐槽下,一些人在公司代码添加自己编写的库,但是这个库不是封装无用的一层又一层,就是文档注释几乎为零,或两者兼之。这种聪明,徒增复杂性,“加强”可维护性,可谓万恶之源。在软件工程中,建议做一个开明的独裁者,强制最大共识的约定,防止坏味道泛滥。

没有对可持续性的长期项目进行投入是存在巨大风险。 我们必须能够应对这些问题,并利用好机会,无论它们是否直接影响我们,或者仅仅表现为我们所建立的技术的过渡性封闭中。变化本质上不是好事。 我们不应该仅仅为了改变而改变。但我们确实需要有能力改变。如果我们考虑到最终的必要性,我们也应该考虑是否加大投入使这种能力变得简单易用(成本更低)。正如每个系统管理员都知道的那样,从理论上知道你可以从磁带恢复是一回事,在实践中确切地知道如何进行恢复以及在必要时需要花费多少钱是另一回事。实践和专业知识是效率和可靠性的重要驱动力。

规模与效率

当你能够安全地改变你应该改变的所有事情,你的组织的代码库是可持续的,并且可以为你的代码库的生命做这样的事情。” 隐藏在能力的讨论中也是成本的一个方面:如果改变某事的代价太大,它可能会被推迟。如果成本随着时间的推移呈超线性增长,操作(这里指的是做出应有的改变)显然是不可扩展的(可以理解为无法做到的)。最终,时间会占据主导地位,出现一些意想不到的情况,你必须改变。

软件的开发也需要进行扩展,包括人力时间的参与和支持开发工作流程的计算资源。如果测试集群的计算成本呈超线性增长,每个季度人均消耗更多的计算资源,那么你的项目就走上了一条不可持续的道路,需要尽快做出改变。

最后,软件系统最宝贵的资产代码库本身也需要扩展。

这个因为变更日志过多,内容增长,会影响到拉取的时间,完成构建的时间等等,时间越久的项目受影响会越大。

示例:编译器升级

潜在的教训不是关于编译器升级的频率或难度,而是一旦我们意识到编译器升级任务是必要的,我们就找到了方法,确保在代码库增长的情况下,由固定数量的工程师执行这些任务。

左移

/images/figure 1-2.png

安全问题不能推迟到开发过程的最后阶段,必须要求“在安全上向左转移”。

根据新的安全约束规范进行开发,要比提交代码后再让其他人分类标识并修复它更简单。

在提交之前通过静态分析和代码审查发现的 bug 要比投入生产的 bug 成本更低。在开发过程的早期提供高质量、可靠性和安全性的工具和实践是我们许多基础架构团队的共同目标。没有一个过程或工具是完美的,所以我们可以采取纵深防御的方法,希望尽早抓住图表左侧的缺陷。

权衡和成本

做出好的工程决策就是权衡所有可用的输入,并就权衡做出明智的决策。 有时,这些决策是基于本能或公认的最佳实践,但仅是一种假设之后,我们用尽了各种方法来衡量或估计真正的潜在成本。

重审决策,标记错误

我们坚信数据能为决策提供信息,但我们也认识到数据会随着时间的推移而变化,新数据可能会出现。这意味着,本质上,在相关系统的生命周期内,需要不时地重新审视决策。 对于长期项目而言,在做出初始决策后,有能力改变方向通常是至关重要的。更重要的是,这意味着决策者需要勇气承认错误。与人的本能相反,勇于承认错误的领导人受更多的尊重。

我们认为,区分相关但不同的术语“编程”和“软件工程”是很重要的。这种差异很大程度上源于随着时间的推移对代码的管理、时间对规模的影响以及面对这些想法的决策。 编程是产生代码的直接行为。软件工程是一组策略、实践和工具,这些策略、实践和工具是使代码在需要使用的时间内发挥作用,并允许整个团队的协作。