软件应该设计成经久耐用

2020-06-29 03:23:04

让我们先提个醒,我将在这本出版物上分享的思考可能不是每个人都喜欢的,但这是一个我想展开辩论的话题。

假设您想要使用您最喜欢的编程语言为您的系统构建一个代理服务器,您该怎么做呢?首先,您可以选择您更习惯的Web框架,这样您就有了一个项目的脚手架,您就可以直接开始实现了。通过这个简单的步骤,您就可以启动服务器并接收开箱即用的HTTP请求。在您的服务器中,您希望根据其源地址和正文内容对请求进行身份验证并过滤流量。因此,与您的Web服务器框架一起,您选择安装一个中间件来处理身份验证,并安装一个库来简化新请求的处理和过滤(我们什么时候,在90年代?您不会重新发明轮子并独自开发所有这些,您希望在两三天内让您的系统在生产中运行)。您可以使用一些胶水代码将所有这些部分粘合在一起,丢弃一些单元和集成测试以查看一切都在工作,您就可以开始工作了。

你知道前面提到的方法有什么问题吗?您的项目最终80%是其他人的代码,来自您的项目所利用的外部库(参见上图),20%是您自己的代码(以及一些来自StackOverflow的“灵感”),充当粘合剂和粗略的定制,以使库按照您的用例所需的方式工作。这种方式是一个不断发展的趋势,不要误会我的意思,如果您正在构建的是一项众所周知的技术,或者是一个辅助系统,比如您的个人CMS,或者一个内部报告系统,那么您很快就会有一些东西启动并运行起来,并且您将尽快开始从您的系统的所有好处中受益。当你对关键系统或数百万用户使用的科技公司平台的核心实现继续遵循这种方法时,问题就真的出现了。相信我,所有规模的公司都是如此(询问顾问公司正在进行的项目的类型,以及为这些公司投入的人力-70%是最近毕业的)。

软件开发确实受到了“快速行动,突破一切”哲学的影响。这很好,我完全同意及早测试新想法,快速迭代以验证您的概念,并在开发过程中尽可能地“精简”。软件原型开发很便宜,何乐而不为呢?这就是创新的意义所在,对吧?当你开始发布你伟大的项目和绝妙的想法时,问题就出现了,它们开始闯入生产,损害了你的用户。就连Facebook也意识到,是时候从“快速行动,突破局面”转变为“快速行动,但基础设施稳定”。

你永远不会指望土木工程师在他的概念验证做了一些调整之后,就把他的桥(一个关键的基础设施)交付给“完工”的人。更何况,桥梁和土木工程项目的设计都是经久耐用的,为什么我们也可以设计软件让它经久耐用呢?正确地设计一座桥需要时间,并且需要对引擎盖下发生的事情(材料、结构、弹性等)有很好的理解。如果我们想让软件工程被认为是一种工程实践(这是理所当然的),我们需要开始构建健壮的、有弹性的和持久的系统,并且停止将我们遇到的任何代码片段丢弃到我们的项目中。当然,我为我的极端概括道歉,我并不是说所有的软件都是草率的,前几天我读到了一篇关于SpaceX软件工程的文章,我真的很喜欢他们如何使用软硬件冗余来最大限度地减少像航天飞机这样关键的系统中的潜在错误。我只是想说,让我们都更像航天飞机软件工程师。

有人还在StackExchange上的回答中讲述了他们与SpaceX团队在GDC2015⁄2016年的互动。他们谈到了三重冗余系统,以及SpaceX公司如何使用演员-法官系统。简而言之,定制板上运行着3个双核ARM处理器(根据elteto的说法)。对于每个决策,“飞行字符串”会比较单个处理器上每个内核的结果。如果输出匹配,则将命令发送到不同的控制器。有3个处理器(双核),这意味着每个控制器/传感器将收到三个不同的命令。然后,控制器充当判断者,并比较这三个命令。如果这三个人都同意,他们就会执行操作。即使只有一个命令不一致,控制器也会执行来自处理器的命令,该处理器之前一直在发送正确的命令。

这意味着在任何给定点,飞行软件都有6个正在运行的进程。

所有这些想法使我决定尝试从外部依赖中解毒,看看它是否

当然,我并不是主张一直停止使用外部依赖项,而是从头开始实现一切。那就太疯狂了!我要说的是省去所有不是严格必要的东西。例如,当Go的http标准库为您提供了构建一台出色的Web服务器所需的一切时,为什么还要使用Go Web框架呢?(我完全同意这个人的观点)。这是我几年前开始构建我的第一个go rest API时经常遵循的做法的完美示例,现在我已经完全放弃了这一做法。这就是我所说的“停止滥用依赖”的一个例子。我不需要Web框架。是的,它在一开始极大地加快了我的开发速度,但不知道幕后发生了什么,当我面临错误或想要提高系统性能时,不能完全控制我在做什么,这真的是扰乱了我的OCD(正如我在这里简要提到的)。

使用框架感觉就像驾驶一辆新的飞车,直到它在几英里外抛锚,你不知道如何修理它。

有一些核心依赖项我永远不会建议您从您的“常用”列表中删除,因为为您的项目从头开始编写它们的功能将是自杀的(因为它们是由非常有才华的个人组成的团队数小时开发的结果)。举几个我最近在我的系统上使用的例子,比如libp2p、Geth客户端、Polkadot或Qiskit(好的,您可能会从我选择的库中看到我有点偏颇:)。如果我的实现没有它们,我早就死了。当然,如果您需要构建P2P网络,我不会要求您重写libp2p。如果您需要它的“其他东西”,它是开放源码的,因此您可以随时尝试,并提出添加建议。如果你需要玩量子计算,同样的道理也适用于Qiskit。他们都有出色的开发团队和研究人员,致力于最大限度地利用他们的产品,所以只要你知道自己在做什么,以及为什么要使用它们(记住使用它们可能会带来潜在的管理费用),就去做吧。

因此,我的观点不是“停止使用外部依赖项”,而是“明智地使用它们”。如果您已经知道如何使用首选编程语言的可用功能对JSON进行解组和解析,请不要懒惰,使用维护不善的JSON解析库。这样可以避免不必要的管理费用和麻烦。同样,与其使用你发现“完成工作”的第一个库,你应该花一些时间研究替代方案,以便选择更适合你需要的库,或者甚至完全放弃使用库来完成任务并编写自己的库的想法(就像土木工程师对桥梁所需的材料和基础进行彻底分析的方式一样)。

让我们转到导致我得出这些结论的原因,以及为什么我试图不过度使用外部依赖。使用库有风险,您至少应该意识到这一点。

您使用的库和外部依赖项可能已过时、维护不足或/并且存在隐藏的安全缺陷和性能瓶颈。当您是代码的所有者时,您“某种程度上”知道自己在做什么,但是当您使用其他地方的代码时,您不知道开发人员是否犯了错误(只要您没有阅读和理解您要添加到项目中的代码)。您不知道他是否使用了低效的实现,或者如果您不检查源代码,您就不知道他是否添加了恶意代码。更重要的是,如果该库被开发人员抛弃,而您需要在未来的系统中依赖它,该怎么办呢?归根结底,这一切都取决于理解您正在使用的代码。下一段完美地说明了使用外部依赖项的风险:

攻击者经常使用社会工程将他们的包放入应用程序中。他们创建一个具有有用功能的包,然后偷偷插入一些恶意代码。一旦代码进入应用程序,用户启动应用程序,代码就会攻击用户。

从今年3月开始,黑客就是这样从用户那里窃取了价值150万美元的加密货币(以及近1300万美元)。

第0天(3月6日):攻击者将一个模块发布到npm:Electronics-Native-Notify。这似乎很有用--它帮助电子应用程序以一种跨平台工作的方式发出原生通知。到目前为止,它还没有任何恶意代码。

第二天:要实施抢劫,攻击者必须将此模块放入加密货币应用程序中。他们选择的矢量是帮助用户管理加密货币Agama Wallet的应用程序中的依赖项。

第41-66天:应用程序被重建,拉入依赖项的最新版本,并与之一起进行电子原生通知。此时,它开始向服务器发送用户的“种子”(用户名/密码组合)。然后,攻击者可以使用这些种子清空用户的钱包。

第90天:用户警告NPM电子原生通知中的可疑行为,他们通知加密货币平台,加密货币平台将资金从易受攻击的钱包转移到安全的钱包。

还有很多这样的例子说明了不分青红皂白地使用图书馆的风险(快速搜索一下,看看这是如何发生的)。

库可以显著增加代码的大小和编译时间。这篇很棒的博客文章生动地说明了铁锈的问题。我强烈推荐它读一读。您可能会遇到这样的情况:当您最终使用整个库的单个函数时,您可能会包含一个完整的库来执行某些任务(以及由此带来的开销)。或者,如何处理您在开发和试验解决方案时包含在Package.json中并且忘记清除的所有依赖项呢?所有这些加起来都对你不利。

库可能会向您隐藏解决方案中的许多权衡、设计决策和潜在故障点。当您完全设计和实现系统的所有基本代码时,识别潜在的攻击矢量比将此信任委托给各种外部依赖项要容易得多。

最后,在您的系统中不使用任何库都是具有挑战性和趣味性的。当然,这不是一个令人信服的理由,但即使你不同意我的观点,我也建议你一生中至少像这样对待你的设计一次。您将看到在这个过程中学到了多少东西;您如何更好地掌握您所选择的编程语言的技能和理解力;能够完全掌控并清楚地了解您的系统在任何时候都在幕后做什么,这是多么令人耳目一新。我能说什么呢,我是那种会拆开他祖父的收音机来了解它是怎么工作的孩子。

我们需要开始构建弹性软件。这是我们现在智能合同和分布式系统领域越来越意识到的一种趋势。在这种类型的系统中,网络中数以万计的用户共享并执行相同的基本代码。因此,软件故障或安全缺陷不只影响个人的基础设施,而是系统中的每个人。据报道,许多智能合同失败造成数百万美元的损失,客户端软件中的错误损害了他们的系统。

从购买食品杂货到与家人和朋友聊天,我们周围的一切都越来越依赖软件。就像我们不想让我们的车在下一次旅行中有缺陷和撞车一样,我们也不应该容忍软件在某些情况下失败。更新库应该不会扰乱您的系统,考虑到它们应该能够在不需要任何外部管理或维护的情况下在野外运行多年,应该(尽可能)以与桥梁设计相同的方式设计一款软件。

我们是否应该着手编写弹性软件宣言?这件事我不知道,但让我们把这件事公开辩论吧。同时,我很想听听你的想法。

软件幻灭。如果你必须从这个列表中选择一个读物,那就选这个吧。它完美地补充了我的观点(我在写作过程结束时发现了这一点,所以我不能在我的讨论中添加许多概念)。