软件工程是什么<谷歌的软件工程师笔记>

2022-04-27 21:54:22 浏览数 (1)

前言

对于谷歌工作的经验,是每个软件工程师都需要学习的知识,也许我们对于谷歌内部组织以及如何运行可能有不同的思考。

什么是软件工程?

没有什么是建立在石头上的;一切都建立在沙子上,但我们必须把沙子当作石头来建造。

——豪尔赫·路易斯·博尔赫斯

我们看到编程和软件工程之间的三个关键区别:时间、范围和权衡取舍在软件工程项目中,工程师需要更加关注时间的流逝和最终的变更需求在软件工程组织中,我们需要更加关注规模和效率,无论是对于我们生产的软件还是生产软件的组织。最后,作为软件工程师,我们被要求做出更复杂的决策和更高风险的结果,通常基于对时间和增长的不精确估计

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

了解时间对程序影响的一种方法是考虑以下问题:“您的代码的预期寿命是多少?”这个问题的合理答案大约相差 100,000 倍。考虑需要持续几分钟的代码与想象可以存活数十年的代码一样合理。通常,该频谱短端的代码不受时间影响。对于实用程序仅持续一小时的程序,您不太可能需要适应新版本的底层库、操作系统 (OS)、硬件或语言版本。这些短暂的系统实际上“只是”一个编程问题,就像在一个维度上压缩得足够远的立方体是一个正方形一样。随着我们延长时间以延长寿命,改变变得更加重要。在十年或更长的时间里,大多数程序依赖关系,无论是隐式的还是显式的,都可能会发生变化。这种认识是我们区分软件工程和编程的根源。

这种区别是我们所谓的软件可持续性的核心。如果在软件的预期生命周期内,无论出于技术原因还是业务原因,您能够对出现的任何有价值的变化做出反应,那么您的项目就是可持续的。重要的是,我们只在寻找能力——您可能会选择不执行给定的升级,无论是因为缺乏价值还是其他优先事项。

  • 2 当您根本无法对底层技术或产品方向的变化做出反应时,您重新投入高风险赌注,希望这种变化永远不会变得至关重要。对于短期项目,这可能是一个安全的选择。几十年后,它可能不是。
  • 3看待软件工程的另一种方式是考虑规模。有多少人参与?随着时间的推移,它们在开发和维护中扮演什么角色?编程任务通常是个人创造的行为,但软件工程任务是团队的努力。定义软件工程的早期尝试为这一观点提供了一个很好的定义:“多版本程序的多人开发”。
  • 4 这表明软件工程和编程之间的区别在于时间和人。团队协作提出了新的问题,但也提供了比任何单个程序员都更有可能产生有价值的系统的潜力。团队组织、项目组成以及软件项目的政策和实践都支配着软件工程复杂性的这一方面。这些问题是规模所固有的:随着组织的发展和项目的扩展,它在生产软件方面是否变得更有效率?我们的开发工作流程随着我们的成长变得更加高效,或者我们的版本控制策略和测试策略是否会按比例增加我们的成本?围绕通信和人员扩展的规模问题从软件工程的早期就开始讨论,可以追溯到神话人物月。
  • 5 这种规模问题通常是政策问题,并且是软件可持续性问题的基础:做我们需要反复做的事情要花多少钱?

我们也可以说,就需要做出的决策的复杂性及其风险而言,软件工程与编程不同。在软件工程中,我们经常被迫评估几种前进路径之间的权衡,有时风险很高,而且价值指标通常不完善。软件工程师或软件工程领导者的工作是针对组织、产品和开发工作流程的扩展成本的可持续性和管理。考虑到这些输入,评估您的权衡并做出合理的决定。我们有时可能会推迟维护更改,甚至接受不能很好扩展的策略,因为我们知道我们需要重新审视这些决定。这些选择应该明确和明确的递延成本。

时间与变化

当新手学习编程时,生成的代码的生命周期通常以小时或天为单位来衡量。编程作业和练习往往只写一次,几乎不需要重构,当然也不需要长期维护。常见的行业环境中短期代码的开发人员。移动应用程序的生命周期通常很短,而且无论好坏,完全重写是相对常见的。早期创业公司的工程师可能正确地选择关注近期目标而不是长期投资:公司可能没有足够长的时间来获得回报缓慢的基础设施投资的好处。一个连续创业的开发人员可能非常合理地拥有 10 年的开发经验,而很少或根本没有维护任何预计存在超过一两年的软件的经验。

考虑图 1-1,它展示了在这个“预期寿命”范围的两端的两个软件项目。对于从事预期寿命为数小时的任务的程序员,可以合理预期哪些类型的维护?也就是说,如果在您编写将执行一次的 Python 脚本时出现了新版本的操作系统,您是否应该放弃正在做的事情并进行升级?当然不是:升级不是关键。但另一方面,谷歌搜索卡在我们 1990 年代的操作系统版本上将是一个明显的问题。

预期寿命谱上的低点和高点表明某处存在过渡。在一次性计划和持续数十年的项目之间的某个地方,发生了转变:项目必须开始对不断变化的外部性做出反应。对于从一开始就没有计划升级的任何项目,这种转变是由于三个原因可能非常痛苦,每个原因都与其他原因相辅相成:

• 您正在执行该项目尚未完成的任务;更多隐藏的假设已被纳入。

• 尝试进行升级的工程师不太可能有此类任务的经验。

• 升级的规模通常比平时大,一次进行数年的升级,而不是更多的增量升级。

因此,在实际经历过一次这样的升级(或中途放弃)之后,高估后续升级的成本并决定“不再重蹈覆辙”是非常合理的。得出这个结论的公司最终会承诺只是扔掉东西并重写他们的代码,或者决定永远不再升级。与其采取自然的方法避免痛苦的任务,有时更负责任的答案是投资于减轻痛苦。这完全取决于您的升级成本、它提供的价值以及相关项目的预期寿命

随着时间的推移,识别“可以工作”和“可维护”之间的区别没有完美的解决方案,因为保持软件的长期可维护性是一场持久战。

海仑定律

如果您正在维护一个由其他工程师使用的项目,关于“它有效”与“它是可维护”的最重要的教训就是我们称之为 Hyrum 定律:

有了足够数量的 API 用户,您在合同中承诺的内容并不重要:您系统的所有可观察行为都将取决于某人。

根据我们的经验,这个公理是任何讨论随时间变化的软件的主要因素。它在概念上类似于熵:关于随时间变化和维护的讨论必须注意海伦定律 就像讨论效率或热力学必须注意熵一样。仅仅因为熵永远不会减少并不意味着我们不应该努力提高效率。仅仅因为在维护软件时适用海伦定律并不意味着我们不能为它做计划或试图更好地理解它。我们可以减轻它,但我们知道它永远无法根除。

Hyrum 定律代表了实践知识——即使有最好的意图、最好的工程师和可靠的代码审查实践——我们也不能假设完全遵守已发布的合同或最佳实践。作为 API 所有者,您将通过明确接口承诺获得一些灵活性和自由,但在实践中,给定更改的复杂性和难度还取决于用户发现您的 API 的某些可观察行为的有用程度。如果用户不能依赖这些东西,你的 API 将很容易更改。如果有足够的时间和足够的用户,即使是最无害的变更也会破坏某些东西;9您对该变更价值的分析必须包含调查、识别和解决这些破坏的困难。

示例:哈希排序

考虑哈希迭代排序的例子。如果我们将五个元素插入一个基于哈希的集合中,我们以什么顺序将它们取出?

代码语言:javascript复制
>>> for i in {"apple", "banana", "carrot", "durian", "eggplant"}: print(i) ...
durian
carrot
     apple
     eggplant
     banana

大多数程序员都知道哈希表是非明显有序的。很少有人知道他们使用的特定哈希表是否打算永远提供特定排序的细节。这可能看起来不起眼,但在过去的十年或两年中,计算行业使用此类类型的经验发生了变化:

• 哈希泛洪攻击为非确定性哈希迭代提供了更大的动力。

• 研究改进的散列算法或散列容器的潜在效率增益需要更改散列迭代顺序。

• 根据 Hyrum 定律,如果程序员有能力,他们将编写取决于遍历哈希表的顺序的程序。

因此,如果你不知道你的代码能存活多久,或者你不能保证你所依赖的任何东西都不会改变,那么这种假设是不正确的。”此外,即使您自己的实现不依赖于散列容器顺序,它也可能被其他隐式创建这种依赖关系的代码使用。例如,如果您的库将值序列化为远程过程调用 (RPC) 响应,则 RPC 调用者可能会根据这些值的顺序结束。

可扩展性和效率

正如站点可靠性工程 (SRE) 一书中所述,谷歌的整个生产系统是人类创造的最复杂的机器之一。构建这样一台机器并保持其平稳运行所涉及的复杂性需要我们组织和全球的专家进行无数小时的思考、讨论和重新设计。

书的大部分内容都侧重于生产这种机器的组织规模的复杂性,以及我们用来保持机器长时间运行的过程。再次考虑代码库可持续性的概念:“当您能够安全地更改您应该更改的所有内容并且可以在代码库的生命周期内这样做时,您组织的代码库就是可持续的。”隐藏在能力讨论中的还有一项成本:如果改变某件事的成本过高,它可能会被推迟。如果成本随着时间的推移呈超线性增长,那么运营显然是不可扩展的。12 最终,时间会占据主导地位,并且会出现一些你绝对必须改变的意想不到的事情。当项目范围扩大一倍并且您需要再次执行该任务时,劳动密集型是否会增加一倍?下次你还会有解决问题所需的人力资源吗?

人力成本并不是唯一需要扩展的有限资源。正如软件本身需要与传统资源(如计算、内存、存储和带宽)很好地扩展一样,该软件的开发也需要扩展,无论是在人力时间参与方面,还是在为您的开发工作流程提供动力的计算资源方面。如果您的测试集群的计算成本呈超线性增长,每季度每人消耗更多的计算资源,那么您将走上一条不可持续的道路,需要尽快做出改变。

最后,软件组织最宝贵的资产——代码库本身——也需要扩展。如果您的构建系统或版本控制系统随着时间的推移呈超线性扩展,可能是由于增长和不断增加的变更日志历史记录,那么您可能无法继续进行下去。许多问题,例如“完成一个完整的构建需要多长时间?”、“拉取一个新的存储库副本需要多长时间?”或“升级到新版本需要多少成本?语言版本?”没有受到积极的监控并且变化缓慢。他们很容易变得像比喻的水煮青蛙;问题很容易慢慢恶化并且从不表现为一个单一的危机时刻。只有拥有全组织范围的意识和对扩展的承诺,您才有可能掌握这些问题。

不可扩展的原则

  • 基础设施团队必须自行将内部用户迁移到新版本,或者以向后兼容的方式进行适当的更新。
  • 开发分支的传统使用是具有内置扩展问题的策略,但是带来重复性的开销。

可扩展的原则

随着组织规模的扩大,专业知识和共享交流论坛有巨大的价值。

示例:编译器升级

考虑升级编译器的艰巨任务。 理论上,考虑到语言向后兼容需要付出多少努力,编译器升级应该很便宜,但在实践中它的操作有多便宜? 如果您以前从未进行过这样的升级,您将如何评估您的代码库是否与该更改兼容?

结论

  • “软件工程”在维度上不同于“编程”:编程是关于生成代码。软件工程将其扩展到包括维护该代码的使用寿命。
  • 短期代码和长期代码的生命周期之间至少有 100,000 倍的因子。假设相同的最佳实践普遍适用于该范围的两端是愚蠢的。
  • 当在代码的预期生命周期内,我们能够响应依赖关系、技术或产品需求的变化时,软件是可持续的。我们可以选择不改变事情,但我们需要有能力。
  • Hyrum 定律:如果一个 API 有足够多的用户,那么你在合同中的承诺并不重要:你系统的所有可观察行为都将依赖于某个人。
  • 就人工输入而言,您的组织必须重复执行的每项任务都应该是可扩展的(线性或更好)。策略是使流程可扩展的绝佳工具。
  • 流程效率低下和其他软件开发任务往往会慢慢扩大。小心煮青蛙的问题。
  • 当与规模经济相结合时,专业知识的回报特别好。“因为我这么说”是做事的可怕理由。
  • 数据驱动是一个好的开始,但实际上,大多数决策都是基于数据、假设、先例和论证的混合。当客观数据构成这些输入的大部分时是最好的,但它很少是全部。随着时间的推移,数据驱动意味着需要在数据发生变化时(或假设被消除时)改变方向。错误或修改计划是不可避免的。

参考资料

Software Engineering at Google -

Lessons Learned from Programming Over Time

0 人点赞