在太多图书馆的坑坑洼洼中绊倒

2020-08-10 13:38:54

在我最近的一次写作休息时间,我思考了一系列似乎总是出现在软件可靠性或可维护性问题上的事情。根据我对这个幼年概念的反应,他们中至少有一个可能不会和我交很多朋友。尽管如此,我认为它还需要探索。

简而言之,我认为对于使用某些编程语言的人来说,使用互联网这一广阔的小丑世界中的库已经变得太容易了。他们的生态系统使得依赖这些东西变得非常非常容易。问题是,这些图书馆经常是狗屎。如果它的某些部分损坏了,您可能无法对其进行编码,并且可能必须实际处理它们才能修复它。

其中一些来自最近与朋友的一次聊天。他们问我为什么认为其他人喜欢一种特定的语言。我说过,很多语言都有庞大的在线图书馆藏书,它们使得导入它们变得非常容易--也就是说,依赖它们。我现在谈论的东西比CPAN(对于Perl)要容易得多。我现在谈论的是围棋、铁锈等较新的东西,是的,还有node.js。人们真的很喜欢这些东西!我补充说,Python并非易事,但在这方面有一些工具可以帮助你。

显然,语言的选择在这里并不是特别重要。你几乎可以用任何东西把自己带入这种境地。这是一种心态问题:你愿意把这份工作卸给图书馆吗?

所以他们问,另一种选择可能是什么。我说,嗯,首先,没有一大堆可以马上导入的库可能是一个开始。有些语言只允许你指向GitHub的URL或其他任何东西(有些甚至更简单),仅此而已。他们说,这将导致更多的工作来完成人们使用这些语言完成的几乎任何事情。我同意这一点,说人们会发现他们不得不写更多的东西。

我的朋友并不认为那听起来很有趣。我反驳说,对我来说,能够进口(依赖)任何琐碎的东西的情况听起来并不有趣。显然,我们在这件事上没有达成一致。我们谈到了一些关于胶水语言的帖子,就在不久前,这些胶水语言成为了常见的HN回合,他认为大多数程序员都属于胶水类。他认为,如果没有解决问题的流行和容易导入的库,软件会更糟糕。

我的猜测是,人们进入这种情况时,似乎图书馆将是一个可靠的100%解决方案,但它让你失望,甚至可能达到80%的关口。有缺少的最佳实践、明显的设计缺陷、错误、安全漏洞或您能想象到的任何其他东西。你伸手去拿虚拟货架上的软件,希望它是坚固的,但往往并非如此。(如果是的话,我就不会在这里抱怨了。)。

我有一个故事,有一次,有人选择尽可能多地将他们的工具卸载到外部库中,这给他们的用户带来了痛苦。当我试图使用他们的工具时,它出现在我的雷达上,但遇到了同样的障碍。

有一个工具,你必须运行它才能与库伯内斯的东西交谈。由于基础设施中的一些古怪决策,您真的不能自己运行实际的原生CLI工具。你必须把它放进这个包装纸里。

我正在构建一个概念验证工具,它将连接到您的几百个作业实例,并有效地跟踪它们的标准,并在您的本地计算机上以一种有用的方式聚合它们。它的目的是向人们展示日志处理不是魔术,您可以从一些相对简单的工具中获得价值,但这不是这里的故事。这个故事是我不得不打给的包装纸,以及它发生了什么。

在处理这个问题时,我让他们的包装器工具进入这个失败模式,在这种模式下,它会抱怨一些损坏的配置文件,仅此而已。它最终会像这样锁存,然后所有调用都会失败。似乎以我的大规模并行方式运行它使得它更有可能发生。

我自己做了功课,并在公司聊天和bug系统中挖掘了其他关于这种情况的报告,结果发现了一些不太有希望的事情:负责的团队告诉受影响的用户删除他们主目录中的一些点文件。这就是所有的回应。不太好。

我也可以那样做,但问题是,这种情况最终会再次发生。我并行运行的实例越多,锁住坏状态的速度就越快,然后我就不得不关闭整个系统,丢弃文件,然后重新开始。

我对此感到厌倦,于是决定开动脑筋。这就是这个故事回到80%图书馆问题的地方。

原来这个点文件被这个程序用来记住它最后一次运行的时间,以及它最后一次向你大喊自己老了的时间。团队已经决定,您手动放到Mac上的这个二进制文件在足够旧的时候需要定期进行投诉。但是,然后他们就这么做了,这样它就不会每次都抱怨了。它有一个计数器,可能每隔十次就会大叫一次,或者类似的叫声。

所以,它有一个记录这些东西的文件。问题是,那个文件被破坏了。当发生这种情况时,它无法将其读回,由于这被视为致命错误,程序将不会继续。没有--无视那该死的东西的旗帜要继续走下去。

查看该文件会发现一些奇怪的东西:该文件看起来像是有一组完整的";var=value&34;行,但随后它又有了文件的尾部。

也就是说,它可能类似于";这是一个配置文件。G文件。它几乎就像是一个较长的版本被写入文件,然后在该文件之上写入一个较短的版本,但该文件随后并没有被截断。很奇怪,对吧?

当然,任何沿着这条路走过的人现在都会在屏幕上跳来跳去地大喊大叫,因为他们没有使用锁定!或者他们没有使用临时文件并重新命名或类似的东西。是的,他们是对的。这东西完全没有做这两件事。

当您运行它时,它只是打开文件并进行写入。如果你并行运行很多次,它们会互相践踏,不出所料,结果有时会产生一个无法完全解析的配置文件。

它可以写入mktemp()类型函数的结果,然后使用rename()自动将其放置到位。但事实并非如此。

预料到了这一点,我拿到了一份他们的源码,并去寻找没有写文件偏执的内容的地方。我找不到它。我所找到的只是对这个执行配置文件读写的库的引用,以及对它的几次调用。实际文件I/O隐藏在Internet上某个地方的另一个库中。

果然,该代码无法进行合理的锁定或原子写入。更糟糕的是,没有机会给它一个合理的文件描述符,或者欺骗它写入一个安全的临时路径,然后我可以在公司的包装工具中将其重命名到适当的位置。

修复它的唯一方法是在这个第三方库中。这将意味着要么从那里分叉并维护它,要么与上游合作并希望他们认真对待我并接受它。

我已经说过,我在这些事情上有过不愉快的时光,所以我倾向于不参与这样的项目。

我决定我已经按原样做了很多,不打算清理他们的烂摊子。这个团队选择使用这个库,这样他们就可以弄清楚他们将如何处理这个问题,并将修复程序推向上游,或者诸如此类。

我与团队一起打开了一份公司内部错误报告:工具X在与自身的其他实例竞争时会损坏文件Y,然后不会运行。我链接到其他人遇到它的地方,以表明不只是我在做一些病态的事情(以免他们试图贬低我的报告)。

他们做出了回应。他们做了什么?他们让它捕捉到点文件读取失败的情况,并使它不会破坏整个程序。取而代之的是,他们只是继续生活,就好像它不在那里一样。

这里错失了太多机会。在不同的环境中,这将是一个向人们传授锁、原子写入、write()可以在消耗整个缓冲区之前返回的事实,以及所有其他有趣的Unixy知识的机会。然后,我们就可以编写一些合理的代码,并在该公司的所有代码中使用。

但是,由于他们已经放弃了这一责任,他们受到了一些项目的摆布,这些项目没有特别的理由去关心他们。我永远不会知道为什么团队选择吞下错误来处理我的报告,而不是处理上游的错误,但这就是发生的事情。

现在将这个模式重复一百万次,你就会得到今天的世界状况:一堆永远不会消失的愚蠢的剪纸。

人们对这种情况的反应方式还有另一个问题。我把上面的故事告诉了一个了解我的职业生涯的人,他曾在某些公司工作过,拥有很多真正的Linux机器。他们说的话实在令人沮丧:

如果将许多非平凡的库应用于解决Google规模的问题,它们将包含各种问题。

是的,那是对的,因为我为G或FB或其他什么公司工作,无论何时我遇到问题,都是因为我想要做规模太大的事情,是因为我想要做一些规模太大的事情吗?你在耍我吗?赶快。

我说,他们试图随意忽略我认为是桌上赌注的那类事情,这就是我的问题所在。这在第一次并没有完全落地,所以我尝试了另一种方法。

我遇到了一个问题(嘿,这个东西因为X和Y的原因一直在变坏),突然之间,它是因为我从G或FB或其他什么地方买的东西,而我想从他们的东西里拿到不合理的东西(#34;M&34;34;I&34;I&34;m&34;to a#34;G&m&34;34;to I";m&34;from";G或FB或诸如此类的东西)。所以,我的请求是无效的,不过还是谢谢你开车。

这就是我所说的80%。我住在另外20%的地方,因为我需要更多的东西来工作。这并不罕见,因为,请记住,我的聊天日志和错误/票证搜索都发现了其他人报告了同样的问题。这就是我如何学到的智慧,只要删除这个点文件就行了。

似乎可以归结为:人们依赖图书馆。结果大部分都是废话。你介绍的越多,你就越有可能得到一些非常糟糕的东西。因此,似乎理性的做法是对这些事情非常挑剔,如果有的话,也不要抓得太多。

但是,如果您向后工作,您可以看到,使添加一些随机库变得非常容易意味着更有可能有人会这样做。把它想象成一个吸引人的麻烦。这使曲柄转动,接下来您知道,您将看到令人叹为观止的依赖树-充满了愚蠢的小缺点,并且缺乏最佳实践。

现在我们有了这个难题。这一个库降低了人们编写该工具的门槛。是真的。不能否认这一点。

但是,这给了他们一种虚假的完成感和安全感,而这既不是完成的,也不是安全的。如果使用足够多,该工具最终将失败,并且(至少在他们添加忽略失败的读取内容之前),会将自身锁定到损坏状态,并且在没有手动干预的情况下不会再工作。

问问自己:这真的是一件好事吗?您希望人们能够在不了解具体情况的情况下发布这样的代码吗?是的,我们显然必须指出,系统不应该在底层变得如此复杂,不得不担心原子写入和锁定是令人恼火的地狱,但这就是存在的情况。如果你打算直接使用文件系统,你必须解决它。它是POSIX文件系统世界附带的包袱的一部分。

这整件事进入了更黑暗的地方,但我想我现在就到此为止。不用说,未来我还有更多关于这个更大主题的文章要写。